diff --git a/ROADMAP.md b/ROADMAP.md index d9f56ef..28b2701 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,10 +1,7 @@ # display-report — Roadmap This layer's roadmap. Cross-repo coordination and the pipeline-wide -sequencing live in -[color-wrangler](https://github.com/Fuse-Technical-Group/color-wrangler); -workstreams there are addressed by slug in backticks and resolve in -that repository's ROADMAP.md. +sequencing live in the umbrella project's roadmap. ## Contract-driven analysis §road:contract-analysis-impl diff --git a/SPEC.md b/SPEC.md index 2ee83da..9ab3d73 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1,10 +1,7 @@ # display-report — Specification This layer's specification. The pipeline-wide architecture and the -artifact chain live in -[color-wrangler](https://github.com/Fuse-Technical-Group/color-wrangler); -sections there are addressed by slug in backticks and resolve in that -repository's SPEC.md. +artifact chain live in the umbrella project's specification. ## Scope §spec:scope @@ -15,18 +12,16 @@ serial port and no DeckLink, imports no device driver, and needs no rig: a machine that reports needs a file and nothing else. One tool touches instruments and signal hardware, and it is -display-measure (`§spec:session-ownership`). This repository consumes -what that tool emits. +display-measure. This repository consumes what that tool emits. **Why the validator owns no measure path.** display-report validates either this pipeline or a display-side calibration, and its value is that it has no stake in the correction under test. A second measure path living inside it would be an ungated one — the session gates -(`§spec:signal-contract`) live in display-measure, and a path that -skips them measures a display in an undeclared state and renders a -well-formed report of it. That is the failure the gates exist to -prevent, reintroduced in the repository whose independence is the -reason they matter. +live in display-measure, and a path that skips them measures a display +in an undeclared state and renders a well-formed report of it. That is +the failure the gates exist to prevent, reintroduced in the repository +whose independence is the reason they matter. **Reproducibility is a property of the seam, not of a bundled loop.** A third party reproduces a report by writing the seam file from @@ -37,16 +32,16 @@ second device path inside the validator was never what delivered it. Not owned here: instrument and signal-generator access (display-measure), OCIO semantics and config generation (ocio-display-gen), the show manifest and the promotion decision -(color-wrangler). +(the umbrella project). ## Report input §spec:report-input *Status: complete* -The report's input is one file: the measurement seam file -(`§spec:measurement-seam`), carrying the measurements, the spectra -behind them, the protocol that produced them, the declared signal -contract, the attested panel state, and the hash chain. +The report's input is one file: the measurement seam file, carrying +the measurements, the spectra behind them, the protocol that produced +them, the declared signal contract, the attested panel state, and the +hash chain. Analysis is a pure function of that file. Two runs over one file produce one report. @@ -61,7 +56,7 @@ catches it. **Rows without a measured spectrum are legible as such.** A disciplined session reads its dark end with a colorimeter, and those -rows carry a reconstructed spectrum or none (`§spec:spectral-retention`). +rows carry a reconstructed spectrum or none. An analysis needing a measured spectrum reports which rows it excluded and why, rather than treating a scaled estimate as a measurement. @@ -113,8 +108,8 @@ judges from. Plot bounds derived from a hardcoded PQ inverse place the measured points wrongly on a gamma session's page — the chart renders, and it is wrong. -The page's content — what figures it carries and what they -discriminate — is specified in `§spec:report-metrics`. +The umbrella project's specification defines the page's content: +what figures it carries and what they discriminate. ## Programmatic surface §spec:report-api @@ -126,9 +121,9 @@ rendered report as bytes. The command-line entry point is a caller like any other and holds no logic of its own. **Why an importable surface.** The operator's surface is a browser -served from the session host (`§spec:web-ui`), and it generates the -report from a loaded artifact without the operator leaving the page or -learning a second tool. A caller reduced to shelling out to a CLI and +served from the session host, and it generates the report from a +loaded artifact without the operator leaving the page or learning a +second tool. A caller reduced to shelling out to a CLI and scraping a path cannot report a failure precisely. display-report remains the only thing that decides what a report says; which surface invokes it is a separate question. diff --git a/display_report/analysis.py b/display_report/analysis.py index db636ba..28e98cb 100644 --- a/display_report/analysis.py +++ b/display_report/analysis.py @@ -296,9 +296,8 @@ def black(self) -> dict: tmp = self._black = {} tmp["measurements"] = measurements = self._data.measurements[mask] - # A disciplined session reads its dark end with a colorimeter - # (§spec:spectral-retention), so the black rows are exactly the ones - # most likely to carry no spectrum. Black's tristimulus is measured + # A disciplined session reads its dark end with a colorimeter, so + # the black rows are exactly the ones most likely to carry no spectrum. Black's tristimulus is measured # either way; the spectral fields are only available when a black # row carried a spectrum, and are None rather than zero when not -- # a zero here would read as a perfectly black display. diff --git a/display_report/gamut.py b/display_report/gamut.py index 12d4ef5..8d08697 100644 --- a/display_report/gamut.py +++ b/display_report/gamut.py @@ -1,15 +1,14 @@ -"""Gamut arithmetic for the situational-awareness view (§spec:gamut-visualization). +"""Gamut arithmetic for the situational-awareness view. Everything here works in CIE 1976 u'v'. The 1931 xy diagram exaggerates green distances and crushes blue ones, which misleads exactly where -narrow-band LED primaries land; artifacts record xy and views convert -(§spec:gamut-visualization). +narrow-band LED primaries land; artifacts record xy and views convert. **Coverage, never area ratio.** The fraction of a target a display can actually reproduce is bounded above by 1.0. The ratio of triangle areas is not, and it flatters a display that is large in a direction no content uses: the bench display measures 131.6% of Rec.709 by area and -cannot reach Rec.709 blue (§spec:report-metrics). +cannot reach Rec.709 blue. **A scalar hides where the shortfall is.** Coverage weights by chromaticity area, so a thin slice of unreachable colour reads as a @@ -165,8 +164,7 @@ def primary_deficits(display: list[Point], standard: str) -> dict[str, float]: Read against the display's own native primaries rather than a standard's wherever the question is what a *configuration* costs: no display reaches Rec.709 blue, so that shortfall is a property of - the emitter and discriminates nothing between calibrations - (§spec:report-metrics). + the emitter and discriminates nothing between calibrations. """ target = [uv_from_xy(*p) for p in STANDARD_GAMUTS[standard]] deficits = {} diff --git a/display_report/transfer.py b/display_report/transfer.py index e02d5a9..14fc787 100644 --- a/display_report/transfer.py +++ b/display_report/transfer.py @@ -1,4 +1,4 @@ -"""Transfer fidelity and quantization headroom (§spec:gamut-visualization). +"""Transfer fidelity and quantization headroom. Two questions about the same ramp. Does the display follow the transfer function its contract declares, and is the encoding precise enough that @@ -17,7 +17,7 @@ here asks the same question of any encoding — a gamma contract at 12 bits, a PQ contract at 10 — rather than asking whether a display tracks one particular curve. That generality is the point: it is the metric -that survives changing the contract (§road:lut-transfer-probe). +that survives changing the contract. Its threshold is not a hard line. Barten's model takes viewing distance, field size and spatial frequency, and the value used here is the peak of @@ -46,7 +46,7 @@ Ramp = list[tuple[int, float]] | tuple[tuple[int, float], ...] # Readings at or under this are instrument noise rather than the display's -# response; fitting through them fits the noise (§road:instrument-floors). +# response; fitting through them fits the noise. FLOOR = 0.001 # The spatial frequency Barten's sensitivity peaks near, cycles per @@ -182,8 +182,7 @@ def quantization_headroom( measured. The ramp is driven at 12-bit codes, so a step at 10 bits is four of them: this is how one contract's quantization gets compared against another's on the same measured display, which is what - §road:lut-transfer-probe needs to settle whether a PQ-like LUT - contract beats the 12-bit gamma one. + settles whether a PQ-like LUT contract beats the 12-bit gamma one. """ codes_per_step = 2.0 ** (MEASURED_BIT_DEPTH - bit_depth) rows: list[HeadroomRow] = [] diff --git a/display_report/volume.py b/display_report/volume.py index deb5735..6858f54 100644 --- a/display_report/volume.py +++ b/display_report/volume.py @@ -1,4 +1,4 @@ -"""The reproducible set as a solid (§spec:gamut-visualization). +"""The reproducible set as a solid. A chromaticity triangle is one slice of a three-dimensional set, and it hides what decides whether a colour is usable: the luminance it is @@ -15,7 +15,7 @@ rather than drawing a capability nobody measured. This display sums to within 0.75% of its measured white, which is what earns the picture. -**Why CIELAB.** §spec:report-metrics asks for a luminance-inclusive +**Why CIELAB.** The report's metrics call for a luminance-inclusive volume in a perceptually uniform space, so that equal distances in the picture mean roughly equal perceived differences and the solid's shape carries information rather than the space's distortion. L* is relative diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md index 35941ae..97afbc6 100644 --- a/tests/fixtures/README.md +++ b/tests/fixtures/README.md @@ -12,10 +12,9 @@ display-measure characterize --out hybrid_session.csmf --instrument doubles-hybr 72 rows from the `color-wrangler/characterize/3` protocol against display-measure's deterministic display double, under a GAMMA 2.4 contract. 71 rows are spectral; the black row is colorimetric and carries no spectrum -at all, because a disciplined session reads its dark end with a colorimeter -(§spec:spectral-retention). That row is the point of the fixture — it is the -shape the analysis has to tolerate, and no hand-built file would have -produced it. +at all, because a disciplined session reads its dark end with a colorimeter. +That row is the point of the fixture — it is the shape the analysis has to +tolerate, and no hand-built file would have produced it. Regenerate it from display-measure when the protocol or the seam format changes. Do not hand-edit it. diff --git a/tests/test_gamut.py b/tests/test_gamut.py index 4a86ac3..e6e0218 100644 --- a/tests/test_gamut.py +++ b/tests/test_gamut.py @@ -1,8 +1,7 @@ -"""Gamut arithmetic for the situational-awareness view (§spec:gamut-visualization). +"""Gamut arithmetic for the situational-awareness view. The numbers here are the bench display's, measured under three different -panel configurations, because the arithmetic's job is to tell them apart -(§spec:report-metrics). +panel configurations, because the arithmetic's job is to tell them apart. """ import pytest @@ -42,7 +41,7 @@ def test_a_gamut_covers_itself_entirely(self) -> None: def test_coverage_never_exceeds_one(self) -> None: """The bench display's triangle is larger than Rec.709's by area and still cannot reproduce all of it — the distinction area ratios - lose (§spec:report-metrics).""" + lose.""" for primaries in (AUG12, AUG29): for name in STANDARD_GAMUTS: target = [uv_from_xy(*p) for p in STANDARD_GAMUTS[name]] diff --git a/tests/test_transfer.py b/tests/test_transfer.py index 38aacb7..f817c29 100644 --- a/tests/test_transfer.py +++ b/tests/test_transfer.py @@ -1,4 +1,4 @@ -"""Transfer fidelity and quantization headroom (§spec:gamut-visualization). +"""Transfer fidelity and quantization headroom. The bench display's own gray ramp, dark-room capture. Its declared contract is a 2.35 power law and it does not follow one: the measured response @@ -108,7 +108,7 @@ def test_the_threshold_flag_tracks_the_ratio(self) -> None: class TestBitDepthComparison: """The same measured display, judged against a different encoding — the - comparison §road:lut-transfer-probe needs.""" + comparison that settles which LUT contract to run.""" def test_fewer_bits_make_every_step_coarser(self) -> None: twelve = { diff --git a/tests/test_volume.py b/tests/test_volume.py index 6e88c6e..8b39bcb 100644 --- a/tests/test_volume.py +++ b/tests/test_volume.py @@ -1,4 +1,4 @@ -"""The reproducible set as a solid (§spec:gamut-visualization). +"""The reproducible set as a solid. A chromaticity triangle is one slice of a three-dimensional set, and it hides the thing that decides whether a colour is usable: the luminance