A melanopic axis for colormaps: rate, rank, and generate colormaps by circadian (melanopic) content.
Project description
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file melanopy-0.0.1.tar.gz.
File metadata
- Download URL: melanopy-0.0.1.tar.gz
- Upload date:
- Size: 41.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e72782cd3afa4a4c1b7c27e7e0e4a4f7852fcf1e82d75fa32f9971a55c16a850
|
|
| MD5 |
335636be960b6b5bde79720df349f765
|
|
| BLAKE2b-256 |
e02ba13eb87cadb595ee6d070bfbc6168f663ad3549d140ea744cc57c909e4a1
|
Provenance
The following attestation bundles were made for melanopy-0.0.1.tar.gz:
Publisher:
release.yml on remrama/melanopy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
melanopy-0.0.1.tar.gz -
Subject digest:
e72782cd3afa4a4c1b7c27e7e0e4a4f7852fcf1e82d75fa32f9971a55c16a850 - Sigstore transparency entry: 2082555232
- Sigstore integration time:
-
Permalink:
remrama/melanopy@b160074141b79733f43116027b7bf5623e28b262 -
Branch / Tag:
refs/tags/v0.0.1 - Owner: https://github.com/remrama
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b160074141b79733f43116027b7bf5623e28b262 -
Trigger Event:
release
-
Statement type:
File details
Details for the file melanopy-0.0.1-py3-none-any.whl.
File metadata
- Download URL: melanopy-0.0.1-py3-none-any.whl
- Upload date:
- Size: 35.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d4207d7dd4a5cf82b2d2814756e20852daf7838f68efb17bec967d256bb70ad6
|
|
| MD5 |
3719b1fd9494f63bfb5ddcab18587269
|
|
| BLAKE2b-256 |
ca6a9ace3348bb78cf84b910987e6bc0bb5864187cb4b769464a7692bd21a569
|
Provenance
The following attestation bundles were made for melanopy-0.0.1-py3-none-any.whl:
Publisher:
release.yml on remrama/melanopy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
melanopy-0.0.1-py3-none-any.whl -
Subject digest:
d4207d7dd4a5cf82b2d2814756e20852daf7838f68efb17bec967d256bb70ad6 - Sigstore transparency entry: 2082555255
- Sigstore integration time:
-
Permalink:
remrama/melanopy@b160074141b79733f43116027b7bf5623e28b262 -
Branch / Tag:
refs/tags/v0.0.1 - Owner: https://github.com/remrama
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b160074141b79733f43116027b7bf5623e28b262 -
Trigger Event:
release
-
Statement type: