Melanopy: A melanopic axis for colormaps
Scientific colormaps are usually judged on two axes: perceptual uniformity and colour-vision-deficiency (CVD) safety. Melanopy adds a third: how much short-wavelength, melanopic (melatonin-suppressing) light a map emits. Unlike the other two, this third axis is a design dimension you choose by context, not a pass/fail gate — a sleep lab wants its protective end, a daytime alerting display the other. Melanopy makes the axis measurable, scores existing colormaps on it, and provides a one-parameter family that walks it while holding uniformity and CVD-safety fixed.
For people who read screens as their main light source at night (e.g., sleep labs, observatories, NICU, night radiology, control rooms) large data-fills (e.g., spectrograms, density maps) actually emit light, and their colour content can cooperate with, or fight, a chosen circadian lighting strategy.
This rates a colour's chromaticity, not light dose. Real circadian load also depends on screen brightness, screen fill, viewing distance, and ambient light. If you genuinely need to stay alert, the dominant lever is room lighting. The value here is measurability, a scored index, a uniformity-preserving generator, and surfacing the axis. The physiological effect of a colormap alone is second-order.
📖 Documentation: https://remrama.github.io/melanopy
Quickstart
import matplotlib.pyplot as plt
import numpy as np
import melanopy as mp
# Score any colormap on the melanopic axis (display white = 1.0)
c = plt.get_cmap("viridis")(np.linspace(0, 1, 256))[:, :3]
print(mp.rate_colormap(c))
# {'melanopic_ratio': ~0.83, 'mp_spread': ..., 'range': (...)}
# Use the named endpoints (registered with matplotlib) on any 2-D field Z
mp.register()
Z = np.add.outer(np.linspace(0, 1, 200), np.linspace(0, 1, 200))
plt.imshow(Z, cmap="sodium") # protective: warm, low-melanopic
plt.imshow(Z, cmap="xenon") # alerting: cool, high-melanopic
plt.imshow(Z, cmap="equilux") # circadian-neutral (M/P ~ 1)
# Dial the whole axis: alpha 0 (protective) .. 1 (alerting)
cmap = mp.circadia(0.3, as_cmap=True)
# Or walk the whole axis in one map, or diverge for signed data
seq = mp.circadia_sweep() # protective -> alerting; melanopic ratio ~linear in the data
div = mp.circadia_diverging() # signed: warm protective <- neutral -> cool alerting
melanopic_ratio < 1 → protective (warm); > 1 → alerting (cool/blue).
The two metrics
- M/P mean (
melanopic_ratio) — where a map sits on the axis (white = 1). - M/P spread (σ) (
mp_spread) — how tightly it sits. A map can be mildly protective on average yet smeared (e.g. viridis dumps blue at its dark end); a tight spread reads as a "pure" ramp. Both numbers are luminance-weighted, so near-black pixels that emit almost nothing don't dominate either one.
What scoring the existing maps reveals
Applying the rater to the colormaps people already use settles how much new design is even needed —
three non-obvious facts fall out (full table in index/):
- A protective, pure map already exists —
copper(M/P 0.49, σ 0.03) andsodium(0.29); the protective end just needed naming, not inventing. - The popular uniform maps are smeared — viridis / magma / inferno / cividis / plasma sit mid-axis but dump high-melanopic blue at their dark, low-data end (σ ≈ 0.4–1.0).
- The genuine gap is a uniform, CVD-safe alerting map — the slot the Circadia family's
xenonendpoint is built to fill.
Scope & novelty
- Borrowed — the melanopic metrology: the CIE S 026 melanopic action spectrum + V(λ),
validated against the luox reference calculator (melanopy reproduces the
CIE S 026 D65 constant to five significant figures — see
manuscript/luox_crosscheck.md). - New — the port to colormaps: the per-display three-coefficient collapse (any sRGB colour → a weighted sum of three per-primary M/P numbers, so no runtime spectral integration); the mean + spread decomposition that catches "smeared" maps; and the constraint-preserving generator in which melanopic content is emergent, never optimized.
Display panels
Melanopic content depends on the display's primary spectra, which sRGB doesn't fix — so the
rater takes a panel= argument selecting among representative archetypes (representative
narrowband, led_lcd blue-pump white-LED, oled, wide_gamut quantum-dot). Absolute M/P
shifts with the panel (the blue coefficient ranges ≈8.8 to ≈13.7), but the ranking is
robust: Spearman ρ ≥ 0.99 across panels, and display white stays exactly 1.0 (see
index/). For exact numbers on a specific monitor, plug its measured
primary SPDs into melanopy.spectra.coefficients_from_primaries.
Metadata
Release files for melanopy 0.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| melanopy-0.0.1.tar.gz | 41.2 kB | With provenance | Details for melanopy-0.0.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| melanopy-0.0.1-py3-none-any.whl | Python 3 | none | any | Details for melanopy-0.0.1-py3-none-any.whl |
Total release size: 76.3 kB
Release files / melanopy-0.0.1.tar.gz
| Download URL | melanopy-0.0.1.tar.gz |
|---|---|
| Size | 41.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e72782cd3afa4a4c1b7c27e7e0e4a4f7852fcf1e82d75fa32f9971a55c16a850
|
|
BLAKE2b-256 checksum How to use checksums |
e02ba13eb87cadb595ee6d070bfbc6168f663ad3549d140ea744cc57c909e4a1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 5, 2026.
Transparency logRelease files / melanopy-0.0.1-py3-none-any.whl
| Download URL | melanopy-0.0.1-py3-none-any.whl |
|---|---|
| Size | 35.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d4207d7dd4a5cf82b2d2814756e20852daf7838f68efb17bec967d256bb70ad6
|
|
BLAKE2b-256 checksum How to use checksums |
ca6a9ace3348bb78cf84b910987e6bc0bb5864187cb4b769464a7692bd21a569
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 5, 2026.
Transparency logSecurity for melanopy 0.0.1
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.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for melanopy-0.0.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 5, 2026.
Transparency logA 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.
To learn more about protecting yourself from malicious actors, visit our package security documentation.