Skip to content

Add image/frame reading API, TIFF + NANSEN ImageStack readers (NDI-matlab#823 companion)#106

Merged
stevevanhooser merged 13 commits into
mainfrom
claude/loving-wozniak-stmogc
Jun 10, 2026
Merged

Add image/frame reading API, TIFF + NANSEN ImageStack readers (NDI-matlab#823 companion)#106
stevevanhooser merged 13 commits into
mainfrom
claude/loving-wozniak-stmogc

Conversation

@stevevanhooser

Copy link
Copy Markdown
Contributor

Summary

NDR-side companion work for VH-Lab/NDI-matlab#823 (imageseries acquisition/ingest/read machinery). Adds a frame-based reading API to ndr.reader, two concrete image readers, requirements, and CI unit tests.

Frame API (ndr.reader.base / ndr.reader)

New methods, each taking (epochstreams, epoch_select, ...) like the existing ephys API: numframes, framesize (reports [Y X C Z T] without loading pixels), dimensionorder (default 'YXCZT', channel kept separate from spatial/ordering axes), datatype, frametimes, readframes. Image readers are siblings of the regularly-sampled readers, not subclasses — they implement only the frame API. Design adapted from nansen.stack.ImageStack (VervaekeLab), cited in headers; no NANSEN source copied.

Concrete readers (registered in ndr.known_readers())

  • ndr.reader.tiffstack ('tiffstack') — native multipage/multichannel TIFF reader using only built-in MATLAB TIFF support. Timing model: a <name>_frametimes.txt sidecar (one time per page, seconds) marks a movie → epochclock = dev_local_time, t0_t1 = [first last]; no sidecar → clockless ordered stack → no_time, [NaN NaN].
  • ndr.reader.imagestack ('imagestack') — backend wrapping nansen.stack.ImageStack, exposing all of NANSEN's +virtual format adapters through one reader string. Constructs via nansen.stack.open(file) → virtual adapter → ImageStack, and restores explicit singleton C/Z dims after getFrameSet (which returns squeezed YXCZT). Errors with a clear install message if NANSEN is absent; native readers never touch it.

Requirements / CI

  • tools/requirements.txt: added https://github.com/VervaekeLab/NANSEN@main, installed by matbox.installRequirements in both CI workflows (NANSEN was recently made R2025a-compatible).
  • tools/tests/+ndr/+unittest/+reader/TestTiffstack.m — frame API tests against synthesized TIFFs (clockless + movie cases).
  • tools/tests/+ndr/+unittest/+reader/TestImagestack.m — NANSEN-backed reader tests (geometry, frame round-trip, agreement with the native tiffstack reader, clock consistency); skipped via assumeTrue when NANSEN is not on the path.

Notes for review

  • This code has not been run locally (no MATLAB in the authoring environment) — this PR is partly to let CI exercise it.
  • Watch for path shadowing from NANSEN's genpath install (its external/ tree) affecting existing tests.
  • Deferred per the issue: projections/downsampling, per-channel frame selection.

Companion NDI-matlab changes (bridge reader ndi.daq.reader.image.ndr, ndi.daq.system.image, element/probe, ingestion docs) are on the same branch name in NDI-matlab.

https://claude.ai/code/session_01M7f7wSBJeN4QJFvuCwdsSu


Generated by Claude Code

claude added 2 commits June 10, 2026 00:38
Add the imageseries half of the NDR reader interface, the NDR-matlab
companion work for VH-Lab/NDI-matlab#823. Image readers are siblings of
the regularly-sampled (ephys) readers, not subclasses: they implement
only the frame API.

- ndr.reader.base / ndr.reader: add the frame API surface (numframes,
  framesize, dimensionorder, datatype, frametimes, readframes), modeled
  on nansen.stack.ImageStack (VervaekeLab). epochclock/t0_t1 already
  exist and carry the clock for movies vs. clockless stacks.
- ndr.reader.tiffstack: native multipage-TIFF reader using only built-in
  MATLAB TIFF support (no external deps). A '<name>_frametimes.txt'
  sidecar marks a movie (dev_local_time); absence means a clockless
  ordered stack (no_time).
- ndr.reader.imagestack: optional backend wrapping nansen.stack.ImageStack
  to expose all NANSEN +virtual formats through one reader string. NANSEN
  stays an optional dependency, loaded only when this reader is used.
- Register 'tiffstack' and 'imagestack' in ndr_reader_types.json so they
  appear in ndr.known_readers().
- tools/tests/+ndr/+unittest/+reader/TestTiffstack.m: CI unit tests for
  the TIFF frame API (clockless + movie cases), independent of NDI.

NANSEN attribution is cited in the class headers; only the API/design is
adapted, no NANSEN source is copied.

https://claude.ai/code/session_01M7f7wSBJeN4QJFvuCwdsSu
Declare NANSEN (VervaekeLab) in tools/requirements.txt so
matbox.installRequirements installs it, including in the CI test
workflows. NANSEN is still touched only by the ndr.reader.imagestack
backend; native readers do not use it.

Fix the imagestack backend against the real ImageStack API:
- construct via nansen.stack.open(filename) -> virtual data ->
  nansen.stack.ImageStack(virtualData); ImageStack's constructor does
  not accept a filename directly.
- restore explicit singleton C/Z dimensions after getFrameSet, which
  returns YXCZT data squeezed along singleton dimensions, so the NDR
  frame contract ([Y X C Z T]) holds.

Add tools/tests/+ndr/+unittest/+reader/TestImagestack.m: CI unit tests
for the NANSEN-backed reader (geometry, frame round-trip, agreement
with the native tiffstack reader, clock consistency). Tests are skipped
via assumeTrue when NANSEN is not on the path, so local runs without
the requirements installed do not fail.

https://claude.ai/code/session_01M7f7wSBJeN4QJFvuCwdsSu

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Analyzer found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Test Results

170 tests   170 ✅  7s ⏱️
 25 suites    0 💤
  1 files      0 ❌

Results for commit 69a929e.

♻️ This comment has been updated with latest results.

github-actions Bot and others added 11 commits June 10, 2026 11:00
NANSEN's ImageStack.getFrameTimes returns durations and, for formats
whose adapter supplies no timing metadata (TimeIncrement unset), can
return an empty/short array instead of erroring. Guarantee the
(epochfiles, frameind) -> times contract regardless:

- convert duration results to seconds (double),
- coerce any result whose length != numel(frameind) to a NaN vector of
  the correct length.

epochclock/t0_t1 already fall back to no_time when the times are not
finite, so a format with no timing surfaces as a clockless epoch rather
than a fabricated timeline; this just makes frametimes itself
length-correct for direct callers.

Add TestImagestack.testFrametimesLengthContractAndConsistency: asserts
one returned time per requested frame across several index patterns and
that frametimes is consistent with the reported epoch clock (no_time =>
all NaN, dev_local_time => all finite).

https://claude.ai/code/session_01M7f7wSBJeN4QJFvuCwdsSu
An imaging acquisition can write thousands of single-plane files per
epoch (e.g. Prairie View). Make both image readers accept an epoch
described as a single file, a directory, or an explicit list of files,
so the epoch can be anchored on its directory instead of enumerating
every frame file.

ndr.reader.tiffstack (native):
- imagefiles(): expand directories to their ordered .tif/.tiff contents;
  ignore non-image companions.
- resolveepoch()/framesource(): map a global frame index to a
  (file, page) source across the ordered files, assuming a homogeneous
  stack (geometry/pages taken from the first file).
- numframes/framesize/datatype/readframes now span all files; frametimes
  reads a directory-level 'frametimes.txt' for multi-file epochs (or the
  '<base>_frametimes.txt' sidecar for a single file).

ndr.reader.imagestack (NANSEN):
- imagesource()/sameextfiles(): expand a directory to its ordered,
  same-extension image files and pass that list to nansen.stack.open
  (which rejects a bare folder); drops companion files like Prairie .xml.

Tests: TestTiffstack gains a Prairie-like directory epoch (single-frame
TIFFs + directory frametimes.txt + an ignored .xml), checking geometry,
ordered frame round-trip, directory-level timestamps, and that passing
the directory equals passing the explicit file list.

https://claude.ai/code/session_01M7f7wSBJeN4QJFvuCwdsSu
…tory

The file navigator can only discover epochs by matching a file, not a
bare directory, so an imaging epoch is anchored on a per-epoch marker
(e.g. a Prairie '.xml'/'.pcf' config). Make ndr.reader.tiffstack accept
such an anchor: any epochstream entry that is neither a TIFF nor a
directory is treated as a marker, and the stack's TIFFs are taken from
the marker's parent directory. This is the format-agnostic scaffolding;
format-specific parsing of the anchor (e.g. reading frame times out of a
Prairie config) layers on top.

Factor the directory globbing into tiffsindir(). Add TestTiffstack
coverage that passing the .xml anchor resolves to the same frames and
directory-level timestamps as passing the directory.

https://claude.ai/code/session_01M7f7wSBJeN4QJFvuCwdsSu
…tlab)

Add ndr.reader.prairieview for legacy Prairie recordings: a directory of
one-TIFF-per-frame plus a '*_Main.pcf' config whose
'[Image TimeStamp (us)]' section provides true per-frame timestamps. The
epoch surfaces as a 'dev_local_time' movie with those (possibly
irregular) times.

The reader extends ndr.reader.tiffstack, inheriting the directory frame
reading (enumerate TIFFs in name order, framesize, datatype, readframes,
anchor-file resolution) and overriding only the timing (frametimes /
epochclock / t0_t1) to come from the Prairie config.

Config handling is ported and revised from VH-Lab/vhlab-TwoPhoton-matlab
(Platforms/PrairieView readprairieconfig.m, tpconfigfilename.m) into
ndr.format.prairieview.{readconfig,configfilename}: the .pcf section
parser is preserved but made robust to CR/LF/CRLF line endings and uses
str2double. Modern Prairie 2.2+ XML is not parsed here (use
ndr.reader.imagestack / NANSEN for that); an .xml config returns
is_xml=true and the reader falls back to tiffstack timing.

Register reader strings prairieview/prairie/pv. Add TestPrairieView
(synthetic .pcf + frame TIFFs) covering config parsing, config-file
discovery, geometry, frame round-trip, and config-derived timestamps
(including the .pcf passed as the epoch anchor).

v1 assumes a single channel (one TIFF per frame); multi-channel
interleaving is future work.

https://claude.ai/code/session_01M7f7wSBJeN4QJFvuCwdsSu
Each Prairie channel is written as its own TIFF
('..._Cycle<n>_Ch<c>_<frame>...'). Parse the Cycle/Ch/frame tokens from
the file names and group all channels of a timepoint onto the C axis, so
a "frame" is one timepoint with one timestamp, and readframes returns
[Y X C 1 nTimepoints] with channels in ascending Ch order.

ndr.reader.prairieview now overrides the frame layout (framelayout +
numframes/framesize/datatype/readframes) instead of inheriting the
one-TIFF-per-frame layout from tiffstack; timing still comes from the
config's real per-frame timestamps. This mirrors how NANSEN's
PrairieViewTiffs groups channels from file names, but keeps the true
per-frame timestamps (NANSEN derives uniform times from a single frame
period).

Factor the imfinfo->class mapping into a static ndr.reader.tiffstack.tiffclass
shared by tiffstack.datatype and the prairieview layout.

TestPrairieView gains a 2-channel recording fixture and tests for
multi-channel geometry, frame round-trip (channels on C), and one
timestamp per timepoint.

https://claude.ai/code/session_01M7f7wSBJeN4QJFvuCwdsSu
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.

3 participants