From a504387460309cd7cae171fcc944ee8a798389be Mon Sep 17 00:00:00 2001 From: Peter Jacobson Date: Fri, 12 Jun 2026 21:58:40 +1000 Subject: [PATCH] Recognise the normal Createc trailing appendix; only warn on oversized tails Every healthy Createc image carries a small appendix after the image planes: four spare scan-line buffers (4 * Num.X floats) plus a 32-float zero block in newer headers. Since the viewer began surfacing scan.warnings in the status bar, this normal format layout produced a "Loaded with warnings" message on every Createc load. The reader now treats tails within the 4 * Num.X + 32 budget as expected layout (still recorded as ignored_tail_float_count for diagnostics) and only warns when the tail exceeds the budget, i.e. payload it does not understand. Corpus test asserts every fixture loads warning-free; a synthetic oversized-tail test keeps the loud path covered. Co-Authored-By: Claude Fable 5 --- docs/createc_dat_reader.md | 16 +++++++++++++ probeflow/io/readers/createc_dat.py | 17 ++++++++++++-- tests/test_createc_dat_decode.py | 35 +++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/docs/createc_dat_reader.md b/docs/createc_dat_reader.md index 4b70fb3..4e501d2 100644 --- a/docs/createc_dat_reader.md +++ b/docs/createc_dat_reader.md @@ -43,6 +43,22 @@ contains decoded DAC values after ProbeFlow's safety cleanup: partial scan; 5. the first stored column is removed by default. +## Trailing Appendix + +Every healthy real Createc image fixture carries a small appendix after the +image planes: four spare scan-line buffers (`4 * Num.X` floats), plus a +32-float zero block in files with the newer header variant. The buffers are +zero-filled apart from an occasional stray sample at the start — the +turnaround point of a line that was never scanned. The appendix size does not +scale with the channel count (10- and 40-channel fixtures carry the same +`4 * Num.X + 32` floats as 4-channel ones). + +Because this appendix is normal format layout rather than data loss, the +reader records its size in `ignored_tail_float_count` but does not emit a +warning for tails within the `4 * Num.X + 32` budget. Tails larger than that +budget indicate payload the reader does not understand and still produce a +decode warning, which load paths surface to users. + The historical `raw_channels_dac` property remains as a compatibility alias, but it points to the same cleaned decoded arrays. Use `original_header`, `original_Nx`, and `original_Ny` when the acquisition dimensions or raw header diff --git a/probeflow/io/readers/createc_dat.py b/probeflow/io/readers/createc_dat.py index 95e0428..da18f04 100644 --- a/probeflow/io/readers/createc_dat.py +++ b/probeflow/io/readers/createc_dat.py @@ -37,6 +37,17 @@ z_scale_m_per_dac, ) +# Createc writes a small appendix after the image planes: four spare +# scan-line buffers (4 * Nx floats, zero-filled apart from an occasional +# stray turnaround sample), plus a 32-float zero block in newer headers. +# Every healthy real fixture carries it regardless of channel count, so a +# tail within this budget is normal format layout, not data loss; it is +# still recorded as ``ignored_tail_float_count`` on the report. Only tails +# larger than the budget indicate payload this reader does not understand +# and stay loud. +_TAIL_SCANLINE_BUFFER_COUNT = 4 +_TAIL_TRAILER_FLOAT_COUNT = 32 + @dataclass(frozen=True) class CreatecChannelInfo: @@ -133,10 +144,12 @@ def read_createc_dat_report( num_chan = _detect_channel_count(payload_float_count, Ny, Nx, header) needed = num_chan * Ny * Nx ignored_tail = payload_float_count - needed - if ignored_tail: + expected_tail = _TAIL_SCANLINE_BUFFER_COUNT * Nx + _TAIL_TRAILER_FLOAT_COUNT + if ignored_tail > expected_tail: warnings.append( f"ignored {ignored_tail} trailing float32 value(s) after " - f"{num_chan} channel(s)" + f"{num_chan} channel(s) — more than the expected Createc " + f"appendix of {expected_tail} value(s) for this image width" ) arr = np.frombuffer(payload, dtype="= 12 +@pytest.mark.parametrize("path", CREATEC_SCAN_FIXTURES, ids=lambda p: p.name) +def test_normal_appendix_tail_is_recorded_but_not_warned(path): + """Every healthy Createc image carries a zero appendix after the planes + (four spare scan-line buffers plus an optional 32-float block). The + viewer surfaces ``scan.warnings`` in the status bar, so this normal + format layout must not produce a user-facing warning; the size stays + available on the report for diagnostics. + """ + report = read_createc_dat_report(path, include_raw=False) + + assert 0 < report.ignored_tail_float_count <= 4 * report.original_Nx + 32 + assert not any("trailing float32" in w for w in report.warnings) + + scan = load_scan(path) + assert not any("trailing float32" in w for w in scan.warnings) + + +def test_oversized_tail_still_warns(tmp_path): + """A tail beyond the known appendix budget means payload the reader does + not understand and must stay loud.""" + dat = tmp_path / "oversized_tail.dat" + header = b"[Paramco32]\nNum.X=2\nNum.Y=2\nChannels=2\n" + # 2 channels of (2,2) pixels = 8 floats, then 41 tail floats: one more + # than the 4*Nx + 32 = 40 appendix budget. + payload = np.arange(1, 50, dtype="