Skip to content

Bring omezarr/smartspim ports into parity with NDR-matlab main - #30

Merged
stevevanhooser merged 3 commits into
mainfrom
claude/lightsheet-zarr-ndi-viewer-djp5vk
Sep 16, 2026
Merged

stevevanhooser merged 3 commits into
mainfrom
claude/lightsheet-zarr-ndi-viewer-djp5vk

Conversation

@stevevanhooser

Copy link
Copy Markdown
Contributor

Follow-up to VH-Lab/NDR-matlab#139 (merged there). The Python-side ports of ndr.format.omezarr.{probe,reduce} and ndr.reader.{omezarr,smartspim} were already on main; this PR adds the pytest coverage that was missing on the Python side and bumps the four bridge entries that had drifted after the recent MATLAB blosc work.

Summary

  • Tests — new pytest coverage that mirrors the MATLAB unit tests function-for-function:
    • tests/test_omezarr_probe.py mirrors TestOMEZarrProbe.m (ok / n_pyramids / pyramid_names / axes_order / level0_* plus the not-ok paths).
    • tests/test_omezarr_reduce.py mirrors TestOMEZarrReduce.m (mean/max, dtype behaviour, per-axis factor, trailing partial block, arity + reduction validation).
    • tests/test_omezarr_reader.py mirrors TestOMEZarrReader.m (pinned / unpinned / unknown / sidecar resolveepoch, all metadata methods, single- and multi-frame pixel reads against ground truth, Level>1 reads, and the mean-vs-max routing proof at level 2).
    • tests/test_smartspim_reader.py mirrors TestSmartSPIMReader.m (pinned / unpinned-channel / unpinned-tile / sidecar resolveepoch, metadata, cross-channel reads against a synthetic TIFF fixture whose pixels encode (channelIndex, z)).
    • tests/_omezarr_fixture.py and tests/_smartspim_fixture.py mirror ndr.test.format.{omezarr,smartspim}.makeExampleFixture (dual-pyramid Zarr v2 store with a shared level-0 array; SmartSPIM tree with real TIFF slices). Zarr chunks are uncompressed here because zarr.open reads them through the same path as any other codec — no zstd binary or numcodecs.Blosc needed to exercise the read side.
  • Bridge hash bumps — four drifted entries picked up by test_matlab_bridge_completeness.py. Each entry now records the current MATLAB commit and a decision_log note explaining why the MATLAB change is a no-op on the Python side:
    • +ndr/+format/+blosc/encode.m 9a87c0a → aa89463
    • +ndr/+format/+blosc/decode.m 9a87c0a → aa89463
    • +ndr/+format/+omezarr/readArray.m a847431 → 8bea2d2
    • +ndr/+format/+omezarr/private/decompressBlosc.m a847431 → 5a44303

Every one is MATLAB-side blosc plumbing (batch API + persistent server, blosc-matlab MEX preference, hand-rolled codec dispatch replaced with a one-line delegation to ndr.format.blosc.decode). Python has always used numcodecs.Blosc through zarr directly, so all four are hash-bump-only.

Intentionally not ported

+ndr/+util/+blosc/* MEX bootstrapping (blosc-matlab pinning, ensureMex, persistent Python server, venv setup) stays MATLAB-only — see the matlab_only entries in src/ndr/format/blosc/ndr_matlab_python_bridge.yaml. The Python side reads and writes Blosc containers through numcodecs.Blosc directly, so there is nothing to port.

Test plan

  • NDR_MATLAB_PATH=../ndr-matlab pytest tests/ --ignore=tests/symmetry — 613 passed, 3 skipped.
  • NDR_BRIDGE_CHECK_STRICT=1 NDR_MATLAB_PATH=../ndr-matlab pytest tests/test_matlab_bridge_completeness.py tests/test_matlab_bridge_conventions.py — 191 passed, drift check green.
  • New reader tests (test_omezarr_reader.py and test_smartspim_reader.py) verify pixel-level correctness against ground truth, including the pyramid-routing invariant that mean and max reads return different pixels at the same coordinates.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UdLHfApBhntCz64BatnwY5


Generated by Claude Code

NDR-matlab now owns a Blosc container codec at +ndr/+format/+blosc,
backed by numcodecs.Blosc in a private subprocess venv (sonpipe
pattern; ndr.util.blosc.setup manages the venv, MATLAB's pyenv is
never touched).

There is no Python mirror and none is planned: Python callers should
use numcodecs.Blosc directly, and any MATLAB wrapper on top would
just be a rename over the same subprocess call. The new bridge yaml
records the four MATLAB functions (encode, decode, isBlosc, header)
with status: matlab_only so the mirror inventory stays honest.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N67xH9BejMzZp4GNAq8m7w
Follow-up to VH-Lab/NDR-matlab#139 (merged there): the probe/reduce
helpers and the omezarr/smartspim reader classes were already ported;
this commit adds the pytest coverage that was missing on the Python
side and bumps the four bridge entries that had drifted.

Tests mirror the MATLAB unit tests function-for-function:

- tests/test_omezarr_probe.py mirrors TestOMEZarrProbe.m: ok /
  n_pyramids / pyramid_names / axes_order / level0_* on the shared
  programmatic fixture, plus the not-ok paths (missing dir, empty
  dir).
- tests/test_omezarr_reduce.py mirrors TestOMEZarrReduce.m:
  mean/max, dtype (max preserves, mean promotes to float64), per-axis
  factor vector, trailing partial block kept, arity + reduction
  validation, factor-1 pass-through.
- tests/test_omezarr_reader.py mirrors TestOMEZarrReader.m: pinned /
  unpinned / unknown / sidecar resolveepoch, all metadata methods,
  single-frame / multi-frame / Level>1 pixel reads against ground
  truth, and mean-vs-max routing proof at level 2.
- tests/test_smartspim_reader.py mirrors TestSmartSPIMReader.m:
  pinned / unpinned-channel / unpinned-tile / sidecar resolveepoch,
  all metadata methods, single-frame / multi-frame / cross-channel
  reads against the synthetic fixture, out-of-range error.

Programmatic fixtures mirror ndr.test.format.{omezarr,smartspim}
.makeExampleFixture.m: dual-pyramid Zarr v2 store with a shared
level-0 array (uncompressed C-order chunks are enough since the read
path is zarr.open); a synthetic SmartSPIM tree with real TIFF slices
whose pixels encode (channelIndex, z) so cross-channel routing has a
byte-for-byte proof.

Bridge yaml updates (all drift picked up by
test_matlab_bridge_completeness.py):

- +ndr/+format/+blosc/encode.m         9a87c0a -> aa89463
- +ndr/+format/+blosc/decode.m         9a87c0a -> aa89463
- +ndr/+format/+omezarr/readArray.m    a847431 -> 8bea2d2
- +ndr/+format/+omezarr/private/
    decompressBlosc.m                  a847431 -> 5a44303

Every one is MATLAB-side blosc plumbing (batch API, persistent
server, MEX preference, hand-rolled codec dispatch replaced with
one-line delegation). Python has always used numcodecs.Blosc through
zarr directly, so all four are hash-bump-only per the decision_log
notes.

+ndr/+util/+blosc/* MEX bootstrapping stays deliberately unported
(numcodecs.Blosc on the Python side; see the matlab_only entries in
src/ndr/format/blosc/ndr_matlab_python_bridge.yaml).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UdLHfApBhntCz64BatnwY5
@stevevanhooser
stevevanhooser merged commit 76fc289 into main Sep 16, 2026
7 checks passed
@stevevanhooser
stevevanhooser deleted the claude/lightsheet-zarr-ndi-viewer-djp5vk branch September 16, 2026 14:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants