Skip to content

Commit 3ffd40b

Browse files
cri: integrate on the 1 nm CMFs — agreement with colour-science to 0.03
John asked whether the values could gain a decimal. Investigation: - TM-30 already agreed to 0.002 (the loose tolerance was hiding it) - CRI's F-series residual (-0.03..-0.08) was hunted through three hypotheses: CCT method (exonerated -- Robertson vs our exact locus differ < 1 K), boundary extrapolation (exonerated -- moves 0.001), integration practice (CONFIRMED -- the 5 nm abridged Riemann sum vs 1 nm-practice integration accounts for 3-5x of it) - fix: cri() integrates on the CIE-normative 1 nm CMFs (what we shipped them for: F-series line+phosphor spectra ARE the spiky case) with a 1 nm Planckian reference. Residual now Ra <= 0.027, Rf/Rg <= 0.002; the remainder is linear-vs-Sprague interpolation flavor, far below the metrics' integer-published precision. - oracle tolerances tightened to just above measured (0.05/0.01/0.01) so any regression from today's agreement fails loudly. 161/161, nothing skipped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 93fcdef commit 3ffd40b

3 files changed

Lines changed: 50 additions & 41 deletions

File tree

docs/whitepoint.js

Lines changed: 32 additions & 32 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/spectral/quality.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
CMF_1931_2, CMF_1964_10, sampleSpd, emissionToXyz, spectrumXy,
1717
planckianSPD, daylightSPD, cctOf,
1818
} from './index.js';
19+
import { CMF_1931_2_1NM } from './data-1nm.js';
1920
import { TCS_CIE1995 } from './data-tcs.js';
2021
import { CES_CIE2017 } from './data-ces.js';
2122

@@ -70,8 +71,11 @@ const dKD = (u, v) => (1.708 * v + 0.404 - 1.481 * u) / v;
7071
*/
7172
export function cri(spd) {
7273
const { cct, duv } = cctOf(spectrumXy(spd));
73-
const ref = cct < 5000 ? planckianSPD(cct) : daylightSPD(Math.min(cct, 25000));
74-
const cmf = CMF_1931_2;
74+
const ref = cct < 5000 ? planckianSPD(cct, { step: 1 }) : daylightSPD(Math.min(cct, 25000));
75+
// the 1 nm CMFs: F-series-style line+phosphor spectra are exactly what
76+
// the abridged 5 nm tables mis-integrate (measured: 3–5× the residual
77+
// against colour-science's 1 nm-practice computation)
78+
const cmf = CMF_1931_2_1NM;
7579

7680
const wt = uv1960(whiteXyz(spd, cmf));
7781
const wr = uv1960(whiteXyz(ref, cmf));

test/quality.test.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ test('CIECAM02 reproduces the CIE 159:2004 worked example', () => {
2828
});
2929

3030
test('CRI of a reference illuminant is 100 by construction', () => {
31-
const { Ra, Ri } = cri(planckianSPD(2856));
32-
assert.ok(Math.abs(Ra - 100) < 1e-6, `Planckian Ra ${Ra}`);
33-
assert.ok(Ri.every((r) => Math.abs(r - 100) < 1e-6));
31+
// same 1 nm grid as the reference cri() builds internally — the
32+
// construction holds only when test and reference share a representation
33+
const { Ra, Ri } = cri(planckianSPD(2856, { step: 1 }));
34+
assert.ok(Math.abs(Ra - 100) < 1e-4, `Planckian Ra ${Ra}`);
35+
assert.ok(Ri.every((r) => Math.abs(r - 100) < 1e-4));
3436
// daylight: cctOf solves ~6502 for the 6504 synthesis — sub-0.5 wiggle
3537
assert.ok(cri(daylightSPD(6504)).Ra > 99.5, `D65 Ra ${cri(daylightSPD(6504)).Ra}`);
3638
});
@@ -84,9 +86,12 @@ test('agreement with python colour-science',
8486
const SPDS = { FL2: FL2_SPD, FL7: FL7_SPD, FL11: FL11_SPD, A: illuminantASPD(), D65: daylightSPD(6504) };
8587
for (const { name, Ra, Rf, Rg } of rows) {
8688
const ours = { Ra: cri(SPDS[name]).Ra, ...tm30(SPDS[name]) };
87-
// tolerance covers 5 nm CES vs the oracle's 1 nm + interpolation policy
88-
assert.ok(Math.abs(ours.Ra - Ra) < 1.0, `${name} Ra ${ours.Ra} vs oracle ${Ra}`);
89-
assert.ok(Math.abs(ours.Rf - Rf) < 1.0, `${name} Rf ${ours.Rf} vs oracle ${Rf}`);
90-
assert.ok(Math.abs(ours.Rg - Rg) < 1.5, `${name} Rg ${ours.Rg} vs oracle ${Rg}`);
89+
// measured agreement: Rf/Rg ≤ 0.002, Ra ≤ 0.027 (the residual is
90+
// linear-vs-Sprague interpolation flavor, far below the metrics'
91+
// integer-published precision). Tolerances sit just above so any
92+
// regression from today's agreement fails loudly.
93+
assert.ok(Math.abs(ours.Ra - Ra) < 0.05, `${name} Ra ${ours.Ra} vs oracle ${Ra}`);
94+
assert.ok(Math.abs(ours.Rf - Rf) < 0.01, `${name} Rf ${ours.Rf} vs oracle ${Rf}`);
95+
assert.ok(Math.abs(ours.Rg - Rg) < 0.01, `${name} Rg ${ours.Rg} vs oracle ${Rg}`);
9196
}
9297
});

0 commit comments

Comments
 (0)