ENH: Add ngff-zarr multi-resolution registration example - #375
Conversation
|
Check out this pull request on See visual diffs & provide feedback on Jupyter Notebooks. Powered by ReviewNB |
|
@thewtex Please do By the way, what does "9566-example-demonstr" mean? |
09b02cd to
56a87dd
Compare
56a87dd to
95de05e
Compare
N-Dekker
left a comment
There was a problem hiding this comment.
Thanks for looking at my initial comments, Matt! Did you push the changes you made to address them?
|
Maybe beyond the scope of this PR, but "CT_2D_head_fixed.mha" looks a bit warped. Especially when looking at the border of the skull. I see, you introduced the image by 56caa00 (back in 2020). In MeVisLab ("CT_2D_head_fixed.mha" at the left side, "CT_2D_head_moving.mha" at the right):
Do you still remember where "CT_2D_head_fixed.mha" and "CT_2D_head_moving.mha" came from? |
95de05e to
96c4f88
Compare
@N-Dekker oops, I think I pushed them to the wrong remote -- re-pushed! |
This is intentional, an artificially induced warp. I believe these images come from the ITK Software Guide. |
Ah, thanks, I see similar images now, in InsightSoftwareGuide-Book2-5.4.6.pdf, although not warped:
Still, I would expect the warping to take place on the moving image, during registration (bspline), and no warping of the fixed image 🤷 Update, I see now, the T1 image (before warping) is at https://github.com/InsightSoftwareConsortium/ITKSoftwareGuide/blob/7510e24a4dd8e98b6ee582c2468961f69ca1ec4b/SoftwareGuide/Art/BrainT1Slice.jpg |
|
@thewtex Thanks for addressing my initial comments, Matt. Feel free to ignore my comment about the artificially warped image. It appears that Marius (@mstaring) actually likes the warped image 😃 I think the other remaining comments that I had are still relevant. Please consider them, when you have the time. |
|
I updated the example so the full-resolution resampling is out-of-core: the new |
|
@vboussot looking good 🔢 To make the functionality more illustrative, can we draw colored bounding boxes in images with matplotlib for:
? |
1729162 to
fc32ade
Compare
|
Hi @niels ! All your comments are addressed, squashed into a single commit: no more =None defaults, variables renamed to *_image, and the float casts removed. The example now also demonstrates out-of-core resampling: with ngff_zarr.itk_transform_resample_bounding_box, each block reads only the moving-image region its resample needs that's what Thanks for the careful review! |
|
@vboussot thanks for the updates 💯 Can we please run the notebook, save the cell outputs? That way people can see the figures in the rendered notebooks on GitHub with a web browser. |
6608bad to
2caf80b
Compare
|
View / edit / reply to this conversation on ReviewNB N-Dekker commented on 2026-08-14T14:12:59Z Line #2. itk_composite_transform = itk.CompositeTransform[itk.D, 2].cast( Please do not do Update: I see now, removing the |
|
View / edit / reply to this conversation on ReviewNB thewtex commented on 2026-08-14T14:38:32Z Since they are different modality, I think we should remove the difference here. |
|
View / edit / reply to this conversation on ReviewNB N-Dekker commented on 2026-08-14T16:28:20Z Line #3. block_info=None, Sorry for nitpicking, but this |
|
View / edit / reply to this conversation on ReviewNB N-Dekker commented on 2026-08-14T16:28:20Z Line #4. *, The use of an asterisk (
So that only the first parameter would be an ordinary "positional argument"...? |
|
View / edit / reply to this conversation on ReviewNB N-Dekker commented on 2026-08-21T14:13:19Z Line #2. # ConvertToItkTransform hands back the itk.Transform[itk.D, 2, 2] base, so the Thank you for clarifying why the cast is necessary! In the future, I hope that such a cast is no longer necessary, as I just made a pull request to add a new member function, ElastixRegistrationMethod::ConvertCompositionToItkTransform, which returns a CompositeTransform. Please check: SuperElastix/elastix#1464 Anyway, for now I agree that the cast should still be there! |
|
View / edit / reply to this conversation on ReviewNB N-Dekker commented on 2026-08-24T12:03:16Z Line #4. scale_factors = [2, 4] For the described registration use case, only the coarsest resolution level is used, so I guess a single scale factor (
|
|
View / edit / reply to this conversation on ReviewNB N-Dekker commented on 2026-08-24T13:28:22Z Line #30. array_location = block_info[0]["array-location"] I find this array-location stuff a bit hard to understand. Does each block_info[i] have a different "array-location"? A previous revision had some explanation, saying:
Would this comment still be applicable? |
|
@N-Dekker @thewtex 962ce96 addresses the remaining threads; the notebook is re-executed with saved outputs.
|
Add Example 23 demonstrating a workflow that combines ngff-zarr multi-resolution image pyramids with ITKElastix registration: - Convert ITK images to ngff-zarr Multiscales via to_multiscales - Register at a coarse resolution (rigid + affine + bspline) - Convert Elastix results to itk.CompositeTransform - Apply the transform at full resolution in parallel using dask.array.map_blocks with itk.resample_image_filter Uses itk_image_to_ngff_image and ngff_image_to_itk_image to bridge between ITK and ngff-zarr data representations.
- Reword 'required by Elastix' to 'recommended for Elastix, by default' in markdown and code comment for the float pixel type discussion - Clarify that 'native itk.Image' is in contrast to 'itkwasm.image.Image' when calling ngff_image_to_itk_image with wasm=False - Rename 'resampled_dask' to 'resampled_dask_array' for clarity
Each map_blocks call computes its block's moving-image region with ngff_zarr.itk_transform_resample_bounding_box, materializes only that region, and resamples it into the block's reference grid. The full moving image is never loaded in memory. Figures visualize the per-block regions.
Make block_info keyword-only. dask then always supplies it, so the metadata probe call and the "if block_info is None" guard it required both go away: measured one probe call before the change, none after. Drop the difference panel from the final figure. The fixed and moving images are different modalities, so their subtraction is not meaningful. Record why the composite transform is cast: ConvertToItkTransform returns the itk.Transform[itk.D, 2, 2] base, which does not expose the composite interface. Cell outputs regenerated by running the notebook.
…t in Example 23 Comment what block_info[0]["array-location"] holds in resample_block, note that elastix's ConvertCompositionToItkTransform (SuperElastix/elastix#1464) removes the need for the cast once ITKElastix ships it, and state that a single scale factor is enough for registration alone. Re-executed with saved outputs.
962ce96 to
bfbf27d
Compare
The reusable workflow runs test-linux-notebooks on the first entry of python3-minor-versions. ngff-zarr 0.44 raised its floor to Python 3.11, so on 3.10 the resolver silently falls back to 0.43, and the notebook is tested against an interface the reader no longer gets.
ngff-zarr 0.44 renamed itk_transform_resample_bounding_box to resample_bounding_box and removed the old name rather than keeping an alias, so the notebook's import fails on any environment that resolves 0.44 or later. The floor moves with it; 0.44 also raised the Python requirement to 3.11.
ngff-zarr 0.44 added resample, which computes each block's region with resample_bounding_box and builds the blocks as tasks of one Dask graph referencing the moving chunks, so a chunk several blocks read is decoded once. The hand-written loop stays as the explanation of what happens per block; the new cell shows the call a pipeline would make, and that it returns the same pixels.
bfbf27d to
80d0e4f
Compare



Add Example 23 demonstrating a workflow that combines ngff-zarr multi-resolution image pyramids with ITKElastix registration:
Uses itk_image_to_ngff_image and ngff_image_to_itk_image to bridge between ITK and ngff-zarr data representations.