05 / 14 · audits
Colormap Topology Audit
Certifies which features a colormap invented or destroyed, per observer including CVD.
The problem
Rainbow colormaps like jet put bright bands in the middle of the value scale, so a smooth data field displays with ridges the data does not contain. The visualization literature has documented these false features qualitatively for decades, but a reviewer looking at a figure still cannot say which displayed features are real and which the colormap invented. The answer also changes for color-vision-deficient viewers, who see a different lightness profile through the same colormap.
The method
The tool computes the colormap's perceived Oklab lightness profile L(v) for each observer, applying Machado 2009 CVD simulation first for protan, deutan, and tritan observers at full severity. If L is strictly monotone, the displayed field is a monotone reparametrization of the data, so critical-point structure is preserved exactly; every interior extremum of L turns a data level set into a ridge the viewer sees but the data lacks. It then runs 0-dimensional persistence on the superlevel-set filtration of both the data field and the displayed lightness field, using union-find with the elder rule on a 4-connected grid, and compares feature counts above a persistence threshold. The colormap literature documents rainbow maps inducing false features qualitatively, and TDA uses persistence to separate real features from noise in data; this joins the two into a per-feature certificate.
What it promises
- A faithful verdict requires both a monotone perceived lightness profile and matching significant feature counts between data and display, checked separately for each observer.
- Audits four observers by default: normal vision plus full-severity protan, deutan, and tritan (Machado 2009 simulation).
- Reports every lightness turnaround with its position and prominence, plus spurious and lost feature counts; fails loudly with a DIMENSION error when the field size does not match width times height.
- On the built-in maps the audit finds jet and rainbow distorted while grayscale and viridis are faithful; the companion audit_colormap_corpus tool ranks a whole corpus by worst offender.
Calling it
{
"tool": "audit_colormap_topology",
"arguments": {
"colormap": "jet",
"field": {
"values": "...1024 scalar values...",
"width": 32,
"height": 32
},
"persistence_threshold": 0.02
}
} Clone the repo, run npm install and npm run build in mcp/, then: claude mcp add maryslab -- node mcp/dist/server.js
A worked example
How many features does the jet colormap invent that the data does not contain?
what was asked
{
"colormap": "jet",
"samples": 64
} what the engine answered
{
"colormap": "jet",
"principle": "A colormap is topologically faithful for an observer exactly when its perceived lightness profile is monotone in the data value: then the displayed field is a monotone reparametrization of the data and critical-point structure is preserved. Each interior extremum of the lightness profile turns a data level set into a ridge the viewer sees but the data does not contain.",
"field_used": "built-in two-bump test field",
"observers": [
{
"observer": "normal",
"verdict": "distorted",
"lightness_monotone": false,
"monotonicity_violations": [
{
"valuePosition": 0.3779527559055118,
"kind": "local_max",
"prominence": 0.002339594036284476
},
{
"valuePosition": 0.6220472440944882,
"kind": "local_max",
"prominence": 0.006224582907578213
}
],
"data_features": 2,
"displayed_features": 16,
"spurious_features": 14,
"lost_features": 0
},
{
"observer": "protan",
"verdict": "distorted",
"lightness_monotone": false,
"monotonicity_violations": [
{
"valuePosition": 0.3779527559055118,
"kind": "local_max",
"prominence": 0.0026759219725219774
},
{
"valuePosition": 0.6220472440944882,
"kind": "local_max",
"prominence": 0.0018796641401649383
}
],
"data_features": 2,
"displayed_features": 20,
"spurious_features": 18,
"lost_features": 0
},
{
"observer": "deutan",
"verdict": "distorted",
"lightness_monotone": false,
"monotonicity_violations": [
{
"valuePosition": 0.3779527559055118,
"kind": "local_max",
"prominence": 0.001529659130057981
},
{
"valuePosition": 0.4330708661417323,
"kind": "local_min",
"prominence": 0.00012976312633128373
},
{
"valuePosition": 0.6220472440944882,
"kind": "local_max",
"prominence": 0.004495364800316426
}
],
"data_features": 2,
"displayed_features": 16,
"spurious_features": 14,
"lost_features": 0
},
{
"observer": "tritan",
"verdict": "distorted",
"lightness_monotone": false,
"monotonicity_violations": [
{
"valuePosition": 0.3779527559055118,
"kind": "local_max",
"prominence": 0.0007883612769052917
},
{
"valuePosition": 0.6141732283464567,
"kind": "local_max",
"prominence": 0.0009920897756483527
}
],
"data_features": 2,
"displayed_features": 16,
"spurious_features": 14,
"lost_features": 0
}
],
"persistence_threshold": 0.02,
"meta": {
"engine_version": "0.4.1",
"engine_backend": "reference-ts",
"dataset_snapshot": "usgs-splib07a-1",
"compute_device": "cpu"
}
} how to read it
Each feature carries a persistence: how far the lightness profile had to descend before it merged away. Low-persistence features are noise; high-persistence ones are boundaries a reader will see.
Produced by running audit_colormap_topology at build time (19 ms). Long arrays are truncated for reading; nothing else is edited.
Known limits
- Persistence is 0-dimensional only: it tracks maxima and the merging of superlevel components, not loops or higher-order structure.
- Built-in colormaps use coarse control points, adequate for lightness-profile analysis but not exact reproductions of the reference implementations.
- It audits colormaps, not decoded figure images; extracting a colormap from a published figure is out of scope for this server.