Skip to content

Fit a z-stack over the bridge - #4

Merged
alex1075 merged 1 commit into
mainfrom
feature/zstack
Sep 9, 2026
Merged

alex1075 merged 1 commit into
mainfrom
feature/zstack

Conversation

@alex1075

@alex1075 alex1075 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

A folder of region_zN.ptu slices goes to FLIMKit's z-stack fit and comes back as a volume the image analysis clients can open.

Each stack is fitted as one FOV: the decay is pooled over every slice, the lifetimes are fitted once from that pooled decay and locked, and each slice then gets a per-pixel fit with only the amplitudes free. A single slice rarely carries the photons to identify two lifetimes; the stack usually does.

Endpoints

POST /v1/zstack/scan what a folder holds, before any settings are offered
POST /v1/zstack run it as a job, progress by slice
GET /v1/zstack/defaults the settings schema
POST /v1/zstack/export rewrite a finished run in the other format, no refit
GET /v1/zstack/volume.ome.tif stream one to a client that cannot reach this disk

The scan exists so a client can refuse an empty folder without starting a job, and say how many stacks and slices it is about to fit.

Why the store looks the way it does

Written as bioformats2raw on Zarr v2, zlib, single resolution level. None of those is a preference:

  • zarr-python 3 defaults to zstd, which QuPath's jzarr refuses outright: Compressor id:'zstd' not supported. zlib is read by every Zarr v2 reader and compresses these maps better than blosc anyway — 4.3 MB against 6.7 MB on a 16.8 MB volume.
  • ome-zarr's default moved to NGFF 0.5, which is Zarr v3 and opens nowhere yet.
  • A plain NGFF store at the root opens with every channel called "Channel 1" and no pixel calibration. Names and voxel size reach QuPath through OME/METADATA.ome.xml.
  • A pyramid is built by interpolating between neighbours, and a lifetime map is mostly NaN where nothing was fitted, so the coarse levels would smear that NaN over the pixels that did fit. Chunking is what keeps a large volume readable.

Verified against QuPath 0.7.0 directly rather than by inspection: the store opens as 8 channels by 8 z, named and calibrated.

The writer is plain zarr now rather than ome-zarr, which also removes the signature sniffing its moving API needed.

Tests

26 new. The full suite passes apart from test_irf.py::test_site_specific_irfs_are_still_offered, which predates this branch and wants more than one machine IRF installed.

A folder of region_zN.ptu slices goes to FLIMKit's z-stack fit and comes
back as a volume the image analysis clients can open.

Each stack is fitted as one FOV: the decay is pooled over every slice, the
lifetimes are fitted once from that pooled decay and locked, and each slice
then gets a per-pixel fit with only the amplitudes free. A single slice
rarely carries the photons to identify two lifetimes; the stack usually
does.

/v1/zstack/scan reports what a folder holds before any settings are
offered, so an empty folder is refused without starting a job and a client
can say how many stacks and slices it is about to fit. /v1/zstack runs it,
/v1/zstack/export rewrites a finished run in the other format without
fitting again, and /v1/zstack/volume.ome.tif streams one for a client that
cannot reach this machine's disk.

The store is bioformats2raw on Zarr v2, zlib, single resolution level, and
none of those is a preference:

- zarr-python 3 defaults to zstd, which QuPath's jzarr refuses outright
  with "Compressor id:'zstd' not supported". zlib is read by every Zarr v2
  reader and compresses these maps better than blosc besides, 4.3 MB
  against 6.7 MB on a 16.8 MB volume.
- ome-zarr's own default moved to NGFF 0.5, which is Zarr v3 and opens
  nowhere yet.
- A plain NGFF store at the root opens with every channel called
  "Channel 1" and no pixel calibration. The names and the voxel size reach
  QuPath through OME/METADATA.ome.xml.
- A pyramid is built by interpolating between neighbours, and a lifetime
  map is mostly NaN where nothing was fitted, so the coarse levels would
  smear that NaN over the pixels that did fit. Chunking is what keeps a
  large volume readable.

Verified against QuPath 0.7.0 directly rather than by inspection: the
store opens as 8 channels by 8 z, named and calibrated.

The writer is plain zarr now rather than ome-zarr, which also removes the
signature sniffing its moving API needed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RxD4ykNwSf7AkdZ5jRgLc
@alex1075
alex1075 merged commit 7cfa750 into main Sep 9, 2026
6 checks passed
@alex1075
alex1075 deleted the feature/zstack branch September 9, 2026 07:39
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.

1 participant