Crosstalk From Datasheet
Documentscrosstalk-from-datasheetCrosstalk From Datasheet is an Agent Skill for documents published in the marcinz606/NegPy repository on GitHub: a SKILL.md instruction file an AI agent can load to do this task.
Use when the user supplies a film-stock spec sheet, datasheet, or spectral data (PDF / URL / numbers) and wants a NegPy spectral-crosstalk .toml matrix for the Process panel's Crosstalk control, or asks to calibrate / derive channel-unmixing for a specific film or scanner. Keywords: crosstalk, datasheet, spec sheet, film stock, dye density, spectral, separation, unmixing, toml.
How to use this skill
Bring this guide into your coding agent with a prompt tailored to the tool you use.
- Open your project in Codex.
- Copy and paste the prompt below.
- Review files and risks before approving any action.
Inspect the Agent Skill at https://github.com/marcinz606/NegPy/blob/HEAD/.claude/skills/crosstalk-from-datasheet/SKILL.md and all supporting files. Report provenance, permissions, scripts, dependencies, and security risks. Propose an exact installation into .agents/skills/crosstalk-from-datasheet, but wait for my approval before writing files or running scripts.
Copying this prompt does not install or run the skill. Review third-party files before use.
Crosstalk from datasheet
Overview
A color negative's three dye layers each leak density into channels they shouldn't
("unwanted absorption"), muddying color. NegPy's Crosstalk control (Process panel)
unmixes them with a 3×3 matrix applied to the raw negative densities. This skill turns a
film's published spectral dye-density curves into that matrix: read the unwanted
absorptions, invert them, write a .toml.
Core principle: the matrix is the inverse of the dye-leakage matrix. You only read
values off curves and judge data quality — derive_matrix.py does the arithmetic.
Read docs/CROSSTALK.md and crosstalk/README.md once for the file format and the
density-domain math NegPy applies (d=-log10(rgb_negative), d_out=M·d, pre-normalization);
NegPy row-normalizes M and blends with identity by the Crosstalk strength (0–1), so
only the off-diagonal/diagonal ratios matter — absolute row scale is irrelevant.
Workflow
-
Acquire the source.
- Local PDF →
Readit withpages(vision — the curves are pixels; a text fetch will not see them). - URL → PDF →
curlit to the scratchpad, thenReadthe pages as images. - HTML page →
WebFetchfor any numeric spectral tables; if it only embeds a plot, download the image andReadit. - Raw spectral numbers / CSV → use them directly (Tier 1).
- Local PDF →
-
Classify the data tier (the one decision that's easy to get wrong):
digraph tier { "Separated C/M/Y dye-density curves\n(or numeric spectral data)?" [shape=diamond]; "Only aggregate neutral dye-density\n(one combined curve)?" [shape=diamond]; "Tier 1: derive directly" [shape=box]; "Tier 2: estimate, label (approx)" [shape=box]; "Tier 3: do NOT fabricate" [shape=box]; "Separated C/M/Y dye-density curves\n(or numeric spectral data)?" -> "Tier 1: derive directly" [label="yes"]; "Separated C/M/Y dye-density curves\n(or numeric spectral data)?" -> "Only aggregate neutral dye-density\n(one combined curve)?" [label="no"]; "Only aggregate neutral dye-density\n(one combined curve)?" -> "Tier 2: estimate, label (approx)" [label="yes"]; "Only aggregate neutral dye-density\n(one combined curve)?" -> "Tier 3: do NOT fabricate" [label="no"]; }Most consumer datasheets (e.g. Kodak Gold 200) are Tier 2: the spectral-dye-density plot is one aggregate "midscale neutral" curve, not separated dyes. Spectral-sensitivity curves are not dye-density and must never be used as a substitute.
-
Read the leakage values into matrix
A(rows = R/G/B measurement bands, cols = C/M/Y dyes). Dye→channel: cyan↔Red, magenta↔Green, yellow↔Blue. Sample each dye's diffuse density at band centers R≈650 / G≈550 / B≈450 nm (datasheets use Status M). Each dye's own-band value is the diagonal; the other two are its unwanted absorption (positive). Reading a graph by eye is ±~0.05 density — fine, the result gets row-normalized.- Tier 1: read all nine values off the separated curves.
- Tier 2: diagonal = own band; estimate the off-diagonals from the visible secondary
humps in the aggregate curve plus known dye chemistry (magenta's unwanted blue and some
red; cyan a little green/blue; yellow nearly clean). Name it
"… (approx)". - Tier 3: stop. State that the datasheet lacks dye-density data. Optionally emit a
conservative starter close to NegPy's Default, named
"… (approx, starter)", and suggest chart-based calibration instead. Never invent precision.
-
Run the deriver:
echo '{"name":"Kodak Gold 200 (approx)","readings":[[1.0,0.06,0.02],[0.04,1.0,0.05],[0.02,0.12,1.0]]}' \ | python3 .claude/skills/crosstalk-from-datasheet/derive_matrix.py --out <film>.tomlreadingsisArow-major (R/G/B band rows). It column-normalizes, inverts, and rescales so the diagonal reads1.0; it exits non-zero on a singular or misshaped input rather than emitting a bad matrix. -
Review & self-validate — never trust the loader's silence (it skips malformed files without error):
- 3×3 numeric, no booleans.
- Diagonal
1.0; off-diagonals negative (a correction subtracts contamination) and small — flag anything|·| > 0.2as a likely misread/transpose. - Confirm it actually loads:
uv run python -c "from negpy.services.assets.crosstalk import CrosstalkProfiles as C; \ r=C._parse_file('<film>.toml'); assert r, 'INVALID — would be skipped'; print('ok', r[0])"
-
Write the file — pick the destination by intent:
- Contribute / bundle → repo gallery
crosstalk/<film>.toml. - Personal use now →
<Documents>/NegPy/crosstalk/<film>.toml(resolve the Documents path viaget_default_user_dir()innegpy/kernel/system/paths.py).
- Contribute / bundle → repo gallery
Quick reference
| TOML keys | matrix (required, 3×3 numeric, no bool); name (optional; "Default" reserved) |
| Convention | rows = output channels, cols = input channels (R/G/B) |
| Band centers | R 650 nm · G 550 nm · B 450 nm (Status M) |
| Dye→channel | cyan→R · magenta→G · yellow→B |
A shape | rows = bands (R/G/B), cols = dyes (C/M/Y); diagonal = own-band peak |
| Default (fallback) | [1.0,-0.05,-0.02, -0.04,1.0,-0.08, -0.01,-0.1,1.0] |
Common mistakes
- Transposing rows/cols —
Ais bands×dyes; the output is out-channels×in-channels. Keep R/G/B order throughout. - Positive off-diagonals in the output — wrong sign; a correction must subtract leakage.
- Using sensitivity curves as dye density — they measure different things; not interchangeable.
- Over-claiming precision — an aggregate (Tier 2) curve gives an estimate; say so in the name.
- Forgetting it's density-domain — read density off the dye-density plot, not transmittance.
Worked example — Kodak Gold 200 (Tier 2)
Read the datasheet PDF pages as images. The CURVES section has Characteristic, Spectral-
Sensitivity, and Spectral-Dye-Density plots — but the dye-density plot is a single aggregate
"midscale neutral" curve → Tier 2. Diagonals = 1.0; estimate off-diagonals from the
curve's secondary absorptions + dye chemistry (magenta's unwanted blue is the biggest term):
A (rows R/G/B, cols C/M/Y): R:[1.0, 0.06, 0.02]
G:[0.04, 1.0, 0.05]
B:[0.02, 0.12, 1.0 ]
Run the deriver → emitted TOML (note name flags it approximate):
name = "Kodak Gold 200 (approx)"
matrix = [
[1.0000, -0.0579, -0.0171],
[-0.0390, 1.0000, -0.0492],
[-0.0152, -0.1191, 1.0000],
]
The strong B,−0.119(G) term is the magenta-dye unwanted-blue correction. Validate it loads,
then write it to crosstalk/kodak_gold_200.toml (contribute) or the Documents folder (personal).