Skip to content

feat(virtualize): sample the omero windows of TIFF and QPTIFF images from the coarsest level - #98

Merged
d-v-b merged 1 commit into
mainfrom
claude/blissful-shtern-5ed7a1
Oct 23, 2026
Merged

d-v-b merged 1 commit into
mainfrom
claude/blissful-shtern-5ed7a1

Conversation

@d-v-b

@d-v-b d-v-b commented Oct 23, 2026

Copy link
Copy Markdown
Owner

🤖 AI text below 🤖

The omero channel windows of the TIFF and QPTIFF profiles are now sampled from the middle chunk of each channel at the coarsest level, as refkv's demo samples a contrast range, instead of only covering the data type's whole range. Float images get a valid omero too: a float TIFF used to have none, and QPTIFF float32 channels had no window, which ome-zarr-py rejects.

Rule (spec/virtualize/tiff.md §4.4, spec/virtualize/qptiff.md §4.3, each with a dated change note)

  • A chunk is sampled only when the core decodes the compression: none, Deflate (miniz_oxide) or LZW (a new size-bounded decoder in lzw.rs). JPEG, JPEG 2000 and zstd are not decoded.
  • The sampled chunk sits at t = 0, at the channel's chunk along c, and at the chunk holding floor(n / 2) along every other axis. Only its values inside the array are used, so an edge tile's padding and a padded last strip are left out. The fill value 0 is dropped, and so are non-finite floats.
  • start/end are the values at indices floor(m·5/100) and min(m−1, floor(m·999/1000)). This is integer arithmetic, so it is the same on wasm32.
  • min/max are the integer type's range, or the extremes of the sampled float values.
  • RGB samples (SVS, NDPI, RGB TIFF and QPTIFF) keep the full type range, since a window per sample would tint them.
  • These cases fall back to the type range for integers, and to no window for floats (no omero at all for a float TIFF): an absent chunk, a chunk that doesn't decode, one over 2^26 bytes, and one with no contrast.

How it reads: projections still read nothing themselves. project::reads returns the source ranges of the sampled chunks, taken from the references of the coarsest level only. The run reads them after the parser, the same way NIfTI and MRC edge chunks are read, and project/window.rs decodes them. The Python and wasm hosts therefore produce identical output, and conformance compares py with web entry for entry.

Tests

  • test_omero_windows_sample_the_coarsest_level in test_tiff.py and test_qptiff.py runs over every fixture. It checks the windows against numpy applied to the coarsest level, read through zarr-python (ir_helpers.omero_and_expected_windows).
  • One test per fallback case: undecoded codec, RGB, absent tile, tile that doesn't decode, tile without contrast. The crafted files come from tifffile, and patched copies of the QPTIFF fixtures cover the undecodable cases.
  • The omero of all 43 accepted TIFF, NDPI and QPTIFF fixtures validates with ome_zarr_models.common.omero.Omero.model_validate.

Checks run

  • cargo test --release --manifest-path rust/refkv-ir/Cargo.toml
  • uv run pytest -q python/tests: 2050 passed, 2 skipped
  • just js::test: 638 passed
  • just sections
  • conformance/virtualize/compare.py <out> --fixtures fixtures/{tiff,ndpi,qptiff}: 0 divergences
Stack notes

Builds on #95 (merged as 7d051e3), which added type_window and sample_channels. sample_channels now takes one window per sample. The frozen reference comparisons already ignore omero for tiff and ndpi (ADDED), so no reference changes are needed.

🤖 Generated with Claude Code

…from the coarsest level

The `omero` channel windows of the TIFF and QPTIFF profiles gave each
integer channel its data type's whole range, which leaves a dim 12- or
16-bit image black, and gave a float image no `omero` (TIFF) or no window
(QPTIFF float32 component data), which ome-zarr-py rejects. Each channel's
window is now sampled from the data, as refkv's demo samples a contrast
range: the middle chunk of the coarsest level, one per channel, is decoded
when the core decodes its compression (none, Deflate, LZW; not JPEG,
JPEG 2000 or zstd), and `start`/`end` are the 5th and 99.9th percentiles of
the channel's values inside the array, leaving out the fill value 0 (and
non-finite floats). `min`/`max` stay the integer type's range, or are the
float values' extremes, so a float TIFF now has a valid `omero`. RGB
samples (SVS, NDPI, RGB TIFF and QPTIFF) keep the type's full range, since
a window per sample would tint them; an absent or undecodable chunk, or one
without contrast, falls back to the type's range, or to no window (no
`omero` for a float TIFF).

The projections still read nothing: `project::reads` gives the TIFF and
QPTIFF sampled chunks' source ranges (from the coarsest level's references
alone), which the run reads after the parser as it reads NIfTI's and MRC's
copied chunks, and project/window.rs decodes them, so the Python and wasm
hosts give the same windows. lzw.rs gains a decoder that reads into the
chunk's size, as the QPTIFF codec does. spec/virtualize/tiff.md (§3.1, §4.4)
and spec/virtualize/qptiff.md (§4.3, §16) record the rule with dated notes,
and the format READMEs describe it. The Python tests check every TIFF and
QPTIFF fixture's windows against numpy over the coarsest level read through
zarr-python, and each fallback on its own; every accepted TIFF, NDPI and
QPTIFF fixture's `omero` validates with ome_zarr_models' Omero.

Assisted-by: ClaudeCode:claude-opus-5-5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@d-v-b
d-v-b merged commit 01c4200 into main Oct 23, 2026
51 checks passed
@d-v-b
d-v-b deleted the claude/blissful-shtern-5ed7a1 branch October 23, 2026 18:52
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