Skip to content

Fix split sub-volume affine origins for s02 and s03#561

Closed
mhalle wants to merge 4 commits intowasserth:masterfrom
mhalle:fix/split-subvolume-affine-origin
Closed

Fix split sub-volume affine origins for s02 and s03#561
mhalle wants to merge 4 commits intowasserth:masterfrom
mhalle:fix/split-subvolume-affine-origin

Conversation

@mhalle
Copy link
Copy Markdown
Contributor

@mhalle mhalle commented Mar 24, 2026

Summary

  • When a large image is split into three sub-volumes along the z-axis for inference, all three parts were saved with the original image's affine. Parts s02 and s03 start at deeper z-offsets, so their spatial origin was incorrect.
  • The multimodel merge path also overwrote the corrected affines when saving per-part predictions, so the fix needed to be applied end-to-end.
  • Fix: shift the affine origin for s02/s03 by start_index * affine[:3, 2] (the z-column vector), and carry the corrected affines through the merge step.
  • Extracted split_image_into_parts(), save_merged_predictions(), and reassemble_image() as standalone functions so the split/merge logic is testable without nnU-Net or GPU.
  • Added a reusable tests/mock_imports.py context manager for importing modules with heavy dependencies mocked, with full sys.modules cleanup.
  • 18 tests: 12 for the split/merge/reassembly logic (including the specific regression path), 6 for the mock_imports helper.

Test plan

  • test_split_affine.py — verifies affine origins for all parts, voxel-to-world consistency, rotated affines, merged prediction affines (the regression path), roundtrip data integrity, and sys.modules cleanup
  • test_mock_imports.py — verifies mock injection, removal, transitive cleanup, and pre-existing module preservation
  • Run inference with force_split=True on a test image and verify predictions are consistent with non-split inference

🤖 Generated with Claude Code

mhalle and others added 4 commits March 24, 2026 07:46
When splitting a large image into three parts along the z-axis, all
three sub-volumes were saved with the same affine from the original
image. Parts s02 and s03 start at deeper z-offsets, so their affine
origin must be shifted accordingly.

Compute the world-space origin offset for each sub-volume by
multiplying the starting voxel index by the affine's z-column vector,
then adjust the translation component of each part's affine.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous commit corrected the affine origin on the split input
files (s02_0000/s03_0000), but the multimodel merge path still saved
the per-part prediction outputs with img_in_rsp.affine, overwriting
the corrected origins on s02.nii.gz and s03.nii.gz.

Build a part_affines dict and use the correct per-part affine when
saving merged predictions, so sub-volume metadata is consistent from
split through merge.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests verify:
- s01 origin is unchanged, s02/s03 origins are shifted correctly
- voxel-to-world mapping is consistent between parts and original
- correction works with rotated (off-diagonal) affines
- split/reassemble roundtrip preserves data exactly
- affines written to disk match expected values

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract split_image_into_parts(), save_merged_predictions(), and
reassemble_image() from inline code in nnUNet_predict_image() so
they can be tested directly.

Rewrite test_split_affine.py to import and call these production
functions instead of local reimplementations. The key addition is
TestSaveMergedPredictions::test_merged_outputs_have_correct_affines,
which exercises the exact code path that previously regressed
(merged predictions being saved with the wrong affine).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@wasserth
Copy link
Copy Markdown
Owner

Thank you for these PRs. I will check them as soon as possible.

@mhalle
Copy link
Copy Markdown
Contributor Author

mhalle commented Mar 27, 2026 via email

@wasserth
Copy link
Copy Markdown
Owner

Thank you again for these PRs. The PRs are very valuable and I merge all except for this one. This one is not needed since TotalSEgmentator splits and combines the image while ignoring the offset. Therefore, we do not have to correct for it.

@wasserth wasserth closed this Mar 30, 2026
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