API reference¶
The public API is curated in melanopy.__all__. Everything below is importable directly from the
top-level package, e.g. import melanopy as mp; mp.rate_colormap(...).
Rating¶
rate_colormap ¶
Rate an sRGB ramp on the melanopic axis (display white = 1.0).
Both summary numbers are luminance-weighted and ignore near-black pixels (which emit almost nothing), so neither is dominated by the dark end of the ramp.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
colors
|
array - like
|
An |
required |
panel
|
str
|
Display archetype selecting the per-primary coefficients (see :data: |
'representative'
|
profile
|
bool
|
When |
False
|
Returns:
| Type | Description |
|---|---|
dict
|
|
Examples:
melanopic_ratio ¶
Melanopic ratio (M/P) of one or more sRGB colours, normalized so display white = 1.0.
< 1 is protective (warm, low melatonin drive); > 1 is alerting (cool, blue-rich).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rgb
|
array - like
|
A single |
required |
panel
|
str
|
Display archetype selecting the per-primary coefficients (see :data: |
'representative'
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The melanopic ratio per input colour. |
Examples:
circadia_rating ¶
Rated melanopic ratio of a Circadia map at alpha — the physical number behind the dial.
alpha is a geometric position on the OKLab morph, not a melanopic ratio, and the M/P a
viewer actually receives is panel-dependent. This composes the generator and the rater so a
live UI (e.g. a labelled slider) can show the rated M/P for its configured panel rather
than bare alpha. The recompute is cheap (a vectorized 256-point rating); a hot UI may
memoize on (round(alpha, 3), panel).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
alpha
|
float
|
Position on the Circadia axis in |
required |
panel
|
str
|
Display archetype selecting the per-primary coefficients (see :data: |
'representative'
|
Returns:
| Type | Description |
|---|---|
tuple of float
|
|
Examples:
Generator — the Circadia family¶
The circadia(alpha) family walks from protective (warm) to alerting (cool) while holding lightness
and CVD-recoverability fixed. Each member is a drop-in matplotlib colormap:
import matplotlib.pyplot as plt
import numpy as np
import melanopy as mp
grad = np.linspace(0, 1, 256).reshape(1, -1)
fig, axes = plt.subplots(5, 1, figsize=(7, 3))
for ax, alpha in zip(axes, [0.0, 0.25, 0.55, 0.75, 1.0]):
ax.imshow(grad, aspect="auto", cmap=mp.circadia(alpha, as_cmap=True))
ax.set_ylabel(f"α = {alpha}", rotation=0, ha="right", va="center")
ax.set_xticks([])
ax.set_yticks([])
plt.show()
circadia ¶
One member of the Circadia family — a perceptually-uniform, CVD-safe ramp at dial alpha.
alpha sets the warm→cool colour temperature: 0 is protective (Sodium, warm, low
melanopic), 1 is alerting (Xenon, cool, high melanopic), and ~0.55 is the M/P ≈ 1
crossover. It is the design input; read the emergent melanopic ratio back with
:func:circadia_rating or :func:rate_colormap.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
alpha
|
float
|
Position on the Circadia axis in |
required |
n
|
int
|
Number of samples in the ramp. |
256
|
as_cmap
|
bool
|
Return a :class: |
False
|
name
|
str
|
Name for the colormap when |
None
|
Returns:
| Type | Description |
|---|---|
ndarray or ListedColormap
|
An |
Examples:
circadian_cmap is an alias of circadia.
circadia_sweep ¶
Full-axis sequential map sweeping protective (Sodium, warm) -> alerting (Xenon, cool), with
the melanopic ratio rising ~linearly along the ramp -- the "data axis is the melanopic axis"
teaching map. The shared monotone OKLab lightness keeps it ordered and CVD-recoverable; the
positions are calibrated against the rater so M/P is linear in the data value. This is the one
melanopic-aware generator (via a local import); circadia itself stays pure OKLab geometry.
Examples:
circadia_diverging ¶
Diverging map for signed data: warm Sodium (protective, M/P<1) <- light neutral centre (M/P=1) -> cool Xenon (alerting, M/P>1). Each arm has monotone lightness, but -- like most diverging maps -- the arms are told apart across zero by hue, so it is NOT CVD-order-recoverable; use the sequential maps where CVD-safety matters.
Examples:
circadia_sweep walks the whole axis along one ramp (a teaching map whose melanopic ratio rises
~linearly with the data value); circadia_diverging is a warm↔cool diverging map, neutral at the
zero crossing:
import matplotlib.pyplot as plt
import numpy as np
import melanopy as mp
grad = np.linspace(0, 1, 256).reshape(1, -1)
maps = [("circadia_sweep", mp.circadia_sweep(as_cmap=True)),
("circadia_diverging", mp.circadia_diverging(as_cmap=True))]
fig, axes = plt.subplots(2, 1, figsize=(7, 1.2))
for ax, (name, cmap) in zip(axes, maps):
ax.imshow(grad, aspect="auto", cmap=cmap)
ax.set_title(name, loc="left", fontsize=9)
ax.set_axis_off()
plt.show()
The named anchors SODIUM (alpha=0.0), EQUILUX (alpha=0.55, the M/P = 1 crossover),
and XENON (alpha=1.0) are exported as ready-made matplotlib.colors.ListedColormap objects.
matplotlib integration¶
register ¶
Register sodium/xenon/equilux + circadia_sweep/circadia_diverging as matplotlib colormaps.
After calling this, the maps are available by name to any matplotlib call. Safe to call more than once.
Examples:
Accent palette¶
CIRCADIA_ACCENT (with CIRCADIA_ACCENT_NAMES) is a small set of vivid colours for qualitative
marks — ticks, points, event lines — drawn over a Circadia colormap fill, where ordinary
qualitative colours can be lost against the fill or clash with it. They are chosen to sit outside the
family's colour footprint across the whole axis (so they contrast with warm and cool fills) and to
stay mutually distinct under colour-vision deficiency — which confines them to the
magenta/violet/green arc. Vivid by design.
import matplotlib.pyplot as plt
import melanopy as mp
fig, ax = plt.subplots(figsize=(7, 1.2))
for i, (color, name) in enumerate(zip(mp.CIRCADIA_ACCENT, mp.CIRCADIA_ACCENT_NAMES)):
ax.add_patch(plt.Rectangle((i, 0), 0.9, 1, color=color))
ax.text(i + 0.45, -0.12, name, ha="center", va="top")
ax.set(xlim=(0, 4), ylim=(0, 1))
ax.set_axis_off()
plt.show()
For qualitative marks that are not over a fill, the melanopic axis does not apply (small marks emit negligibly) — any CVD-safe qualitative palette (e.g. ColorBrewer, Okabe–Ito) serves.
Adding a measured panel¶
The built-in panels (representative, led_lcd, oled, wide_gamut) are spectral archetypes,
not measurements — so a reported M/P is indicative, not metrological. The protective ↔ alerting
ranking is stable across panels (Spearman ρ ≥ 0.99), so relative comparisons hold on any panel;
only the absolute M/P needs a measured one.
For research-grade work, measure the three primary SPDs (R, G, B at full drive) on the package's
1 nm grid (melanopy.spectra.WL, 380–780 nm) and derive the coefficients:
>>> from melanopy.spectra import coefficients_from_primaries
>>> coeffs = coefficients_from_primaries({"R": spd_r, "G": spd_g, "B": spd_b}) # doctest: +SKIP
Add the result to melanopy.coeffs.PANELS as a new keyed row (e.g. "lab_monitor": {...}); the
panel="lab_monitor" argument then threads through rate_colormap, melanopic_ratio, and
circadia_rating.
coefficients_from_primaries ¶
Derive the three per-primary M/P coefficients from measured primary SPDs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
primaries
|
dict
|
|
required |
smel
|
array - like
|
Override the melanopic action spectrum / V(λ); default to the shipped CIE tables. |
None
|
vlam
|
array - like
|
Override the melanopic action spectrum / V(λ); default to the shipped CIE tables. |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
|
Examples: