This file complements CLAUDE.md with a targeted overview of the
TPTBox/registration/ sub-package. It is meant as a working memory for anyone
extending or debugging the registration code paths, plus a running log of the
changes that were made in the "Point-Reg" refactor branch.
Update this file incrementally whenever you touch the registration code – add sections when you introduce new classes, and update the status table when you add or finish tasks.
TPTBox/registration/
├── __init__.py # Aggregates public API. Optional imports guarded.
├── script_ax2sag.py # CLI helper (unchanged).
├── _ridged_points/
│ ├── point_registration.py # SITK closed-form rigid (VersorRigid3D) landmark fit.
│ └── deepali_point_registration.py # NEW: DeepALI equivalent of the above.
├── _ridged_intensity/
│ └── affine_deepali.py # Rigid intensity-based registration used by Rigid_Elements.
├── _deepali/
│ ├── deepali_model.py # General_Registration wrapper around DeepaliPairwiseImageTrainer.
│ ├── deepali_trainer.py # Multi-resolution pyramid training loop.
│ └── spine_rigid_elements_reg.py # Per-vertebra rigid registration + weighted blending.
└── _deformable/
├── deformable_reg.py # Wraps General_Registration for BSpline / SVFFD.
└── multilabel_segmentation.py # Template_Registration + Template_Registration2 (NEW).
| Class | File | Purpose |
|---|---|---|
Point_Registration |
_ridged_points/point_registration.py |
SITK VersorRigid3D landmark-based rigid registration. Serialisable. |
Deepali_Point_Registration |
_ridged_points/deepali_point_registration.py (NEW) |
Kabsch/Horn SVD fit on paired POI landmarks, wrapped as a DeepALI HomogeneousTransform. |
General_Registration |
_deepali/deepali_model.py |
Generic DeepALI pairwise image registration (rigid / affine / SVFFD / …). |
Deformable_Registration |
_deformable/deformable_reg.py |
Thin wrapper enforcing a non-rigid transform on General_Registration. |
Template_Registration |
_deformable/multilabel_segmentation.py |
Two-stage rigid-then-deformable atlas → target alignment (POI-based rigid). |
Template_Registration2 |
_deformable/multilabel_segmentation.py (NEW) |
Same idea, but accepts a Deepali_Point_Registration as pre-registration to skip the SITK resample. |
Rigid_Elements_Registration |
_deepali/spine_rigid_elements_reg.py |
Per-vertebra rigid registration + inverse-distance blending field. |
- Coordinate conventions. POIs and NIIs store voxel coords.
local_to_global(x, itk=True)converts to LPS (ITK / DeepALI) world coords.local_to_global(x)(default) yields RAS (NIfTI) world coords. Never mix conventions. - Rigid transform direction. For resampling (SITK
Resampleand DeepALITransformImage) the forward direction of the transform isfixed → moving. Landmark fits usually estimatemoving → fixed; invert once and stick with fixed→moving thereafter. - DeepALI transform tensor space.
HomogeneousTransform.tensor()returns a(N, D, D+1)matrix expressed in target-grid cube coordinates (Axes.CUBE_CORNERSifalign_corners=True). When the fit is done in LPS world coords, convert viaM = A^-1 @ W @ AwithA = target.transform(CUBE_CORNERS, WORLD). The moving-grid conversion is done bySampleImageat sampling time and must not be baked in – see_build_deepali_transformindeepali_point_registration.py. SampleImage(target, source)vsTransformImage(target, source). The existing_warp_imageindeepali_model.pyusessource=target_grid, which silently requires the moving image to already live on the fixed grid. This is where the "same-space" assumption ofGeneral_Registrationcomes from.
Status legend: 🟡 in progress · ✅ done · ⬜ pending
| # | Task | Status |
|---|---|---|
| 1 | New Deepali_Point_Registration (closed-form rigid via DeepALI) |
✅ |
| 2 | General_Registration accepts fixed/moving on different grids (new same_space=True flag) |
✅ |
| 3 | General_Registration accepts POI / POI_Global landmark sets and matches shared IDs automatically |
✅ |
| 4 | New Template_Registration2 that consumes a Deepali_Point_Registration as pre-registration |
✅ |
| 5 | Unit tests + speed / memory sanity checks | ✅ |
| 6 | This file + CLAUDE.md cross-reference |
✅ |
| 7 | Speed benchmark Deepali vs SimpleITK on CPU (recorded below) | ✅ |
-
Conda env:
/home/robert/anaconda3/envs/py3.12/bin/python– DeepALI (hf-deepali) is installed there. -
Sample data:
TPTBox/tests/sample_ct/andTPTBox/tests/sample_mri/– tiny NIfTIs + segmentations, checked into the repo.tutorials/tutorial_data_processing/– DICOM + PixelPandemonium MR pair, downloaded by the tutorial.
-
Existing unit tests live in
unit_tests/. New registration tests should follow the same pattern (no GPU-only paths in default tests; guard CUDA imports). -
test_deformable_stage_improves_over_rigid_onlyneedselasticdeform. Known issue: the PyPI wheel is built against NumPy 1.x and fails at import under NumPy 2.x. Install the source tarball instead so it recompiles locally:pip install https://github.com/gvtulder/elasticdeform/archive/refs/tags/v0.5.1.tar.gzConfirmed working with NumPy 2.4.1 / SciPy 1.17.0. If
elasticdeformis unavailable the test is skipped cleanly (_HAS_ELASTIC = False).
Two benchmarks were run in py3.12:
Tiny CT (73×47×73, 3 landmarks):
| step | SimpleITK | Deepali | notes |
|---|---|---|---|
| fit | ~7.3 ms | ~1.4 ms | Kabsch SVD is trivially cheap |
| warp | ~9.6 ms | ~19.7 ms | SITK BSplineResampler is fast on this size |
| accuracy (mean-abs voxel err on identity round-trip) | 54.2 HU | 0.003 HU | SITK BSpline shows heavy ringing on CT |
| peak mem | – | 0.5 MiB (tracemalloc) |
Tutorial MR volume (270×220×72, 6 landmarks):
| step | SimpleITK | Deepali | notes |
|---|---|---|---|
| fit | ~87 ms | ~1.4 ms | ~60× faster – closed-form SVD stays constant with landmark count |
| warp | ~131 ms | ~243 ms | SITK still edges out on CPU for pure resampling |
| accuracy | 6.53 | 0.0002 | Deepali linear sampler is drastically more accurate |
| peak mem | 32.6 MiB | 32.6 MiB | Same order of magnitude |
Short answer to the user's mid-run question: Deepali is meaningfully more accurate and its fit is ~5–60× faster on CPU, but SITK is still ~1.5–2× faster on the actual image warp step on CPU. Deepali becomes clearly faster once a GPU is used or once the warp is followed by more DeepALI work (no extra CPU→GPU copies). See unit_tests/test_registration_deepali.py::TestSpeedAndMemory – it prints a summary line every run.
hf-deepali (and its prerequisite PyTorch) is an optional install.
TPTBox.registration/__init__.py therefore imports each deepali-backed entry
point inside its own try/except ImportError. On failure the name is replaced
by a small class/function stub built by _make_missing_deepali_stub /
_make_missing_deepali_func – instantiating or calling the stub raises
ImportError: `<Name>` requires the optional dependency `hf-deepali`
(which in turn requires PyTorch). Install both with:
pip install torch hf-deepali
so users get an actionable message instead of a bare NameError. The SITK
Point_Registration path stays fully usable when deepali is absent. See the
TestOptionalDeepaliStubs test for a regression guard.
- The current
_warp_imageusesTransformImage(target=target_grid, source=target_grid). Passing a source image that lives on the moving grid is only safe when moving grid == fixed grid. Task #2 addresses this properly by threading the moving grid through whensame_space=False. - When adding DeepALI landmark loss (
LandmarkPointDistance), the trainer expects target/source landmarks as(N, M, D)tensors inAxes.CUBE_CORNERSon the transform's grid. The wrapper inGeneral_Registrationshould convert from POI voxel coords to that space. Template_Registrationmutates its inputs by resampling the atlas after each SITK point-reg attempt. Template_Registration2 avoids the resample by keeping the transform composable with the downstreamDeformable_Registration.