Skip to main content

astroclr

Astroclr allows perceived colours of light sources to be calculated from the physical spectra that emit them, and conversions between various colour spaces. This allows spectra radiated from (for example) stars to be converted into colours that can be replicated on a computer display.

Astroclr is heavily inspired by Colorpy by Mark Kness, which was an invaluable resource for me when calculating colours of white dwarfs, and developing my own interest in colour science. While Colorpy is well documented and referenced, it is no longer actively maintained, and requires several tweaks for compatibility with python3. Astroclr was written with the express aim of making colour calculations from spectra as simple as possible, usng a modern python interface, and with a particular focus on observational astronomy.

Features

  • A modern interface with a variety of colour classes and methods to convert between them (all supporting numpy arrays for multiple objects or images)
    • XYZ_Color
    • RGB_Color
    • IRGB_Color
    • UCS_Color
    • LUV_Color
    • LAB_Color
  • A bespoke Spectrum class with easy conversion to XYZ colours
    • XYZ integrations are performed trapezoidally, so wavelengths can have arbitrary spacing.
    • Works with astropy units for calculating luminance, illuminance, and visual magnitudes
    • Easy generation of black body spectra
  • Support for a variety of RGB colour spaces
    • sRGB
    • Rec709
    • Rec2020/2100
    • DCI P3
  • A suite of example scripts for interesting things an astronomer might want to do, and demonstrations of various colour science concepts

Note: All documentation uses the British spelling of "colour" (since I am British). For code, however, I have opted to use "color", for consistency with Colorpy, and other similar software (and to save on typing). The module name was chosen to be neutral towards spelling.

Installation:

pip install astroclr

Examples

Calculating colours with astroclr has been made as easy as possible, with the basic recipe looking like:

  • create a Spectrum object
  • convert to the CIE 1931 XYZ colour space
  • convert from XYZ to a linear RGB colour space (sRGB by default)
  • convert linear RGB to the non-linear display RGB (IRGB) space by applying a transfer function

Lets say we want to determine the sRGB colour of a 10,000 K black body (something like the A-type star Sirius A) as a hexstring. In Astroclr we can follow the above recipe as follows:

>>> from astropy import units as u
>>> from astroclr import Spectrum, RGB_Color

>>> lam = np.arange(300, 900, 0.1) * u.nm
>>> S = Spectrum.BlackBody(temperature=10000*u.K, wavelength=lam)
>>> XYZ = S.to_XYZ()
>>> RGB = RGB_Color.from_XYZ(XYZ, brightest=True)
>>> IRGB = RGB.to_IRGB()
>>> print(IRGB.to_string())
'#CDD9FF'

The brightest keyword, ensures the brightest RGB channel is scaled to 1.0, so observed spectra (typically faint) can be shown as bright as possible, and bright spectra (such as models) can be displayed without colour clipping. The above code can be actually be performed even more succinctly:

>>> S = Spectrum.BlackBody(temperature=10000*u.K, wavelength=lam)
>>> print(S.to_IRGB_string(brightest=True))
'#CDD9FF'

You may have noticed that most conversion methods are prefixed with 'to', but to convert to RGB we need to use a 'from' method. This is because RGB colour spaces (the default is sRGB) are defined in terms of XYZ chromaticities (see below), and so all colour spaces are defined in terms of the more fundamental XYZ_Color. This can be seen by inspecting the sRGB colour space:

>>> from astroclr import sRGB
>>> print(sRGB)
RGB_ColorSpace(name='sRGB', R=XYZ_Color(0.64, 0.33), G=XYZ_Color(0.3, 0.6), ...)

If you have your own spectrum, calculating colours is just as simple:

>>> wavelength, flux = np.loadtxt("my_spectrum.dat", unpack=True)
>>> wavelength *= u.AA
>>> flux *= u.erg / u.s / u.cm**2 / u.AA
>>> S = Spectrum(wavelength, flux)
>>> print(S.to_IRGB_string(brightest=True))
'#DDFFB8'

Note that fluxes must be in terms of spectral radiance/irradiance (per unit wavelength), i.e. equivalent units to W/m²/nm/sr or W/m²/nm, which is necessary for calculating the luminance/illuminance using the to_luminance() method. If no units are provided, default units of nm and W/m²/nm/sr are assumed for wavelengths and fluxes.

Colour science basics

If we have a spectrum $F_\lambda(\lambda)$ and want to calculate its displayable RGB colour. Mathematically we do something akin to synthetic photometry in astronomy:

X = \int F_\lambda(\lambda) \bar{x}(\lambda) \,\mathrm{d}\lambda \qquad
Y = \int F_\lambda(\lambda) \bar{y}(\lambda) \,\mathrm{d}\lambda \qquad
Z = \int F_\lambda(\lambda) \bar{z}(\lambda) \,\mathrm{d}\lambda

where $\bar{x}(\lambda)$ etc, are the CIE 1931 colour matching functions (CMFs), and $X,Y,Z$ are the integrated fluxes in the XYZ colour space. The CMFs were empirically determined and do not directly represent cone sensitivites. All curves have the same area, and $\bar{y}(\lambda)$ is the photopic luminous efficiency function. Therefore for calculated $(X,Y,Z)$ triplets, $Y$ can be taken to be the perceived brightness/intensity/luminance. image

But what if we want to consider the colour independent of its brightness? Then we can normalise the $X, Y, Z$ by scaling by their sum:

x = \frac{X}{X+Y+Z} \qquad y = \frac{Y}{X+Y+Z} \qquad z = \frac{Z}{X+Y+Z}

Having lost a degree of freedom (the intensity), $x$ and $y$ now uniquely define the brightness-independent component of colour and is called the chromaticity. The CIE 1931 chromaticity diagram is shown below, where the shark-fin shape corresponds to monochromactic colours and bounds all perceivable colours, the triangle shows the sRGB gamut (colours outside the triangle are approximated by clipping negative values), and the black curve starting in the red corner is the blackbody locus (ticks from 2000K to 10000K in 1000K steps). image

The sRGB colour space specifies the chromaticities of the red/green/blue primaries (and also the white point), which uniquely define a linear transformation between XYZ and sRGB.

    \left(\begin{array}{c}
    R \\
    G \\
    B
    \end{array}\right) =
    \left(\begin{array}{rrr}
     3.2410 & -1.5374 & -0.4986 \\
    -0.9692 &  1.8760 &  0.0416 \\
     0.0556 & -0.2040 &  1.0570
    \end{array}\right) \cdot
    \left(\begin{array}{c}
    X \\
    Y \\
    Z
    \end{array}\right)

These give linear intensities in the R/G/B channels in the range 0 to 1, where (R,G,B)=(1,1,1) represents full white. To convert these to display RGB values (IRGB in astroclr), we cannot simply multiply by 255 and round. As the display scale is non-linear. We therefore have to apply an opto-electrical transfer function (OETF). In some colour-spaces this is just a power-law (gamma curve), though in sRGB a piecewise function is used that is linear at low-brightness and a shifted plus scaled power-law at higher brightness, but closely resembles a power-law with exponent 1/2.2:

    V_\mathrm{out} = \left\{
    \begin{array}{ll}
    12.92V_\mathrm{in} & \mathrm{if}\quad V_\mathrm{in} \leq 0.0031308 \\
    1.055V_\mathrm{in}^{1/2.4} - 0.055 & \mathrm{otherwise}
    \end{array}
    \right.

After applying a OETF, RGB values can be multiplied by 255 and rounded to determine the 8-bit per channel display values.

Metadata

Release files for astroclr 1.0.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for astroclr 1.0.3
File Size Uploaded Provenance
astroclr-1.0.3.tar.gz 3.0 MB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for astroclr 1.0.3
File Interpreter ABI Platform
astroclr-1.0.3-py3-none-any.whl Python 3 none any Details

Total release size: 3.2 MB

Release files / astroclr-1.0.3.tar.gz

Download URL astroclr-1.0.3.tar.gz
Size 3.0 MB
Tags Source
SHA-256 checksum
How to use checksums
fcdd91767441a839f20991efdc56229d253348ff40d00e04af5222884d1554e8
BLAKE2b-256 checksum
How to use checksums
0c7676728dd08a53b59d3e2cad13aef49794b5a0eaf9ad37408b376c0d5ccbb4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.9

Release files / astroclr-1.0.3-py3-none-any.whl

Download URL astroclr-1.0.3-py3-none-any.whl
Size 243.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8e58e86f8138e8d574c6d691ac633b2b73440f83d3003196a169e71296a8e934
BLAKE2b-256 checksum
How to use checksums
6098fa27023e44e9113265a0ab5f159db496873acce3cbe9060c053b213bb7fa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.9

Security for astroclr 1.0.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page