Fit a z-stack over the bridge - #4
Merged
Merged
Conversation
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
This was referenced Sep 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A folder of
region_zN.ptuslices 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/scanPOST /v1/zstackGET /v1/zstack/defaultsPOST /v1/zstack/exportGET /v1/zstack/volume.ome.tifThe 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:
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/METADATA.ome.xml.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.