Skip to content

ENH: Add ngff-zarr multi-resolution registration example#375

Open
thewtex wants to merge 2 commits into
mainfrom
9566-example-demonstr
Open

ENH: Add ngff-zarr multi-resolution registration example#375
thewtex wants to merge 2 commits into
mainfrom
9566-example-demonstr

Conversation

@thewtex
Copy link
Copy Markdown
Member

@thewtex thewtex commented Mar 9, 2026

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.

@review-notebook-app
Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@thewtex thewtex requested a review from N-Dekker March 9, 2026 12:18
@N-Dekker
Copy link
Copy Markdown
Collaborator

N-Dekker commented Mar 9, 2026

@thewtex Please do git rebase main on this PR! It appears 14 commits behind!

By the way, what does "9566-example-demonstr" mean?

Comment thread examples/ITK_Example23_NgffZarrMultiscaleRegistration.ipynb
Comment thread examples/ITK_Example23_NgffZarrMultiscaleRegistration.ipynb Outdated
@thewtex thewtex force-pushed the 9566-example-demonstr branch from 09b02cd to 56a87dd Compare March 24, 2026 19:07
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.
@thewtex
Copy link
Copy Markdown
Member Author

thewtex commented Mar 24, 2026

@thewtex Please do git rebase main on this PR! It appears 14 commits behind!

@N-Dekker 👍 done!

By the way, what does "9566-example-demonstr" mean?

This is a unique identifier for the git worktree I am using.

@thewtex thewtex force-pushed the 9566-example-demonstr branch from 56a87dd to 95de05e Compare March 24, 2026 19:19
@thewtex thewtex marked this pull request as draft March 24, 2026 19:19
Comment thread examples/ITK_Example23_NgffZarrMultiscaleRegistration.ipynb Outdated
Comment thread examples/ITK_Example23_NgffZarrMultiscaleRegistration.ipynb Outdated
Comment thread examples/ITK_Example23_NgffZarrMultiscaleRegistration.ipynb Outdated
- 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
@thewtex thewtex marked this pull request as ready for review May 18, 2026 18:59
Copy link
Copy Markdown
Collaborator

@N-Dekker N-Dekker left a comment

Choose a reason for hiding this comment

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

Thanks for looking at my initial comments, Matt! Did you push the changes you made to address them?

@N-Dekker
Copy link
Copy Markdown
Collaborator

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).
image

In MeVisLab ("CT_2D_head_fixed.mha" at the left side, "CT_2D_head_moving.mha" at the right):

image

Do you still remember where "CT_2D_head_fixed.mha" and "CT_2D_head_moving.mha" came from?

Comment on lines +276 to +277
"def resample_block(block, block_info=None, transform=None, moving_image=None,\n",
" spacing_itk=None, origin_itk=None):\n",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why all those =None defaults?

block_info=None, transform=None, moving_image=None, spacing_itk=None, origin_itk=None

It appears that resample_block crashes when doing transform=None, for example.

I would suggest to simply remove all those =None defaults.

Comment on lines +91 to +92
"ngff_fixed = itk_image_to_ngff_image(fixed_image)\n",
"ngff_moving = itk_image_to_ngff_image(moving_image)\n",
Copy link
Copy Markdown
Collaborator

@N-Dekker N-Dekker May 20, 2026

Choose a reason for hiding this comment

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

For readability, can you please rename the following variables, by appending "_image"?

  • ngff_fixed to ngff_fixed_image
  • ngff_moving to ngff_moving_image
  • fixed_coarse to fixed_coarse_image
  • moving_coarse to moving_coarse_image
  • fixed_coarse_ngff to fixed_coarse_ngff_image
  • moving_coarse_ngff to moving_coarse_ngff_image

Comment on lines +143 to +147
"ImageType = itk.Image[itk.F, 2]\n",
"if not isinstance(fixed_coarse, ImageType):\n",
" fixed_coarse = itk.cast_image_filter(fixed_coarse, ttype=(type(fixed_coarse), ImageType))\n",
"if not isinstance(moving_coarse, ImageType):\n",
" moving_coarse = itk.cast_image_filter(moving_coarse, ttype=(type(moving_coarse), ImageType))"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why would you do this? (Conditionally casting the images to float) I'm sorry, but I think this part should be removed. First of all, the images returned by ngff_image_to_itk_image are already float images, so itk.cast_image_filter is not called. Secondly, ITKElastix/elastix will already take care of converting the input images to the internal pixel types, automatically. So I think users do not need to "learn" how to do that manually, beforehand.

"\n",
"# Materialize the full-resolution moving image as an itk.Image for resampling.\n",
"# Each block function will sample from this image.\n",
"moving_itk_full = ngff_image_to_itk_image(moving_full, wasm=False)\n",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

moving_itk_full is basically identical to the original moving_image, right? Same size, same pixel values, right? So it's a loss-less round-trip: moving_image ==> itk_image_to_ngff_image ==> to_multiscales ==> images[0] ==> moving_itk_full

Maybe it's worth noting 🤷

Comment on lines +364 to +365
"# Compute the result (triggers parallel execution across chunks)\n",
"resampled_array = resampled_dask.compute()\n",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do I understand correctly that this compute() may internally call resample_block multiple times in parallel, for multiple chunks? And does that actually happen, when running this example?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this a relevant use case for dask map_blocks, or is it more like a toy example? The aim is to speed up the resampling by using multi-threading, right? But then, isn't itk.resample_image_filter itself already multi-threaded?

@thewtex thewtex force-pushed the 9566-example-demonstr branch from 95de05e to 96c4f88 Compare May 20, 2026 20:51
@thewtex
Copy link
Copy Markdown
Member Author

thewtex commented May 20, 2026

Thanks for looking at my initial comments, Matt! Did you push the changes you made to address them?

@N-Dekker oops, I think I pushed them to the wrong remote -- re-pushed!

@thewtex
Copy link
Copy Markdown
Member Author

thewtex commented May 20, 2026

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).

This is intentional, an artificially induced warp. I believe these images come from the ITK Software Guide.

@N-Dekker
Copy link
Copy Markdown
Collaborator

N-Dekker commented May 21, 2026

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:

image

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

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