Skip to content

fix(py): normalize generated axes to the spec order (t, c, z, y, x) - #623

Merged
thewtex merged 2 commits into
fideus-labs:mainfrom
vboussot:fix/canonical-axis-order
Aug 6, 2026
Merged

fix(py): normalize generated axes to the spec order (t, c, z, y, x)#623
thewtex merged 2 commits into
fideus-labs:mainfrom
vboussot:fix/canonical-axis-order

Conversation

@vboussot

@vboussot vboussot commented Aug 4, 2026

Copy link
Copy Markdown
Member

Addresses the channel-last axis order flagged in #611.

OME-Zarr (0.4–0.6) orders axes by type: time, then channel, then space.
ngff-zarr itself produced channel-last stores — the TIFF S (sample) axis,
ITK component images, and the 4-D/5-D default dims inference all yield
(..., c), and the downsampling methods preserve their input order. The
result: stores that validate_structural itself flags with axis-order,
hence the write-gate carve-out in #611 that warns instead of raising.

What changes

  • to_multiscales() normalizes the axis order to (t, c, z, y, x) with a
    lazy transpose before generating metadata; the pyramidal-TIFF reuse path
    (which bypasses to_multiscales) does the same per level.
  • Dims outside the canonical set pass through untouched, so RFC-3 axis
    models (fix(py,ts): support OME-Zarr RFC-3 (arbitrary axis dimensions, names, types, ordering) #611) are unaffected.
  • In-memory NgffImage layouts remain unrestricted; reading existing
    channel-last stores is unchanged.

Behavior change

Converted RGB/vector data is written (c, z, y, x) instead of
(z, y, x, c) — the spec's MUST, and what bioformats2raw writes for RGB.
Chunking keeps c whole, so each output chunk is a locally transposed
input tile; the transpose stays lazy.

Tests

  • New test_canonical_axis_order.py: channel-last input, 4-D default dims,
    canonical passthrough, dim-keyed metadata integrity, unknown-dims
    inertness.
  • Updated the tests that pinned the channel-last order (itkwasm downsample
    orders, bin-shrink fast path, pyramidal RGB TIFF, chunk preservation,
    issue-436 store shapes).
  • Full Python suite: 803 passed, 3 skipped. TS is unaffected (no conversion
    backend produces channel-last there).

Once this and #611 land, the axis-order write gate's warning only concerns
hand-built metadata.

Summary by CodeRabbit

  • New Features
    • Multiscale image outputs now consistently use the OME-Zarr axis order: time, channel, depth, height, width.
    • Channel-last images are automatically reordered to channel-first format while preserving lazy processing.
    • Scale, translation, and chunk metadata now follow the normalized axis order.
  • Bug Fixes
    • Improved consistency for TIFF, RGB, and multi-channel image conversion, including pyramid generation and downsampling.
    • Images with unsupported or unknown axis names remain unchanged.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1becf9c0-eccd-47cf-946c-e94c3c1830e0

📥 Commits

Reviewing files that changed from the base of the PR and between 5bab919 and 0bbbd5b.

📒 Files selected for processing (9)
  • py/ngff_zarr/methods/_support.py
  • py/ngff_zarr/tiff_to_ngff_image.py
  • py/ngff_zarr/to_multiscales.py
  • py/test/test_bin_shrink_map_blocks_fast_path.py
  • py/test/test_canonical_axis_order.py
  • py/test/test_issue_436.py
  • py/test/test_large_image_chunking.py
  • py/test/test_tiff_pyramid_rgb.py
  • py/test/test_to_ngff_zarr_itkwasm.py
🚧 Files skipped from review as they are similar to previous changes (9)
  • py/ngff_zarr/methods/_support.py
  • py/test/test_large_image_chunking.py
  • py/test/test_bin_shrink_map_blocks_fast_path.py
  • py/ngff_zarr/tiff_to_ngff_image.py
  • py/ngff_zarr/to_multiscales.py
  • py/test/test_issue_436.py
  • py/test/test_canonical_axis_order.py
  • py/test/test_tiff_pyramid_rgb.py
  • py/test/test_to_ngff_zarr_itkwasm.py

📝 Walkthrough

Walkthrough

The change normalizes supported image dimensions to OME-Zarr’s t, c, z, y, x order during TIFF import and multiscale generation. It applies lazy transposes, reorders chunk mappings, updates metadata handling, and adjusts tests for channel-leading outputs.

Changes

Canonical Axis Order

Layer / File(s) Summary
Canonical axis-order helper
py/ngff_zarr/methods/_support.py
Adds lazy normalization to t, c, z, y, x order. Unsupported and already-canonical dimensions remain unchanged.
TIFF import normalization
py/ngff_zarr/tiff_to_ngff_image.py
Normalizes the base image and pyramid levels before metadata construction.
Multiscale generation normalization
py/ngff_zarr/to_multiscales.py
Converts inputs to Dask arrays, applies lazy normalization, and reorders chunk mappings.
Validation of normalized outputs
py/test/test_canonical_axis_order.py, py/test/test_bin_shrink_map_blocks_fast_path.py, py/test/test_issue_436.py, py/test/test_large_image_chunking.py, py/test/test_tiff_pyramid_rgb.py, py/test/test_to_ngff_zarr_itkwasm.py
Adds axis-order coverage and updates output shape, metadata, chunking, TIFF, and downsampling assertions.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant InputImage
  participant to_multiscales
  participant canonical_axis_order
  participant DaskArray
  InputImage->>to_multiscales: provide image and chunk mapping
  to_multiscales->>DaskArray: convert image data
  to_multiscales->>canonical_axis_order: normalize dimensions
  canonical_axis_order->>DaskArray: apply lazy transpose
  to_multiscales->>to_multiscales: re-key chunks in normalized order
Loading

Possibly related PRs

  • fideus-labs/ngff-zarr#612: Adds RFC-3 arbitrary-axis support and validation, which is related to this PR’s axis-order handling.

Poem

I hop through t, c, z, y, x in line,
Lazy arrays realign in time.
Channels lead and chunks agree,
TIFF pyramids follow me.
Tests now guard each ordered stride.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: normalizing generated axes to the OME-Zarr specification order.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@vboussot

vboussot commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@vboussot

vboussot commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.


Your included review limit is currently reached under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 53 minutes.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@py/test/test_issue_436.py`:
- Around line 146-153: Update the assertions in test_issue_436 around scale0 and
arr to validate the complete expected (c, y, x) shape, including the leading RGB
channel length from base, while preserving the existing y and x dimension
checks.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 741562cf-c791-434b-8306-23667a354cf3

📥 Commits

Reviewing files that changed from the base of the PR and between 6b315b0 and dbc3a39.

📒 Files selected for processing (9)
  • py/ngff_zarr/methods/_support.py
  • py/ngff_zarr/tiff_to_ngff_image.py
  • py/ngff_zarr/to_multiscales.py
  • py/test/test_bin_shrink_map_blocks_fast_path.py
  • py/test/test_canonical_axis_order.py
  • py/test/test_issue_436.py
  • py/test/test_large_image_chunking.py
  • py/test/test_tiff_pyramid_rgb.py
  • py/test/test_to_ngff_zarr_itkwasm.py

Comment thread py/test/test_issue_436.py Outdated
OME-Zarr orders axes by type: time, then channel, then space. ngff-zarr
itself produced channel-last stores -- the TIFF S (sample) axis, ITK
component images and the 4-D/5-D default dims inference all yield
`(..., c)`, and the downsampling methods preserve the input order.

`to_multiscales` and the pyramidal-TIFF reuse path now normalize the
axis order with a lazy transpose before generating metadata, so every
written store orders time, then channel, then space. In-memory
`NgffImage` layouts are unrestricted as before; dims outside the
canonical set pass through untouched, leaving RFC-3 axis models alone.

Behavior change: converted RGB/vector data is now written `(c, z, y, x)`
instead of `(z, y, x, c)`. Reading existing channel-last stores is
unchanged.
@vboussot
vboussot force-pushed the fix/canonical-axis-order branch from 84d8bdb to 0bbbd5b Compare August 5, 2026 23:52
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@thewtex thewtex left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🥇

@thewtex
thewtex merged commit 9560e29 into fideus-labs:main Aug 6, 2026
50 checks passed
vboussot added a commit to vboussot/ngff-zarr that referenced this pull request Aug 7, 2026
… order

Merging main brought fideus-labs#623, which normalizes generated axes to the spec
order (t, c, z, y, x). The brain_two_components DASK_IMAGE_GAUSSIAN
baseline in the v0.21.0 testing-data archive was generated before that
change, with the component axis last, so the key sets no longer match.
Regenerate that baseline against the merged code and pin the updated
archive.
vboussot added a commit that referenced this pull request Aug 7, 2026
… order

Merging main brought #623, which normalizes generated axes to the spec
order (t, c, z, y, x). The brain_two_components DASK_IMAGE_GAUSSIAN
baseline in the v0.21.0 testing-data archive was generated before that
change, with the component axis last, so the key sets no longer match.
Regenerate that baseline against the merged code and pin the updated
archive.
vboussot added a commit to vboussot/ngff-zarr that referenced this pull request Aug 20, 2026
…r MUST

Axis order is a spec MUST and validate_structural rejects it, so the writer
rejects it too. The gate refuses exactly what the validator refuses.

fideus-labs#623 normalizes generated axes to (t, c, z, y, x), so the Python pipeline
produces a canonical order and a model that reaches the gate out of order
comes from the caller.

That normalization is Python only. Two TypeScript downsampling tests feed
`(z, y, c, x)` and `(t, z, y, c, x)` to check that the channel axis survives
in place, and write at 0.9.dev1, whose axis model allows the order they assert.
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