feat(registration): add displacement-field sampling and inversion for B-spline transforms#235
Open
sdiebolt wants to merge 12 commits into
Open
feat(registration): add displacement-field sampling and inversion for B-spline transforms#235sdiebolt wants to merge 12 commits into
sdiebolt wants to merge 12 commits into
Conversation
… B-spline transforms Adds `bspline_to_displacement_field` and `invert_displacement_field` to confusius.registration.bspline, letting a B-spline (or composite affine + B-spline) transform be sampled into a dense displacement field and inverted via SimpleITK's InvertDisplacementFieldImageFilter. resample_volume and resample_like now accept displacement fields directly alongside affines and B-spline DataArrays, so a saved transform's inverse can be applied without a closed-form inverse -- needed for future napari "apply inverse transform" support. Also fixes two pre-existing bugs surfaced while validating on real (anisotropic, singleton-axis) fUSI data: - sitk_bspline_to_dataarray/_dataarray_to_sitk_bspline assumed SimpleITK reverses axis order relative to the DataArray; the rest of the module never reverses, so this silently swapped control-point grid geometry between axes on anisotropic images (invisible on isotropic test fixtures). - InvertDisplacementFieldImageFilter requires a real spatial neighborhood along every axis and silently returns an all-zero field when one has size 1 (fUSI data is routinely stored as a single 2D slice, e.g. (1, y, x)). invert_displacement_field now reuses expand_thin_dims (promoted from volume.py to the shared registration/_utils.py) to pad and crop degenerate axes, the same trick register_volume already relies on for its own thin-dimension images. Closes #233.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Contributor
|
📖 Doc preview: https://confusius.tools/pr-preview/pr-235/ |
Replace the module-level DISPLACEMENT_FIELD_INVERSION_ITERATIONS/MAX_ERROR constants (only ever used as default values) with plain literal defaults on invert_displacement_field's max_iterations/max_error_tolerance parameters. Also add sitk_threads to bspline_to_displacement_field and invert_displacement_field, matching the convention already used by resample_volume/resample_like -- both underlying SimpleITK filters (TransformToDisplacementFieldFilter, InvertDisplacementFieldImageFilter) support per-call thread control via set_sitk_thread_count.
assert_allclose(inverse_field.values[0], 0.0) used the default atol=0, so a tiny platform-dependent floating-point residual (~1e-16 on macOS CI, exact 0.0 on Linux/Windows) failed the relative-tolerance check against a true zero. Use atol=1e-9, well above any such noise and well below any real displacement signal.
…rings Use commas or true em-dashes (no surrounding spaces) instead of "--" as a prose separator. Also updates the degenerate-axis test docstring, which still described the earlier squeeze/reinsert approach rather than the current expand/crop one.
Extends the same-subject registration example with a B-spline step initialized from the rigid transform, using the same mesh_size=(6, 6, 6) that demo_bspline_inversion.py uses. Rigid registration parameters are left untouched. Tested end-to-end against the actual dataset: rigid alone reaches a final metric around -0.81, and the B-spline refinement typically improves it to around -0.85 to -0.89 depending on run-to-run optimizer variance. Also points readers at bspline_to_displacement_field/ invert_displacement_field for applying the resulting B-spline transform (or its inverse), since unlike the rigid transform it has no closed-form inverse.
Exercises _validate_displacement_field_dataarray's two raise branches through invert_displacement_field (its public caller), not by importing the private validator directly.
…ion example update
Member
Author
|
@FelipeCybis Ready to review! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
bspline_to_displacement_fieldandinvert_displacement_fieldtoconfusius.registration.bspline: sample a B-spline (or composite affine + B-spline) transform into a dense displacement field on an explicit grid, then invert it via SimpleITK'sInvertDisplacementFieldImageFilter.resample_volume/resample_likenow accept displacement fields directly alongside affines and B-spline DataArrays, so a saved registration transform's inverse can be applied without a closed-form inverse — the missing piece for a future napari "apply inverse transform" hook.sitk_bspline_to_dataarray/_dataarray_to_sitk_bsplineassumed SimpleITK reverses axis order relative to the DataArray; the rest of the module never reverses, so this silently swapped control-point grid geometry between axes on anisotropic images (invisible on isotropic test fixtures). Pre-existing, shipped in released alpha versions — see B-spline control-point grid geometry is swapped between axes on anisotropic images #236.InvertDisplacementFieldImageFiltersilently returns an all-zero field when any spatial axis has size 1 (fUSI data is routinely a single 2D slice, e.g.(1, y, x)).invert_displacement_fieldnow reusesexpand_thin_dims(promoted fromvolume.pyto the sharedregistration/_utils.py) to pad and crop degenerate axes — the same trickregister_volumealready relies on for its own thin-dimension images. New code introduced in this PR, not a prior regression.Closes #229. Closes #233. Closes #236.