- JavaScript 62.3%
- Go 26.9%
- HTML 10.7%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
hot-linkable; the composition as nested functions New section 1.1 compares this sheet's dye readings — peak wavelength and density per ink — against the printer library's stored dyes for the same printer (preferring the same paper and results carrying model-2 spectra), with ΔD per ink and the source result named; agreement means the printer behaves as recorded, drift means re-store. On the ET-2850 the match is exact, as it should be — the library entry came from this sheet. All equations take the M1. page prefix, (M1.1)-(M1.13), each an anchor link matching the transforms page's T.n convention, with every cross-reference renumbered. And M1.3 is now a proper nested equation: (r,g,b)_s = F₁(x) = encode(sense(mix(gcr(ink(x))))), each function defined below by its own equations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
| cmd/godatacolor | ||
| color | ||
| datacolor | ||
| dataset | ||
| examples | ||
| internal/usbfs | ||
| model | ||
| packaging | ||
| profile | ||
| spydercheckr | ||
| web | ||
| .gitignore | ||
| go.mod | ||
| go.sum | ||
| Makefile | ||
| README.md | ||
| sc24.json | ||
| Taskfile.yml | ||
godatacolor
A Go rework — for Ubuntu/Linux — of pydatacolor,
driving the Datacolor SpyderPRINT spectrocolorimeter (USB 085c:0007).
It talks to the device, runs the connect handshake, calibrates against the white tile, takes measurements, and turns the raw CIELAB readings into correct sRGB — fixing the clipping / out-of-gamut problems the original project ran into.
What's improved over pydatacolor
- No dependencies, no libusb, no cgo. The USB layer talks to the Linux
usbfsioctl interface directly (internal/usbfs), so the whole thing is pure Go and builds with just the Go toolchain. - Correct colour pipeline (
color): CIELAB → XYZ → Bradford chromatic adaptation → sRGB primaries → sRGB transfer function, with a selectable reference white (D50 for print, D65). Out-of-gamut colours are handled with hue-preserving gamut mapping (chroma is compressed toward the boundary with L* and hue fixed) instead of per-channel clipping, which shifts hue. - SpyderCheckr charts (
spydercheckr): render measured patches to a PNG, optionally drawing the naively-clipped colour beside the mapped one so you can see the correction. - Robust protocol layer (
datacolor): the device transport is behind an interface (testable without hardware), handshake/calibration mismatches are lenient by default (firmware responses vary) but can be made-strict, timeouts are typed and tolerated where appropriate. - Data export: every run saves to JSON and CSV.
- Two front-ends: a scriptable CLI and a lofigui-style web UI.
Install
Requires Go 1.24+.
make build # -> ./bin/godatacolor
sudo make install # -> /usr/local/bin/godatacolor (optional)
sudo make install-udev # udev rule for non-root device access
The udev rule (packaging/99-datacolor-spyderprint.rules) grants the active
local user access to the device. After installing it, unplug and replug the
SpyderPRINT. Note: like the original, using this driver path means the device is
no longer claimed by Datacolor's own software while the rule is active.
CLI
godatacolor info # connect, print serial + one reading
godatacolor calibrate # white-tile calibration
godatacolor measure -n 100 -name sky -white D50 -o run.json
godatacolor capture -patches 24 -o checker.png # interactive patch capture -> chart
godatacolor chart -in run.json -o chart.png # render a saved dataset
godatacolor serve -addr 127.0.0.1:1340 # web UI
Common flags: -white D50|D65, -n <readings to average>, -strict.
Device progress goes to stderr; data goes to stdout.
Interactive capture
godatacolor capture calibrates once, then prompts you to place each patch and
press Enter. It averages -n readings per patch, writes a SpyderCheckr-style
PNG and a dataset JSON. Press q to finish early.
Calibration overlay (profiling)
The device returns raw CIELAB with a systematic bias (a freshly calibrated white tile reads ~L*102.7, slightly blue). Two host-side corrections sit on top of the raw numbers:
- White balance (
-host-cal, or the web "Set white ref" button): a von Kries diagonal correction from the white tile alone — quick, neutralises the white point. - Fitted overlay (
profile): a 3×3-matrix + offset fitted by least squares from a known-reference target, correcting scale, offset and hue across the whole range.
Build an overlay from a target whose patches have known L*a*b* (a .json or
.csv — see examples/target.example.csv):
godatacolor profile build -target mychart.csv -o mychart.json -n 50
godatacolor profile show -in mychart.json # matrix + residual ΔE
godatacolor measure -profile mychart.json -name patch1
godatacolor capture -profile mychart.json -patches 24 -o chart.png
profile build reports the ΔE to reference before vs after correction, so you
can see the improvement. After measuring all patches it re-reads the first row
to measure session drift (-row, default 6) and fails if the drift or the fit
residual exceeds -tolerance (default 1.0 ΔE) — so a run where the device
drifted out of your limits is flagged rather than silently trusted.
Repeatability characterisation
godatacolor noise recalibrates in situ N times, takes a timed run of readings
each, and reports single-reading σ, the standard error of the mean, an
averaging-convergence table, the systematic within-run drift, and the
between-calibration spread — so you can choose how long to average:
godatacolor noise -repeats 3 -n 200
On the validated unit, per-reading noise is ~0.06 ΔE (gone by ~50 reads), while drift (~0.1) and calibration repeatability (~0.17 ΔE) dominate — i.e. averaging past ~50 readings is wasted; warm-up and recalibration are the real levers.
Web UI
godatacolor serve starts a menu-driven UI on http://127.0.0.1:1340, built
around printing a small palette of colours (a tartan swatch) accurately:
- Spectro (
/) — connect a Nix Spectro 2 (via the sibling gonixcolour module; clone it next to this repo, see thereplacein go.mod) and measure sample by sample into a ticker-tape feed showing Lab, OkLCH and the spectral curve. JSON/CSV export. - Calibrate (
/calibrate) — characterise a printer: print a chart of known sRGB patches (grey ramp, primaries/secondaries/tertiaries + the palette) with pinned CUPS options, measure it patch by patch, then fit an affine intended → measured map in OKLab space. Exports the session as CGATS.ti3so ArgyllCMS'scolprofcan build a real ICC from the same data. - Argyll (
/argyll) — real ICC profiles for arbitrary colours (an unknown tartan):targenchart → print uncorrected → measure with the Nix (journalled, resumable) →colprof→ .icc, thenxicclu(absolute colorimetric) seeds the palette's device values from the profile. Charts and profiles live in the-argylldir (defaultargyll/); profiles also work in any colour-managed app./docsexplains the .ti1/.ti3/.icc pipeline. - CMS (
/cms) — the palette: each target colour (entered as hex or OkLCH) gets a device RGB seeded from the inverted fit, then refined in a closed loop — print a check sheet, measure it, each colour is nudged by its OKLab error until the print measures within tolerance. Palette, fit and device values persist (-cms cms.json) as the recipe for the final swatch. - Measure (
/measure) — the dedicated chart-measuring page: whichever chart session is active (built-in or targen) is walked patch by patch here, journalled and resumable. - Verify (
/verify) — how good is the match, really? Prints a colour set (built-in greys + hue wheel + palette, or a targen set like the Argyll chart) through the active calibration — via the ICC when one is applied — then measures it back: target vs measured OkLCH and ΔEok side by side, an interactive a*b* vector plot (hover a point for details), and per-run stats. Completed runs join a persistent history — the honest accuracy estimate per printer + paper. - TI3 (
/ti3) — visualise any CGATS .ti3: sent-vs-measured swatch pairs, summary ΔE, and the a*b* error-vector plot that makes the printer's character visible (chroma compression, hue twists, grey-axis cast). - SpyderPRINT (
/spyder) — the original single-page SpyderPRINT UI: Connect / Calibrate / Measure gated on the device state machine, guided profiling, match-sheet printing.
All pages share the scrolling log and auto-refresh while an operation runs.
Guided profiling in the browser. Pass a reference target to serve and the
page adds a profiling panel that walks you through the chart patch by patch —
showing the next patch's reference swatch and L*a*b*, and, as each reading
lands, a live table comparing measured vs reference (swatches + ΔE). It then
re-reads the first row for drift, fits the overlay (raw vs corrected ΔE, with a
within/out-of-tolerance badge), applies it, and offers the profile for download:
godatacolor serve -target examples/spydercheckr24.csv
Direct printing (CUPS). The print form can bypass the browser's print
dialog entirely: the server renders the solid sheets / tuning grids as a PDF
of plain device-RGB rectangles (no ICC tag, no browser or OS colour
management) and submits it with lp, with the driver options — paper size and
type, input slot, colour space, resolution, quality, colour mapping — chosen
on the page and logged to the terminal panel, so a print is reproducible from
its log line. Only the options that matter for colour targets are surfaced
(e.g. Gutenprint's MediaType, StpQuality, StpInkSet,
StpColorCorrection); everything else keeps its PPD default. For profiling
runs on a Gutenprint queue, set Color Correction to Uncorrected so the
patch values map deterministically to ink. An as PDF checkbox on the
preview buttons shows exactly what would be sent.
Layout
| Package | Role |
|---|---|
internal/usbfs |
Dependency-free Linux USB transport (usbfs ioctls) |
datacolor |
SpyderPRINT protocol: handshake, calibrate, measure |
color |
CIELAB → sRGB with adaptation, gamut mapping, von Kries + affine corrections |
model |
Thread-safe connect→calibrate→measure state machine |
profile |
Fitted calibration overlay (3×3+offset) + drift QA |
dataset |
Sample persistence + JSON/CSV export |
spydercheckr |
Chart (PNG) rendering |
web |
lofigui-style HTTP UI |
cmd/godatacolor |
CLI entry point |
Protocol notes
The device speaks a command-framed bulk protocol: an OUT transfer whose first
byte is the command, then an IN transfer that echoes the command byte followed
by a length byte; the payload is length - 2 bytes. Measurements come back as a
13-byte frame — a mode byte plus three big-endian signed 16.16 fixed-point
values interpreted as raw L*a*b*. The reset handshake and the non-standard
two-write calibration frames are ported from pydatacolor's reverse engineering;
see comments in datacolor/datacolor.go. Calibration is expected to be applied
on the host (this is why the raw values need the color pipeline).
Testing
make test
Tests cover the protocol framing/parsing (against a scripted fake transport, no hardware needed), the state machine transitions, the colour conversions (white, black, sRGB primaries, D50 vs D65, gamut mapping) and chart rendering.
Status
The protocol, colour pipeline, charting, CLI and web UI are implemented and
tested. The reset/calibration sequences are ported faithfully from pydatacolor
but, like the original, were captured from one unit — run with -strict to see
exactly where your device's responses differ. Hardware-in-the-loop verification
is the remaining step.
Credit
Based on drummonds/pydatacolor and its USB reverse-engineering work.