Skip to main content

Derive, optimise and audit a scientific colour palette from one seed colour

Project description

palettecore

Derive, optimise and audit a scientific colour palette from one seed colour.

Existing tools let researchers pick established colour maps. This one starts from the researcher's own anchor colour and returns a palette plus its diagnostics — because "harmonised" and "accessible" are separate properties, and a generator that returns bare HEX codes is hiding the second one.

Grounded in Crameri, Shephard & Heron (2020), The misuse of colour in science communication, Nature Communications 11:5444.

Free and open source, Apache-2.0. Python core now; R package with scale_colour_accessible() / scale_fill_accessible() in r/, checked against the same fixtures.

What it checks (not "verifies")

Every palette ships with:

  • adjacent / pairwise CIEDE2000 distances under normal vision
  • the same distances under simulated protanopia, deuteranopia, tritanopia
  • lightness monotonicity and greyscale luminance (what print does to it)
  • sRGB gamut status (chroma is clamped, never channel-clipped)
  • WCAG contrast against the declared background, judged by declared use
  • warnings whenever a threshold is not met — the honest output for seeds that cannot satisfy every constraint at once

Usage

Python:

from palettecore import generate_palette

result = generate_palette(
    seed="#8B6FC9",
    n=8,
    kind="sequential",   # or "diverging", "categorical"
    background="#FFFFFF",
    use="data_fill",     # or "text", "line", "UI"
    anchor="path",       # or "exact" — see Stated conventions
)
result.hexes         # 8 HEX codes
result.diagnostics   # the audit
result.warnings      # what did NOT pass

Command line (also the agent-friendly entry point — --format json returns the full machine-readable audit):

python3 -m palettecore "#8B6FC9" -n 8 --kind sequential --format json

How it works

All work happens in OKLCH (path construction, gamut clamping) with CIEDE2000 as the distance metric. Nothing is interpolated in RGB or HSL.

  • Sequential — a dense OKLCH path at the seed hue (monotonic lightness, chroma envelope scaled by seed chroma, gamut-clamped per stop), then n stops chosen by arc-length reparametrisation: equal spacing in cumulative CIEDE2000, so near-equal perceptual steps hold by construction rather than by free optimisation.
  • Categorical — seed anchored exactly, then greedy farthest-point placement on a constrained hue circle (lightness band, moderated chroma), followed by swap-improvement passes on the maximin objective: maximise the minimum pairwise distance across normal vision and all three CVD simulations.
  • Diverging — two sequential halves meeting at a near-neutral light centre. The second pole (seed hue + 180°) is a design assumption and is flagged as such in the warnings.

Stated conventions

These are the decisions a reader needs before trusting any number the audit reports.

White point. sRGB is interpreted under its native D65 illuminant throughout; the CIELAB reference white is D65 and no chromatic adaptation to D50 (or anything else) occurs anywhere. CIELAB/CIEDE2000 values are D65-relative.

CVD model. Machado, Oliveira & Fernandes (2009) matrices at severity 1.0 on the paper's [0, 1] scale (some libraries write the same endpoint as 100). Severity 1.0 simulates complete dichromacy; the milder anomalous trichromacies are not modelled, so a palette passing here has been checked against the extreme case only. The matrices are used as published — no fixture-level parity with other implementations (colorspacious, colorblindr, …) has been established, and their pipelines may differ.

CVD gamut policy. The Machado transform can leave displayable sRGB. Simulated colours are clamped channel-wise in linear RGB, because the clamped colour is what a display actually shows — audit distances are measured on displayed colours. The pre-clamp excursion magnitude is reported in the diagnostics (cvd_gamut.max_linear_excursion_before_clamp) so you can see when clamping may have distorted a measured separation.

Seed anchoring. Categorical palettes always contain the seed exactly. For sequential/diverging the seed defines the path (hue + chroma envelope) but the exact HEX is not guaranteed to appear; anchor="exact" snaps the nearest stop to the seed at the cost of slightly uneven spacing, and the audit reports seed_nearest_stop_deltaE under either policy.

Thresholds. The ΔE floors (normal ≥ 8, each CVD condition ≥ 6) are package-default design rules, not established universal accessibility cut-offs. They are configurable (thresholds=), recorded in every result (thresholds_used), and labelled as defaults there too.

Determinism. No random state anywhere — candidate grids, greedy selection and swap passes are deterministic, so identical inputs always give identical palettes. No seed to store.

Dependencies

numpy only. All colour science — OKLab/OKLCH, CIELAB, CIEDE2000, the Machado matrices — is implemented in-package so the numbers are inspectable and portable.

Tests and cross-language parity

python3 -m pytest tests/ -q

71 tests, including a pathological-seed battery (pure primaries, near-black, near-white, neutrals) and no-false-pass checks (impossible constraints must warn, never silently pass).

fixtures/parity.json (regenerate with tools/make_fixtures.py) is the cross-language contract: conversions and distances must match within 1e-6, palette HEX codes exactly. The R implementation in r/ is validated against it — one reference algorithm, two frontends.

Roadmap

  • use="text" mode that constrains generation, not just warns
  • Configurable severity (<1.0) for the CVD simulations
  • Fixture-level comparison against colorspacious/colorblindr
  • PyPI / CRAN packaging

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

palettecore-0.2.0.tar.gz (21.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

palettecore-0.2.0-py3-none-any.whl (19.9 kB view details)

Uploaded Python 3

File details

Details for the file palettecore-0.2.0.tar.gz.

File metadata

  • Download URL: palettecore-0.2.0.tar.gz
  • Upload date:
  • Size: 21.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for palettecore-0.2.0.tar.gz
Algorithm Hash digest
SHA256 70c036aef0491491ea18806051e2f1e73810b7b834e5f8dd17f7d6b1cb42982e
MD5 7f930eb95d9ca21047642ab396c9c781
BLAKE2b-256 6eb728873a0f5727933c12eee4ca01802ea4019207123f92eb8234fba8cb293e

See more details on using hashes here.

Provenance

The following attestation bundles were made for palettecore-0.2.0.tar.gz:

Publisher: publish.yml on heidihelena/palettecore

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file palettecore-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: palettecore-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 19.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for palettecore-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4830aefad8ef96d365a73e7497e856373e2fd67fb1d9e0792ecd8418363b750d
MD5 eeac651b24ee7f191f5af29b529c36a3
BLAKE2b-256 34026ae758bea6b88a219f8077305ef413d349a2967f46076d475e35ee72ae73

See more details on using hashes here.

Provenance

The following attestation bundles were made for palettecore-0.2.0-py3-none-any.whl:

Publisher: publish.yml on heidihelena/palettecore

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page