Skip to content

CSB: no CI coverage for event decoding, integration or To Frames #91

Description

@CSSFrancis

The .csb reader (#90) has ~2700 lines and CI exercises only the top of it.

electron/tests/csb_movie.spec.ts drives the real path end to end — but
against an 8192², 400-frame, 125.8M-event stream that is not in the repo,
so it test.skips itself on every runner. spyde/tests/migrated/test_csb_reader.py
(added in #90) covers what can be reached without event payload:
registration, the 108-byte header, block-grid geometry and padding, and the
four ways a file is rejected.

Uncovered in CI: event decoding, integration over a frame range, the
plane cache, spatial binning, To Frames, and the claim that matters most —

All three CPU paths are bit-identical to one another and to the GPU path.

That is asserted in _core.py's module docstring and nothing checks it.

This is more tractable than it looks

I initially assumed synthesising events meant reverse-engineering the sparse
encoder. It does not — _core.py documents the format, and it is simple:

Little-endian throughout. A 108-byte metadata header (padded out to
csb_data_offset), then one uint16 per event, then a footer of one uint16
event-count per block.

y = block_y_origin + word // csb_block_width
x = block_x_origin + word %  csb_block_width

So a fixture is: header → event words → counts table, with
csb_lengths_offset = csb_data_offset + n_events * 2. test_csb_reader.py
already builds a valid zero-event file (_csb_bytes); this is that plus a
payload. And because the mapping above is two lines of arithmetic, the
expected image is computable in plain numpy, so the test asserts against
a value derived independently of the reader rather than a golden blob.

Worth covering

  • A handful of events at known positions decode to the right pixels —
    including the last raster slot in a block, and a partial edge block
    (edge blocks keep the full stride and are cropped at readout, so an
    off-by-one there is invisible on a frame that divides evenly)
  • csb_order 0 vs 1 (column- vs row-major block ordering)
  • Integration over [f0, f1) sums the frames it should, and no others
  • Binning: bin=n equals the unbinned result box-summed by n
  • Backend parity — numpy / numba / cupy / torch bit-identical, skipping
    whichever are absent. This is the docstring's own claim.
  • To Frames at an exposure that is not a multiple of the load exposure
  • plane_counts matches summing the decoded frames

Notes

  • Keep the fixture small (a 64×48 frame with a few blocks). The point is
    correctness of the mapping, not throughput — benchmark_*.py is where
    scale belongs.
  • The GPU paths need the usual care: torch-CUDA work segfaults under pytest
    on Windows, so parity runs in a subprocess (see
    test_vector_orientation_gpu.py for the pattern).
  • This blocks nothing — Direct Electron .csb — read the event stream, scrub it, and re-cut it To Frames #90 is safe to merge on the coverage it has, since
    the uncovered code is exercised by hand on a machine with the stream. It
    is what stops a future refactor of _sparse.py or _torch.py from
    breaking decoding silently.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions