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
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.
56a87dd to
95de05e
Compare
- 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
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. |
fc32ade to
6608bad
Compare
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.
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"...? |
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.
|
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? |
…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.
|
@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:
Uses itk_image_to_ngff_image and ngff_image_to_itk_image to bridge between ITK and ngff-zarr data representations.