A data-driven analytical color space trained on 64,000 human color-difference observations
Project description
Helmlab
A family of purpose-built color spaces for UI design systems.
Helmlab provides two complementary color spaces: MetricSpace for perceptual distance measurement (STRESS 22.48 on COMBVD with Bradford CAT — 23% better than CIEDE2000's 29.20), and GenSpace for gradient/palette generation (62-9-19 vs OKLab on ColorBench's 90 metrics including independent datasets, 360/360/360 gamut cusps, zero monotonicity violations in sRGB/P3 — 1 in Rec.2020).
Website | Documentation | Playground | Paper
Key Features
- State-of-the-art color difference — MetricSpace: STRESS 22.48 vs CIEDE2000's 29.20 on COMBVD (3,813 pairs, with Bradford CAT pre-processing)
- Superior gradient generation — GenSpace: 62 wins vs OKLab's 9 (19 ties) across 90 ColorBench metrics (3,038 gradient pairs, 3 gamuts), 360/360/360 valid cusps, zero monotonicity violations in sRGB/P3 (1 in Rec.2020)
- Depressed cubic transfer —
y³ + αy = x(α=0.021): eliminates cusp singularities while preserving gradient quality. Exact analytical inverse via hyperbolic functions - Chroma power — Mild compression (C^0.978) improves gradient step uniformity across 3,038 pairs
- L-gated hue enrichment — Targeted hue rotation in the blue region, gated by lightness. Fixes blue→white purple shift without affecting other colors
- True blue gradients — Blue→White midpoint is sky blue (G/R = 1.51), not lavender
- Perfect achromatic axis — Grays map to C* ≈ 10⁻¹⁵ (structural guarantee from uniform transfer function)
- Perfectly uniform gradients — Built-in CIEDE2000 arc-length reparameterization, CV ≈ 0% on any pair
- Embedded Helmholtz-Kohlrausch — MetricSpace: lightness is chroma-dependent, learned from data
- UI tooling — Gamut mapping, WCAG contrast enforcement, palette generation, dark/light mode adaptation
- Token export — CSS (
oklch()), Android XML, iOS Swift (Display P3), Tailwind, JSON
Installation
npm (TypeScript / JavaScript)
npm install helmlab
import { Helmlab } from 'helmlab';
const hl = new Helmlab();
hl.gen.gradient('#ff0000', '#0000ff', 8); // Perfectly uniform gradient
hl.gen.gradient('#00f', '#fff', 8, { gamut: 'display-p3' }); // wide-gamut output
hl.gen.scale('#3B82F6'); // Tailwind-style 50-950 scale
hl.gen.harmonies('#3B82F6', 'triadic'); // constant-L,C hue rotations
hl.gen.vivid('#6488b8', { gamut: 'display-p3' }); // chroma to the gamut boundary
hl.gen.ensureContrast('#3B82F6', '#ffffff', 4.5); // adjust to meet 4.5:1
hl.metric.difference('#ff0000', '#00ff00'); // trained perceptual metric
hl.metric.jnd('#808080', '#828282'); // just-noticeable-difference units
hl.metric.ciede2000('#ff0000', '#00ff00'); // industry standard, ~86.6
hl.tokens.css('#3B82F6', 'oklch'); // design-token export
~17.8KB gzipped, zero dependencies, ESM + CJS with full TypeScript types. Full API reference: helmlab.space/docs.
PostCSS
Use Helmlab color spaces directly in CSS — transformed to rgb() at build time:
npm install postcss-helmlab
/* Input */
.card { color: helmlab(0.78 0.52 -0.20); }
.bg { background: linear-gradient(in helmgen, #e63946, #457b9d); }
/* Output */
.card { color: rgb(255, 76, 119); }
.bg { background: linear-gradient(#e63946 0.0%, ..., #457b9d 100.0%); }
Python (pip)
pip install helmlab
Quick Start (Python)
One Helmlab instance, three namespaces: hl.gen creates colors,
hl.metric measures them, hl.tokens exports design tokens.
The two spaces have branded Lab types — mixing them raises a TypeError
instead of silently producing a wrong color.
from helmlab import Helmlab
hl = Helmlab()
# ── Create (GenSpace) ──────────────────────────────────────────────
hl.gen.gradient("#ff0000", "#0000ff", 8) # equal perceptual steps
hl.gen.gradient("#0000ff", "#ffffff", 8, gamut="display-p3") # wide-gamut output
hl.gen.mix("#ff0000", "#0000ff") # visual midpoint (same path)
hl.gen.scale("#3b82f6") # Tailwind 50-950, 500 == input
hl.gen.harmonies("#3b82f6", "triadic") # constant-L,C hue rotations
hl.gen.vivid("#6488b8", gamut="display-p3") # chroma -> gamut boundary
hl.gen.cusp(263) # most colorful point of a hue
hl.gen.ensure_contrast("#ffffff", "#3b82f6", ratio=4.5, strict=True)
# ── Measure (MetricSpace) ──────────────────────────────────────────
hl.metric.difference("#ff0000", "#00ff00") # trained metric (COMBVD 22.48)
hl.metric.jnd("#808080", "#828282") # in just-noticeable-difference units
hl.metric.ciede2000("#ff0000", "#00ff00") # industry standard, ~86.6
hl.metric.confidence("#808080", "#828282") # observer-disagreement estimate
hl.metric.info("color(display-p3 1 0 0)") # wide-gamut input works everywhere
# ── Export (hex in, tokens out) ────────────────────────────────────
hl.tokens.css("#3b82f6", "oklch") # 'oklch(62.3% 0.1881 259.8)'
hl.tokens.css_variables(hl.gen.scale("#3b82f6"), "--primary")
JS is the camelCase mirror (hl.gen.ensureContrast(...)). Parity is
enforced by a permanent cross-language gate: every string output is
byte-identical, numeric worst-case difference ~1e-12, and hex round-trips
are bit-exact on a 1728-color grid in both spaces and both languages.
Full surface: API.md. Upgrading from 0.x: the flat
hl.gradient()/hl.from_hex() API was removed in 1.0 — every method now
lives on hl.gen / hl.metric / hl.tokens.
Architecture
Helmlab is a family of purpose-built color spaces:
Helmlab (UI layer)
├── MetricSpace — 72-param enriched pipeline (distance, deltaE)
│ XYZ → M₁ → γ → M₂ → Hue → H-K → L → C → HL → NC → φ → Lab
│
└── GenSpace — generation-optimized pipeline (gradient, palette)
XYZ → M₁ → depcubic(α) → M₂ → PW_L_corr → L-gated enrichment → C^cp → NC → Lab
+ CIEDE2000 arc-length reparameterization for gradient()
MetricSpace (72 parameters)
Jointly optimized with CMA-ES against COMBVD (with Bradford CAT pre-processing). 13-stage enriched pipeline with hue correction, Helmholtz-Kohlrausch, chroma scaling, neutral correction, and rigid rotation. STRESS 22.48 with Bradford CAT (22.73 without) — the lowest published figure on COMBVD; cross-validated estimate ~24.3.
GenSpace (v0.11.1 — Depressed Cubic + Chroma Power + L-Gated Enrichment)
Pipeline: XYZ → M₁ → depcubic(α=0.021) → M₂ → PW L_corr → L-gated hue enrichment → chroma_power(0.978) → NC → Lab
Transfer function: y³ + αy = x (depressed cubic, α=0.021)
Solved analytically via y = 2s·sinh(arcsinh(x/2s³)/3) where s = √(α/3), refined with a single Halley iteration for full precision. The inverse is trivial: x = y³ + αy. This depressed cubic has a finite derivative at zero (unlike standard x^(1/3)), which eliminates gamut boundary singularities.
Chroma power: C' = C^0.978 — mild chroma compression applied post-M2 that improves gradient step uniformity. Analytically invertible (C = C'^(1/0.978)).
L-gated hue enrichment: A targeted hue rotation h' = h + A·gate(L)·gauss(h−center) applied post-M2, where the gate function sin²(π·(L−L_lo)/(L_hi−L_lo)) activates only in mid-to-high lightness and the Gaussian targets the blue hue region. This fixes the blue→white purple shift with zero impact on achromatic colors or other hue regions. Invertible via Halley iteration (cubic convergence, 8 iterations).
Key properties:
- 360/360/360 valid cusps in sRGB, Display P3, and Rec.2020 (OKLab: 299/308/360)
- Zero invalid cusps across all gamuts; zero monotonicity violations in sRGB/P3 (1 in Rec.2020)
- Blue→White gradient: sky blue midpoint (G/R = 1.51), no lavender shift
- Achromatic: C* ≈ 10⁻¹⁵ (structural guarantee — uniform transfer × orthogonal M₂)
- Munsell Value uniformity: 0.16% (OKLab: 2.80% — 18x better)
- Adaptive gamut clipping with cusp-finding (Ottosson-style L0 calculation)
- Piecewise-linear L correction with 19 breakpoints (analytically invertible)
ColorBench evaluation (90 metrics, 3,038 gradient pairs, 3 gamuts):
| Category | GenSpace wins | OKLab wins | Tie |
|---|---|---|---|
| Gamut geometry | 24 | 0 | 3 |
| Application | 8 | 1 | 3 |
| Independent (Hung-Berns, Ebner-Fairchild, Pointer) | 6 | 1 | 0 |
| Gradient quality | 5 | 3 | 3 |
| Perceptual accuracy | 5 | 0 | 0 |
| Structural | 4 | 2 | 2 |
| Achromatic | 2 | 0 | 0 |
| Advanced | 2 | 0 | 4 |
| Hue | 2 | 0 | 0 |
| Special | 2 | 1 | 0 |
| Accessibility | 1 | 1 | 0 |
| Banding | 1 | 0 | 1 |
| Numerical stability | 0 | 0 | 3 |
| Total | 62 | 9 | 19 |
Known trade-offs: Slightly reduced round-trip precision in sRGB (~5.6×10⁻⁸ vs OKLab's ~1.6×10⁻¹⁵, due to enrichment Halley iteration — invisible in 8-bit pipelines), minor primary hue discontinuity at exact primary vertices, and reduced near-achromatic gradient uniformity in very low chroma regions.
Blue-region gamut fold: All power-law based M1→f→M2 spaces exhibit tiny non-contiguous gamut regions near h≈260° in sRGB due to cubic polynomial roots in the inverse. OKLab has 46 such holes; GenSpace has 5, each ~0.001 chroma wide (sub-pixel, invisible). See color.js#81.
Benchmarks
Perceptual Distance (MetricSpace)
STRESS on COMBVD (3,813 pairs), all methods without CAT for a like-for-like protocol. Lower is better. (With Bradford CAT pre-processing Helmlab v21 reaches the headline 22.48.)
| Method | COMBVD STRESS | vs CIEDE2000 |
|---|---|---|
| Helmlab v21 | 22.73 | -22.1% |
| CIEDE2000 | 29.18 | — |
| CIE94 | 33.59 | +15.1% |
| CAM16-UCS (Euclid.) | 33.90 | +16.2% |
| ΔE CMC | 34.04 | +16.6% |
| IPT (Euclid.) | 41.21 | +41.3% |
| CIE Lab ΔE76 | 42.80 | +46.7% |
| Oklab (Euclid.) | 47.46 | +62.7% |
How was STRESS measured?
STRESS (Standardized Residual Sum of Squares) is the CIE-standard metric for evaluating color difference formulas. COMBVD is a combined visual-difference dataset of 3,813 color pairs from 6 independent psychophysical experiments (Luo & Rigg 1986, RIT-DuPont, Witt, Leeds, BFD, He et al. 2022), containing 64,000+ individual human judgments.
For each pair i, let ΔVᵢ = human visual difference, ΔEᵢ = predicted distance. STRESS finds the optimal scale F minimizing residuals:
STRESS = 100 × √( Σ(ΔEᵢ − F·ΔVᵢ)² / Σ(ΔEᵢ)² )
Scale: 0 = perfect, 100 = no correlation. Full methodology: arXiv:2602.23010.
Gradient Quality (GenSpace)
Helmlab GenSpace vs OKLab — head-to-head on ColorBench (90 metrics, 3,038 gradient pairs across sRGB, Display P3, and Rec.2020 gamuts):
| Category | GenSpace wins | OKLab wins | Tie |
|---|---|---|---|
| Gamut geometry | 24 | 0 | 3 |
| Application | 8 | 1 | 3 |
| Independent (Hung-Berns, Ebner-Fairchild, Pointer) | 6 | 1 | 0 |
| Gradient quality | 5 | 3 | 3 |
| Perceptual accuracy | 5 | 0 | 0 |
| Structural | 4 | 2 | 2 |
| Achromatic | 2 | 0 | 0 |
| Advanced | 2 | 0 | 4 |
| Hue | 2 | 0 | 0 |
| Special | 2 | 1 | 0 |
| Accessibility | 1 | 1 | 0 |
| Banding | 1 | 0 | 1 |
| Numerical stability | 0 | 0 | 3 |
| Total | 62 | 9 | 19 |
Gradient Uniformity
CV (coefficient of variation of CIEDE2000 step sizes). Lower is better.
| Method | Red→Blue | Orange→Cyan | Black→White | Technique |
|---|---|---|---|---|
Helmlab gradient() |
≈ 0% | ≈ 0% | ≈ 0% | arc-length reparam. |
| Helmlab GenSpace | 30.3% | 26.5% | 40.7% | linear interpolation |
| Oklab | 31.5% | 41.4% | 41.2% | linear interpolation |
| CIE Lab | 44.8% | 52.3% | 61.5% | linear interpolation |
Note:
gradient()achieves ≈ 0% via CIEDE2000 arc-length reparameterization. This redistributes steps to equal perceptual spacing — an algorithm that could be applied to any space. Helmlab ships it built-in.
Project Structure
src/helmlab/
├── helmlab.py # Main API (Helmlab class)
├── spaces/
│ ├── metric.py # MetricSpace — 72-param enriched pipeline
│ ├── gen.py # GenSpace — depcubic + enrichment pipeline
│ ├── base.py # Abstract base class
│ └── ... # Baseline spaces (CAM16, IPT, Oklch, etc.)
├── metrics/
│ ├── delta_e.py # Color difference formulas
│ ├── stress.py # STRESS computation
│ └── benchmarks.py # Cross-method benchmarking
├── utils/
│ ├── srgb_convert.py # sRGB/Display P3 conversions
│ ├── gamut.py # Gamut mapping (binary search + adaptive clipping)
│ └── ... # Converters, I/O, visualization
├── data/
│ ├── metric_params.json # MetricSpace params (v21, STRESS 22.48 w/ Bradford CAT)
│ ├── gen_params.json # GenSpace params (v0.11.1, depcubic + enrichment)
│ └── ... # Dataset loaders (COMBVD, Munsell, etc.)
├── export.py # Token export (CSS, Android, iOS, Tailwind)
└── feedback/ # Human feedback collection tools
packages/helmlab-js/ # npm package (TypeScript)
packages/postcss-helmlab/ # PostCSS plugin
tests/ # 588 tests (336 Python + 252 JavaScript)
# ColorBench (the 90-metric evaluation suite) lives in its own repo:
# https://github.com/Grkmyldz148/colorbench
Tests
python -m pytest tests/ -q # 336 Python tests (334 pass + 2 skip)
cd packages/helmlab-js && npx vitest run # 252 JS tests
AI skill: color-space routing
There is no perfect color space — but there is a right one per task. skills/color-space-routing packages our benchmark evidence as a drop-in skill for Claude Code / Cursor / any AI assistant: task → space routing table, measured numbers (including where each space loses), copy-paste recipes, and a pitfall checklist.
Research
The optimization experiments, checkpoints, and analysis scripts that led to the current GenSpace v0.11.1 are available in a separate repository:
helmlab-experimental — 480+ experiments across 4 transfer functions, 3 M₁ variants, and systematic grid searches. Includes all checkpoints, optimization scripts, and the full experiment report.
Citation
@article{yildiz2026helmlab,
title={Helmlab: A Two-Space Family of Analytical, Data-Driven Color Spaces for UI Design Systems},
author={Y{\i}ld{\i}z, G{\"o}rkem},
journal={arXiv preprint arXiv:2602.23010},
year={2026},
url={https://arxiv.org/abs/2602.23010}
}
Author
License
MIT
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 helmlab-1.0.0.tar.gz.
File metadata
- Download URL: helmlab-1.0.0.tar.gz
- Upload date:
- Size: 116.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4626bcf022210d82cb7415d3aaaf8dd99d837eced54300a3d2decfbf7fabe219
|
|
| MD5 |
ab0b76df146eebbbc9eb45c9634d48b4
|
|
| BLAKE2b-256 |
3fa233c48a857dbe1f6e7e4038c66115b5df056059384ee652f181e3f8e901d5
|
Provenance
The following attestation bundles were made for helmlab-1.0.0.tar.gz:
Publisher:
deploy.yml on Grkmyldz148/helmlab
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
helmlab-1.0.0.tar.gz -
Subject digest:
4626bcf022210d82cb7415d3aaaf8dd99d837eced54300a3d2decfbf7fabe219 - Sigstore transparency entry: 2111513641
- Sigstore integration time:
-
Permalink:
Grkmyldz148/helmlab@96a0f16d623c72f1accc2fce02fd90c295cef94c -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Grkmyldz148
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
deploy.yml@96a0f16d623c72f1accc2fce02fd90c295cef94c -
Trigger Event:
push
-
Statement type:
File details
Details for the file helmlab-1.0.0-py3-none-any.whl.
File metadata
- Download URL: helmlab-1.0.0-py3-none-any.whl
- Upload date:
- Size: 93.8 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 |
2c1aa1cd6905b19235b2de16de9cf7a7c86b4be5ec67f31607ed64f795530b57
|
|
| MD5 |
3f4d456bdb7a5973fcee1df2aaff6b58
|
|
| BLAKE2b-256 |
b87fe496b61068f0dac778db948fa191e12ac12d60dd59ddf6fc666f51cacec2
|
Provenance
The following attestation bundles were made for helmlab-1.0.0-py3-none-any.whl:
Publisher:
deploy.yml on Grkmyldz148/helmlab
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
helmlab-1.0.0-py3-none-any.whl -
Subject digest:
2c1aa1cd6905b19235b2de16de9cf7a7c86b4be5ec67f31607ed64f795530b57 - Sigstore transparency entry: 2111513888
- Sigstore integration time:
-
Permalink:
Grkmyldz148/helmlab@96a0f16d623c72f1accc2fce02fd90c295cef94c -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Grkmyldz148
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
deploy.yml@96a0f16d623c72f1accc2fce02fd90c295cef94c -
Trigger Event:
push
-
Statement type: