This page documents the lab's instruments: what each of the 39 tools computes, what it promises, and where those promises end. It is written for anyone pointing an agent at the server, whether to certify a palette, design a spectrum, or check our arithmetic against their own.
39 tools · 7 groups · one server
Quickstart
The install switcher above covers every supported client; the steps below assume you have completed one of them.
claude mcp add maryslab -- node mcp/dist/server.js
The engines
Two engines answer every call, and they are not allowed to disagree. A TypeScript reference engine defines the math; a C++20 core behind a stable C ABI reproduces it. Parity is enforced from both directions: 401 golden vectors, generated from the reference engine and checked by the native test suite, pin the numbers, while a shared suite of metamorphic relations (round trips, metric axioms, white-point preservation, spectral linearity) verifies that both engines obey the same algebra, not merely the same examples. Every response is stamped with its engine version, ABI version, and dataset snapshot, so a result can always be traced to the exact code and data that produced it.
401 golden vectors, three engines
Generated from the TypeScript reference engine, checked in CI against the native
C++ build. This runs them through the WebAssembly build of that same core, in
your browser, at the tolerance the native test uses.
Core color math: space conversions, perceptual difference, appearance under different lights, and the physical limits of surface color. These are the workhorse tools the rest of the surface builds on.
Converts colors or spectra between representations (sRGB, XYZ, Oklab, raw SPDs, or dataset records) in batches. sRGB outputs carry explicit out-of-gamut flags instead of being clipped quietly.
inputs array of color inputs, 1..500
Colors or spectra: {space: srgb|xyz|oklab, value}, an SPD, or a dataset record id
to enum: xyz | oklab | srgb
Target color space
illuminant enum D65|D50|A|E, default D65
Illuminant used when integrating spectra
observer enum, default cie1931_2deg
Standard observer for spectral integration
returns Converted values in the target space, with out-of-gamut flags when the target is sRGB.
"Convert these five reflectance records to Oklab under illuminant A"
Computes perceptual color difference between a set of colors, either as a full pairwise matrix or along consecutive pairs, optionally as seen by a color-deficient observer. It also points out the closest pair.
colors array of color inputs, 2..200
Colors or spectra to compare
mode enum, default pairwise_matrix
pairwise_matrix or consecutive_pairs
metric enum, default delta_e_ok
delta_e_ok (Oklab) or ciede2000
under_cvd CVD profile, optional
Evaluate differences as perceived under protan/deutan/tritan at a given severity
returns The ΔE matrix or pair list plus the minimum-distance pair.
"Compute the pairwise ΔE matrix for my 8 chart colors as a full deutan would see them"
Estimates correlated color temperature and Duv for light sources by finding the nearest point on the Planckian locus in CIE 1960 UCS with parabolic refinement.
inputs array of color inputs, 1..200
Light source colors or SPDs
returns CCT in kelvin and Duv per input.
Flags chromaticities with |Duv| > 0.05, where a CCT number is not physically meaningful.
"What is the CCT and Duv of this measured LED spectrum?"
Predicts how a color seen under one illuminant will appear under another, using CAT16 von Kries chromatic adaptation with a controllable degree of adaptation.
inputs array of color inputs, 1..500
Colors to adapt
from_illuminant enum D65|D50|A|E
Illuminant the colors were viewed under
to_illuminant enum D65|D50|A|E
Illuminant to adapt to
degree number 0..1, default 1
Adaptation completeness (0 = none, 1 = full)
output_space enum xyz|oklab|srgb, default xyz
Space for the adapted result
returns Adapted colors in the requested output space.
"Predict how these brand colors will look when the viewer adapts from D65 daylight to incandescent A"
Finds the dominant wavelength and excitation purity for colors using the standard CIE definitions, i.e. the monochromatic hue that matches each color. Purples get a complementary wavelength instead.
colors array of color inputs, 1..200
Colors to analyze
white_illuminant enum D65|D50|A|E
White point the definitions are taken against
returns Dominant (or complementary) wavelength in nm and excitation purity per color.
"Find the dominant wavelength and purity of this filter's transmitted color against D65"
Tests whether colors are physically realizable as diffuse surface colors, i.e. whether they lie inside the MacAdam object-color solid for a given illuminant. Returns signed margins rather than a bare yes/no.
colors array of color inputs, 1..500
Colors to test
illuminant enum D65|D50|A|E, default D65
Illuminant defining the object-color solid
resolution int 16..256, default 96
Solid discretization resolution
returns Per-color inside/outside verdicts with signed distance margins to the solid boundary.
"Are any of these paint targets physically impossible surface colors under D50?"
Builds the full MacAdam object-color solid mesh (the boundary of all physically possible surface colors, via two-transition optimal colors) for an illuminant.
illuminant enum D65|D50|A|E, default D65
Illuminant the solid is computed for
resolution int 16..256, default 96
Mesh resolution
output_space enum xyz|oklab, default oklab
Space the mesh vertices are expressed in
returns A mesh of the object-color solid boundary in XYZ or Oklab.
"Build the object-color solid under illuminant A in Oklab so I can plot it against sRGB"
Maps out-of-gamut colors into sRGB perceptually: Oklch lightness and hue are preserved and only chroma is compressed toward the gamut boundary. It reports exactly what was changed.
colors array of color inputs, 1..500
Colors to bring into the sRGB gamut
returns In-gamut sRGB colors plus a report of which inputs were modified and by how much.
"Bring these wide-gamut Oklab colors into sRGB without shifting their hue or lightness"
Dataset & Spectral Analysis
Access to the MARY-DATA spectral catalog (380..750 nm, 10 nm grid, 38 samples) and tools for representing, reconstructing, and decomposing spectra.
Searches the MARY-DATA catalog by metadata: free text, domain, source, license, Oklab color proximity, or sRGB gamut membership. Returns paginated record summaries; use get_spectrum for full values.
query string up to 200 chars, optional
Free-text search over record metadata
domain enum, optional
material_reflectance, biological, cvd_calibration, illuminant, or emission
oklab_near object {L, a, b, max_delta_e}, optional
Find records whose color is within a ΔE radius of a target Oklab point
in_srgb_gamut boolean, optional
Restrict to records whose color fits (or does not fit) in sRGB
limit int 1..200, default 25
Page size
offset int, default 0
Pagination offset
returns Record summaries (ids, names, domains, licenses) matching the filters.
"Find material reflectance records whose color is within ΔE 0.05 of this teal in Oklab"
Projects spectra onto a Chebyshev polynomial basis, giving a compact coefficient representation together with the reconstruction error incurred. Only accepts actual spectra (SPD or record inputs), not tristimulus colors.
inputs array of spd/record inputs, 1..200
Spectra to project
basis enum, default chebyshev
Functional basis (currently Chebyshev only)
order int 2..36, default 16
Basis order (number of coefficients)
returns Coefficient vectors plus per-spectrum reconstruction error.
"Compress these 40 measured reflectances to order-12 Chebyshev coefficients and tell me the worst reconstruction error"
Reconstructs spectra from Chebyshev coefficients on any wavelength grid you choose, optionally applying the analytic derivative first. The inverse of expand_spectrum_basis.
coefficients array of coefficient vectors (3..65 each), 1..200
Chebyshev coefficients per spectrum
operation enum none|derivative, default none
Optionally evaluate the analytic derivative instead of the function
Simulates how colors appear to a color-deficient observer using the Machado 2009 physiological model, covering protan, deutan, and tritan at any severity from 0 to 1.
Remaps a palette so its colors stay mutually distinguishable under a color vision deficiency, using constrained daltonization in Oklab with optional locked anchor colors.
profile CVD profile object
Deficiency to compensate for
colors array of color inputs, 2..64
The palette to remap
min_delta_e_ok number 0.01..0.5, default 0.06
Required minimum pairwise ΔE_OK under the deficiency
preserve_lightness_tolerance number 0..0.2, default 0.02
How much each color's lightness may move
anchored_indices int array, default []
Palette positions that must not change (e.g. brand colors)
returns A remapped palette meeting the separation floor, or an error carrying the best achievable bound.
Remaps for discriminability only; it cannot restore information lost to dichromacy. Infeasible constraints fail loudly with the achievable bound.
"Remap this 12-color categorical palette so a deutan observer can tell every pair apart, but keep colors 0 and 3 fixed"
Generates an N-class palette with a guaranteed minimum ΔE_OK under normal vision and under every CVD profile you list simultaneously. Built for safety-critical telemetry displays.
n_classes int 2..32
Number of palette classes
min_delta_e_ok number 0.01..0.5, default 0.15
Guaranteed separation floor (0.15 is safety-critical grade; JND is about 0.02)
cvd_profiles array of up to 3 CVD profiles, default full protan + deutan
Observer conditions the guarantee must also hold under
anchors array of {index, color}, up to 8
Fixed colors at specific palette positions
lightness_range [min, max], default [0.35, 0.85]
Allowed Oklab lightness band
seed int, default 42
Deterministic search seed
returns A palette certified to meet the floor for all listed observers, or a loud infeasibility error stating what was achievable.
The default floor is deliberately strict, so feasible class counts are small by design; infeasibility reports the achieved bound instead of silently relaxing.
"Generate a 7-class alarm palette that keeps ΔE_OK at least 0.15 for normal, protan, and deutan vision"
Certificates & Bounds
Tools that return guarantees rather than samples: provable ceilings on palette quality, discriminability certified over entire illuminant families, safe blending regions, and feasibility oracles for spectral constraints.
Answers how good ANY N-color palette can possibly be: a certified upper bound on the achievable minimum pairwise ΔE_OK (worst case across CVD profiles) via a covering argument, plus a constructed witness palette as the lower bound.
n_classes int 2..32
Palette size to bound
cvd_profiles array of up to 3 CVD profiles, default full protan + deutan
Observer conditions in the worst case
threshold_delta_e_ok number 0.01..0.5, optional
A specific floor to test feasibility against
gamut_steps int 6..12, default 9
Covering grid density
seed int, default 42
Witness construction seed
returns An upper bound and a witness lower bound that together bracket the true optimum for your exact case.
Brackets the optimum rather than computing it exactly; the point is that published feasibility figures are one-observer napkin estimates while this is computed for your configuration.
"What is the best minimum ΔE any 16-class palette could achieve if it must also survive full protan and deutan vision?"
Certifies that a set of surfaces stays mutually discriminable under EVERY illuminant in a convex family you specify, not just at sampled lights. It exploits XYZ being linear in the illuminant, so the reachable set is exactly the convex hull of basis responses.
surfaces array of color inputs, 2..24
Surface spectra or records to certify
basis array of 1..4 illuminant names, default [D65, A]
Corners of the illuminant family; accepts D65/D50/A/E, BB<kelvin>, LED<nm>
threshold_delta_e_ok number 0.001..0.5, default 0.02
Discriminability floor to certify
divisions int 2..12, default 6
Grid density for the Lipschitz-bounded Oklab step
returns A pass/fail certificate over the whole family, with the binding surface pair; reports grid_sufficient false and a recommended division count when the nonlinear correction dominates.
The XYZ stage is exact (no relaxation); only the Oklab step uses a Lipschitz bound, and the tool says so when that bound is too loose to decide.
"Certify these 6 fabric swatches stay at least ΔE 0.02 apart under every mix of D65 and 2700K blackbody light"
Computes the convex core of the sRGB gamut in Oklab: a verified shrink factor such that any linear blend (gradients, midpoints, crossfades) between colors inside it stays in gamut.
boundary_steps int 6..16, default 10
Boundary sampling density for the certificate
returns The certified shrink factor defining the safe interpolation region, with the sampling density stated.
A sampled certificate with stated density, not a closed-form proof.
"Give me the safe Oklab region where I can crossfade any two UI colors without ever leaving sRGB"
A feasibility oracle for multi-illuminant color demands: does any physical reflectance show color A under illuminant 1, color B under illuminant 2, and so on, within tolerances? Returns a verified witness spectrum or a conflict-localized infeasibility report.
returns A verified witness reflectance, or an infeasibility report naming the smallest conflicting constraint subsets.
Generating multi-condition metamers when they exist is prior art; the new part is the decision procedure with conflict localization when they do not.
"Is there a physical surface that looks like this warm gray under D65 but shifts to this green under illuminant A?"
Inverse Design & Adversarial
Designing physical spectra to order (palettes, test stimuli, benchmark confusions), attacking colorimetric systems with realizable lights, and mapping the geometry of metamerism.
Designs N reflectance SPECTRA (not colors) whose mutual separation holds jointly across every illuminant-times-observer condition you list. A palette designed in a color space is only safe under its design illuminant; designing the physical spectra removes that failure mode.
n_classes int 2..16
Number of spectra to design
conditions array of 1..8 {illuminant, cvd}, default D65 + A + D65-deutan
Viewing conditions the separation must hold under
target_min_delta_e_ok number 0.01..0.5, optional
Separation floor to hit; omit to maximize
order int 2..16, default 6
Chebyshev order of the designed spectra
iterations int 100..20000, default 2000
Optimization budget
seed int, default 42
Deterministic seed
returns N physical reflectance spectra with the achieved worst-condition separation; unreachable targets fail with the achieved bound.
"Design 5 physical pigment spectra that stay distinguishable under daylight, incandescent, and deutan vision at once"
Designs a spectrally-realizable color-vision test stimulus: a pair of reflectances a normal trichromat separates easily but the target deficiency cannot. Optimizes the stimulus directly from spectra instead of the trial-and-error used for pseudoisochromatic plates.
target CVD profile
The deficiency the stimulus should be invisible to
illuminant enum D65|D50|A|E, default D65
Viewing illuminant
max_target_delta_e_ok number 0.001..0.1, default 0.02
Maximum separation allowed for the target observer
iterations int 100..20000, default 3000
Optimization budget
seed int, default 42
Deterministic seed
returns A reflectance pair with its normal-vision and deficient-vision separations.
A research stimulus designer, not a validated clinical screening test.
"Design a reflectance pair that a normal observer separates clearly but a full tritan cannot tell apart under D65"
Finds a physically realizable illuminant that collapses a palette's color separation for a human observer, an adversarial light. Prior adversarial color work attacks CNNs in pixel space; this attacks a colorimetric system with a real light source.
palette array of color inputs, 2..16 (spectra required)
The palette to attack
baseline_illuminant enum D65|D50|A|E, default D65
Reference light the palette was evaluated under
observer CVD profile or null, default null
Optionally attack a color-deficient observer instead
iterations int 100..20000, default 2000
Optimization budget
seed int, default 42
Deterministic seed
returns The attack SPD, the separation degradation factor, and which color pair collapsed.
"Find a realizable lamp spectrum that makes my 6 wayfinding colors indistinguishable"
Samples the metamer set: physically valid reflectances that all match a target color exactly under a given illuminant, using the convex-set framework of Logvinenko and Godau-Funt.
target one color input
The color every metamer must match
illuminant enum D65|D50|A|E, default D65
Matching illuminant
count int 2..64, default 16
Number of boundary samples
seed int, default 42
Deterministic seed
returns The set center, boundary sample spectra, and a per-wavelength envelope; unrealizable targets error with NO_SOLUTION.
"Sample 20 physically valid reflectances that all match this mid-gray exactly under D65"
Maps how the SHAPE of the metamer set changes as you move through color space: which box constraints are active, how much free extent remains, and where transitions occur. Shows where metamerism is geometrically rich versus where a color nearly pins down its spectrum.
illuminant enum D65|D50|A|E, default D65
Matching illuminant
path neutral_ramp or {points: 2..64 colors}, default neutral_ramp
The path through color space to stratify along
steps int 2..32, default 8
Points sampled along the path
samples int 4..32, default 10
Metamer samples per point
seed int, default 42
Deterministic seed
returns Per-step active constraints and free extent, with detected shape transitions along the path.
A sampled stratification, not exact face-lattice enumeration.
"Map how metamer-set richness changes along the neutral ramp from black to white under illuminant A"
Generates a benchmark of physically realizable metameric confusions: pairs of valid surface reflectances that are identical under one illuminant and diverge under another. Useful for stress-testing color constancy and imaging pipelines.
anchors array of 1..16 color inputs, optional
Specific anchor colors; omitted means auto-chosen
anchor_count int 1..16, default 5
Number of anchors when auto-choosing
match_illuminant enum D65|D50|A|E, default D65
Illuminant under which pairs match
split_illuminant enum D65|D50|A|E, default A
Illuminant under which pairs diverge
items_per_anchor int 1..4, default 2
Confusion items generated per anchor
seed int, default 42
Deterministic seed
returns Benchmark items, each a reflectance pair with its match and split ΔE; unrealizable anchors fail with NO_SOLUTION.
Every item is a realizable surface, no pixel perturbation; it perturbs the surface where prior benchmarks perturb the lamp.
"Build a benchmark of 10 surface pairs that match under D65 but split apart under incandescent A"
Audits & Forensics
Interrogating artifacts you did not make: colormaps that lie about data structure, LUTs that corrupt tones, spectra that were quietly post-processed, images with physically impossible pixels, and radiation spectra with unknown peaks.
Certifies whether a colormap invents or destroys features in visualized data. A colormap is topologically faithful exactly when its perceived lightness profile is monotone; each interior extremum creates a ridge viewers see that the data lacks.
colormap named preset (viridis, jet, ...) or {stops}, default viridis
The colormap to audit; custom maps as 2..64 RGB stops
field object {values, width, height}, optional
A concrete 2D data field to evaluate the colormap against
persistence_threshold number 0.001..0.5, default 0.02
Minimum feature persistence counted as real
returns Monotonicity violations plus spurious and lost feature counts from persistent homology, per observer including CVD.
"Audit whether jet invents features in this 128x128 temperature field for both normal and deutan viewers"
Runs the topological faithfulness audit across a whole corpus of colormaps at once, for normal and CVD observers, and ranks them by worst offender. Existing corpus studies check CVD accessibility; this checks whether the visualization tells the truth about structure.
colormaps array of 1..64 named presets or {name, stops} objects
The colormaps to audit
persistence_threshold number 0.001..0.5, default 0.02
Minimum feature persistence counted as real
returns A ranked audit of the corpus with per-map violation details.
Audits colormap definitions, not decoded figure images; image extraction is out of scope.
"Rank these 15 in-house colormaps by how badly they distort data topology for a deutan viewer"
Measurement forensics for reflectance curves: is this a real instrument measurement or something interpolated, smoothed, or synthesized? Looks for a missing noise floor, interpolation signatures, value quantization, and clamped extrapolation tails.
inputs array of color inputs, 1..100
Spectra to examine
returns Per-spectrum provenance findings with the specific signatures detected.
A triage signal only; thresholds are calibrated on synthetic negatives, not validated against verified instrument data.
"Check whether these vendor-supplied reflectance curves look like real measurements or smoothed synthetics"
Behavioral audit of a color transform supplied as a 3D LUT lattice: gray-axis tone reversals, neutral contamination, hue flips beyond 25 degrees, outputs escaping [0,1], and local tears. LUT viewers show you the transform; this tests what a sane transform must never do.
lattice flat number array, n^3 x 3 RGB values in [0,1]
The LUT lattice, red-fastest (.cube order)
lattice_size int 2..33
Lattice edge length n
returns A defect report listing each violation class with the offending lattice locations.
"Audit this 33-point film-emulation LUT for tone reversals and hue flips before we ship it"
Issues a per-pixel physical impossibility certificate: which supplied sRGB pixels cannot be diffuse surface colors under ANY illuminant in a declared family at a given exposure, and by how much each overshoots.
pixels array of 1..4096 sRGB triples in [0,1]
Small pixel grids supplied directly, not decoded image files
basis array of 1..4 illuminant names, default [D65, A]
The illuminant family; accepts D65/D50/A/E, BB<kelvin>, LED<nm>
exposure number 0.1..10, default 1
Assumed exposure scaling
returns Per-pixel possible/impossible verdicts with overshoot magnitudes.
Fluorescent and emissive sources legitimately exceed the bound; the output says not-a-plain-surface, never fake.
"Which pixels in this sampled patch could not be a real matte surface under any daylight-to-incandescent light?"
A perceptual and forensic diff between two versions of a spectral dataset. Classifies every changed record as renoised, smoothed, rescaled, or replaced, quantifies the perceptual impact under D65, and emits a CI gate.
before array of 1..500 {id, 38 values}
The earlier dataset version
after array of 1..500 {id, 38 values}
The later dataset version
allow_smoothed boolean, default false
Whether smoothing counts as an acceptable change
max_perceptual_delta number 0.001..0.5, default 0.05
Perceptual change budget for the gate
returns Per-record change classifications, perceptual impact, and a pass/fail CI gate that fails on silent post-processing of claimed measurements.
"Diff last month's dataset snapshot against today's and fail if any measured record was quietly smoothed"
The forensic corollary of the constraint oracle: could two (illuminant, color) observations have come from the SAME physical surface? Answers with a witness reflectance or a physical impossibility finding.
Detects gamma-ray photopeaks in a pulse-height spectrum using SNIP continuum removal and a significance test; with an energy calibration supplied it returns ranked isotope candidates.
returns Detected peaks with significances, and ranked isotope candidates when calibrated.
Decision support only, never an authoritative isotope identification.
"Find the significant peaks in this 4096-channel NaI spectrum and suggest which isotopes they could be"
Correctness Infrastructure
Testing machinery for color code itself: metamorphic and differential testing of implementations, certified conformance vectors, and a static type checker for color pipelines.
Metamorphic and differential testing for color implementations. Checks algebraic identities any correct implementation must satisfy (round-trip identity, gamut-map idempotence, adaptation white preservation, ΔE metric axioms, spectral linearity, and more), shrinking any failure to a minimal witness.
cases int 1..2000, default 200
Random test cases per relation
relations array of relation names, optional
Subset of the 9 built-in algebraic relations; omitted runs all
external_results array of numeric triples, optional
Another library's outputs, enabling differential comparison
Emits certified numeric test vectors for colorimetric transforms with documented formulae and tolerances, or scores a candidate implementation's outputs against them. Fills the gap left by ICC conformance, which validates profile structure rather than computation.
transforms array of transform names, default [srgb_to_xyz, xyz_to_oklab, ciede2000]
Which transforms to emit vectors for (8 available, incl. spd_to_xyz_d65 and cvd_deutan_full)
candidate_results array of numeric arrays, optional
Supply an implementation's outputs to score instead of just emitting vectors
returns Certified input/output vectors with tolerances, or a scored comparison of the candidate against them.
A reference suite where this engine is the oracle; its authority is published formulae and metamorphic self-consistency, not independent ground truth, so a disagreement means one of the two implementations has a defect to explain.
"Emit conformance vectors for sRGB-to-XYZ and CIEDE2000 so I can score our Rust port against them"
A static checker for color pipeline specifications. Tracks encoding, color space, and illuminant symbolically through a list of operations and flags the classic silent-corruption bugs: a matrix applied to gamma-encoded values, a doubled transfer function, resampling in a nonlinear encoding, or missing chromatic adaptation.
returns A per-step trace of encoding, space, and illuminant with flagged type errors and their locations.
"Typecheck this 12-step imaging pipeline and tell me if we ever blend or resize in a gamma-encoded space"
Honesty rules
The lab's standing discipline is that caveats travel with results, in the results themselves. Ask for a palette under constraints that cannot be met and the tool fails loudly, reporting the bound it actually achieved rather than quietly relaxing what you asked for. Certificates audit their own resolution: when the grid is too coarse to certify anything, the response says so instead of printing a hollow zero. The forensics tool states in its own output that it was calibrated on synthetic negatives, and its scores are decision support, never authoritative identification. A number without its limits is not a result here.