diff --git a/README.md b/README.md
index 279f5b6..e02f9a8 100644
--- a/README.md
+++ b/README.md
@@ -44,6 +44,8 @@ data/
pseudo/roundK/ in-house pseudo-labels (rows.jsonl, masks, summary.json)
pseudo_nnu/roundK/ nnU-Net pseudo-labels (rows.jsonl, masks, summary.json)
pseudo_monai/roundK/ MONAI pseudo-labels (rows.jsonl, masks, summary.json)
+ impact_models/ IMPACT TorchScript feature-extractor cache (registration
+ `impact` pipeline, see REGISTRATION_BENCHMARK.md)
src/ U-Net, dataset, losses, training/eval helpers
scripts/ Pipeline A (in-house 3D U-Net)
scripts_nnu/ Pipeline B (nnU-Net)
diff --git a/REGISTRATION_BENCHMARK.md b/REGISTRATION_BENCHMARK.md
index ef28021..67a74da 100644
--- a/REGISTRATION_BENCHMARK.md
+++ b/REGISTRATION_BENCHMARK.md
@@ -1,6 +1,6 @@
# Registration Pipeline Benchmark — CT → T1c (M6)
-Benchmarks **four rigid, cross-modality registration pipelines** that align a
+Benchmarks **five rigid, cross-modality registration pipelines** that align a
head-and-neck CT to a tightly-cropped T1c MRI so the GTV can be warped onto the
T1c (MR) native grid. This is the registration step of
`scripts/02_build_m6_dataset.py`. The pairs are hard: different modality (CT
@@ -34,8 +34,9 @@ handling — not on who finds the ~30–60 mm gross offset first.
|---|----------|---------|--------|-----------|--------------------|
| 1 | `elastix` | ITK-Elastix 0.25.4 (pip `itk` + `itk-elastix`, elastix 5.3.1) | Mattes MI (10 bins) | `EulerTransform` rigid (5.x replacement for `Rigid3DTransform`), ASGD, 3 resolutions | ✅ ran (main env, in-process python API — no CLI) |
| 2 | `simpleitk` | SimpleITK 2.5.6 | Mattes MI (32 bins) | `Euler3D` rigid, pyramid `[4,2,1]` | ✅ ran |
-| 3 | `antspyx` | ANTsPy / ANTs 0.6.3 | Mattes MI (multi-res) | ANTs `Rigid` | ✅ ran (isolated py3.12 env) |
+| 3 | `antspyx` | ANTsPy / ANTs 0.6.3 | Mattes MI (multi-res) | ANTs `Rigid` | ✅ ran (main env; stochastic MI subsampling) |
| 4 | `deepreg` | Oxford DeepReg (CycleGAN) | CycleGAN + reg. loss (pretrained) | 3D displacement field | **not installed** (see below) |
+| 5 | `impact` | Docker `vboussot/elastix_impact` (Elastix 5.2.0 + IMPACT) | **IMPACT semantic** (TotalSegmentator `TS/M730.pt` features, L2, Jacobian/GPU) | `EulerTransform` rigid, ASGD, 4 scales (no image resampling) | ✅ ran (main env, Docker + GPU) |
`simpleitk` has two budgets: `--simpleitk-mode benchmark` (single pyramid,
centered start — the default, comparable to the other tools) and
@@ -85,75 +86,97 @@ Production QA, crop-independent:
| pipeline | toolkit | registered | accepted | gap mid (med/mean) | gap std (med/mean) | s/reg (med) | tv-in-CT | MI (med/mean, nats) |
|---|---|---|---|---|---|---|---|---|
| **simpleitk** | SimpleITK 2.5.6 | 9 | **4/10 (0.40)** | 12.6 / 16.1 | 15.4 / 16.0 | 8.6 | 0.93 | **0.643 / 0.630** |
-| **antspyx** | ANTsPy 0.6.3 | 9 | **1/10 (0.10)** | 14.2 / 17.3 | 24.8 / 22.4 | 12.7 | 0.93 | **0.600 / 0.523** |
+| **antspyx** | ANTsPy 0.6.3 | 9 | **0/10 (0.00)** | 14.9 / 16.5 | 18.4 / 22.7 | 14.5 | 0.93 | **0.598 / 0.529** |
| **elastix** | ITK-Elastix 0.25.4 | 9 | **5/10 (0.50)** | 15.3 / 16.1 | 9.9 / 13.8 | 3.2 | 0.93 | **0.562 / 0.549** |
+| **impact** | Docker Elastix 5.2 + IMPACT | 9 | **3/10 (0.30)** | 12.4 / 19.0 | 15.1 / 16.7 | **562.3** | 0.93 | **0.475 / 0.448** |
| deepreg | — | — | — | — | — | — | — | — |
+*(antspyx and impact are stochastic (random metric/patch subsampling), so their
+accepted counts are one draw each — see Interpretation. impact's s/reg is ~9.4
+min/case on GPU, orders of magnitude above the intensity-metric tools.)*
+
Per-case (mid / std, `OK` = accepted):
-| case | simpleitk | antspyx | elastix |
-|---|---|---|---|
-| m6_22GD5VL2_20230724 | incomplete-CT | incomplete-CT | incomplete-CT |
-| m6_22IRRKU6_20240115 | 25.7 / 29.3 | 26.3 / 29.2 | 26.4 / 30.3 |
-| m6_23XIMO4M_20230222 | 22.2 / 23.9 | 23.8 / 25.0 | 21.1 / 23.3 |
-| m6_24V62QQM_20230223 | 10.7 / 16.9 | 9.4 / 18.2 | 10.6 / 8.2 **OK** |
-| m6_24V62QQM_20231017 | 11.5 / 7.3 **OK** | 12.5 / 12.8 | 11.0 / 6.1 **OK** |
-| m6_24V62QQM_20240820 | 11.6 / 9.6 **OK** | 14.8 / 11.7 **OK** | 15.3 / 14.0 |
-| m6_26TYQI3C_20221027 | 12.2 / 3.7 **OK** | 6.1 / 37.2 | 11.4 / 3.2 **OK** |
-| m6_2BBN5XA4_20250411 | 18.6 / 8.0 **OK** | 14.3 / 12.7 | 18.1 / 9.9 **OK** |
-| m6_2C4TKI6H_20240506 | 12.6 / 15.4 | 13.6 / 14.7 | 12.0 / 6.8 **OK** |
-| m6_2CDNN7BH_20240102 | 19.4 / 30.2 | 18.2 / 33.6 | 18.9 / 22.7 |
+| case | simpleitk | antspyx | elastix | impact |
+|---|---|---|---|---|
+| m6_22GD5VL2_20230724 | incomplete-CT | incomplete-CT | incomplete-CT | incomplete-CT |
+| m6_22IRRKU6_20240115 | 25.7 / 29.3 | 24.4 / 27.7 | 26.4 / 30.3 | **8.4 / 12.6** |
+| m6_23XIMO4M_20230222 | 22.2 / 23.9 | 23.4 / 24.8 | 21.1 / 23.3 | 21.4 / 24.1 |
+| m6_24V62QQM_20230223 | 10.7 / 16.9 | 9.5 / 18.4 | 10.6 / 8.2 **OK** | 10.0 / 8.3 **OK** |
+| m6_24V62QQM_20231017 | 11.5 / 7.3 **OK** | 14.9 / 15.5 | 11.0 / 6.1 **OK** | 11.1 / 8.8 **OK** |
+| m6_24V62QQM_20240820 | 11.6 / 9.6 **OK** | 14.3 / 17.5 | 15.3 / 14.0 | 54.4 / 15.1 |
+| m6_26TYQI3C_20221027 | 12.2 / 3.7 **OK** | 17.7 / 37.6 | 11.4 / 3.2 **OK** | 23.9 / 22.6 |
+| m6_2BBN5XA4_20250411 | 18.6 / 8.0 **OK** | 14.3 / 12.7 | 18.1 / 9.9 **OK** | 10.1 / 5.9 **OK** |
+| m6_2C4TKI6H_20240506 | 12.6 / 15.4 | 13.1 / 14.3 | 12.0 / 6.8 **OK** | 12.4 / 22.3 |
+| m6_2CDNN7BH_20240102 | 19.4 / 30.2 | 17.2 / 35.4 | 18.9 / 22.7 | 19.6 / 30.3 |
### Interpretation
-- **Translation is recovered well by all three** (mid gaps mostly 6–20 mm,
+- **Translation is recovered well by all four** (mid gaps mostly 6–20 mm,
within the `≤ 20 mm` band). `tv-in-CT` is identical (~0.93): the warped GTV
lands inside the CT head for every pipeline, so the target is in the right
place.
- **The binding failure is rotation** — `std > 12 mm` on the repositioned cases
- (the ~30° inter-session tilt produces MI local minima that a single centered
+ (the ~30° inter-session tilt produces local minima that a single centered
start can lock onto). This is exactly why production `02_build_m6_dataset.py`
uses a **multi-start tilt grid**.
-- **Elastix is the most accurate of the three standard single-init configs**
- (5/10 accepted, lowest median std 9.9 vs SimpleITK 15.4 and ANTs 24.8) and
+- **Elastix is the most accurate of the standard single-init configs**
+ (5/10 accepted, lowest median std 9.9 vs SimpleITK 15.4 and ANTs 18.4) and
the fastest (3.2 s/case median wall, ~1.8 s pure registration vs 8.6 s
- SimpleITK / 12.7 s ANTs): ASGD's adaptive step sizes climb out of small
+ SimpleITK / 14.5 s ANTs): ASGD's adaptive step sizes climb out of small
misalignments better than RSGD/ANTs on these boxes. It accepts `m6_24V62QQM_20230223`
(std 8.2) and `m6_2C4TKI6H` (std 6.8), which the other two tools fail on
(std 16.9 / 15.4), and misses `m6_24V62QQM_20240820` (std 14.0) that both
others accept — different optimizers settle in different local minima on the
hard tilted cases.
-- **ANTs' Rigid is the weakest** (1/10 accepted): it frequently locks onto the
- tilted local minimum. Note `m6_26TYQI3C`: ANTs nailed the position (mid 6.1)
- but missed the orientation (std 37.2) — a textbook tilted local minimum.
+- **ANTs' Rigid is the weakest of the intensity-metric tools** (0/10 in this
+ draw; it is stochastic, so a prior draw gave 1/10): it frequently locks onto
+ the tilted local minimum. Note `m6_26TYQI3C`: ANTs nailed the position (mid
+ 17.7) but missed the orientation (std 37.6) — a textbook tilted local minimum.
+- **IMPACT (semantic metric) is the standout on the hardest repositioned case**
+ and the least reliable overall. On `m6_22IRRKU6` (the ~30° tilt every
+ intensity tool fails, std 28–30 mm) it gets **mid 8.4 / std 12.6** — the best
+ translation of any pipeline and within 0.6 mm of the acceptance threshold —
+ because matching *anatomical semantics* (TotalSegmentator features) is far
+ more discriminating for gross pose than raw cross-modality intensity. But the
+ semantic landscape is rugged: single-init ASGD lands in bad local minima on
+ 4 of 9 cases (std 15–30 mm), and one (`m6_24V62QQM_20240820`) even drifts the
+ translation (mid 54 mm). Net 3/10 accepted — between ANTs and SimpleITK — at
+ ~9.4 min/case, two orders of magnitude slower than the intensity tools.
- **Speed** (median wall, 4 cases in parallel, 24 threads): Elastix ~3.2 s
(warm workers; a worker's first case pays a ~10 s warm-up), SimpleITK ~8.6 s,
- ANTs ~12.7 s (includes the pre-shift resample + its affine-MI init stages).
- All CPU-bound.
+ ANTs ~14.5 s (CPU-bound), **IMPACT ~562 s** (GPU-bound: every ASGD iteration
+ runs the TotalSegmentator extractor on the sampled patches with backprop).
- **Mattes MI (warped-CT ~ MR) is an independent, intensity-based confirmation**:
- medians 0.643 (SimpleITK) / 0.600 (ANTs) / 0.562 (Elastix), and it diverges
- sharply on the tilted cases — ANTs `m6_26TYQI3C` drops to 0.094 nats
- (rotation ~37 mm off) while SimpleITK holds 0.670. A high MI means the CT
- skull/tissue intensities fall on the matching MR structures after the inverse
- transform.
+ medians 0.643 (SimpleITK) / 0.598 (ANTs) / 0.562 (Elastix) / 0.475 (IMPACT),
+ and it diverges sharply on the tilted cases — ANTs `m6_26TYQI3C` drops to
+ ~0.1 nats (rotation ~38 mm off) while SimpleITK holds 0.670. A high MI means
+ the CT skull/tissue intensities fall on the matching MR structures after the
+ inverse transform. IMPACT's lower median MI is consistent with its less
+ reliable single-init convergence.
- **Stochasticity**: ANTs' Mattes-MI subsamples the image at random, so its
per-case numbers carry run-to-run variance (this table is one draw);
SimpleITK's MI (and registration) is deterministic at fixed thread count
- (RSGD wall time is thread-sensitive, convergence is not); Elastix's ASGD is
- deterministic given its fixed seed.
+ (RSGD wall time is thread-sensitive, convergence is not); the in-process
+ Elastix ASGD is deterministic given its fixed seed. **IMPACT is the most
+ stochastic**: its Jacobian mode draws random patches every iteration, so the
+ final pose — and even *which* cases get accepted — changes run to run (two
+ independent 10-case draws both accepted 3/10 but on different cases). Treat
+ its per-case numbers as one sample from a distribution.
## Reproducing
```bash
-# main env — SimpleITK + Elastix (itk-elastix is pip-installed here;
-# antspyx/deepreg report unavailable and are recorded as such)
+# main env — SimpleITK + Elastix + ANTsPy (all pip-installed here); deepreg
+# reports unavailable and is recorded as such
source /opt/conda/etc/profile.d/conda.sh && conda activate longitudinal-313
python scripts/bench_registration.py --n-cases 10 \
--workers 4 --threads 24 --simpleitk-mode benchmark
-# isolated py3.12 env with antspyx — ANTs (+ optional SimpleITK cross-check)
-/home/xfr/.conda/envs/ants312/bin/python scripts/bench_registration.py \
- --pipelines antspyx --n-cases 10 --workers 4 --threads 24
+# IMPACT (needs Docker + the pulled image + a model; see the Impact section
+# below for the one-time setup)
+python scripts/bench_registration.py --pipelines impact --n-cases 10 \
+ --workers 4 --threads 24
# combine the per-pipeline JSONs accumulated across envs into one table + plot
python scripts/bench_registration.py --merge
@@ -162,8 +185,9 @@ python scripts/bench_registration.py --merge
Other flags: `--sample random --seed 0` (reproducible random 10),
`--keys k1,k2,...` (explicit cases), `--n-cases N`, `--workers W`,
`--threads T`, `--simpleitk-mode {benchmark,production}`,
-`--deepreg-ckpt <.ckpt>` (DeepReg), `--out-dir
`,
-`--qa-images` / `--qa-out ` (visual QA, below).
+`--deepreg-ckpt <.ckpt>` (DeepReg), `--impact-model `,
+`--impact-gpu `, `--impact-iters N`, `--impact-samples N`,
+`--out-dir `, `--qa-images` / `--qa-out ` (visual QA, below).
## Visual QA images (checkerboard + color overlay)
@@ -189,10 +213,11 @@ The title carries the pipeline, case key, Mattes MI, and the skull-ring gap
visuals agree. Generate for the available pipelines with:
```bash
-/home/xfr/.conda/envs/longitudinal-313/bin/python scripts/bench_registration.py \
- --pipelines simpleitk elastix --n-cases 10 --qa-images
-/home/xfr/.conda/envs/ants312/bin/python scripts/bench_registration.py \
- --pipelines antspyx --n-cases 10 --qa-images
+source /opt/conda/etc/profile.d/conda.sh && conda activate longitudinal-313
+python scripts/bench_registration.py \
+ --pipelines simpleitk elastix antspyx --n-cases 10 --qa-images
+python scripts/bench_registration.py \
+ --pipelines impact --n-cases 10 --qa-images
```
(9 images each; the incomplete-CT case is skipped.)
@@ -213,6 +238,61 @@ python scripts/bench_registration.py --pipelines deepreg --n-cases 10 \
The adapter runs `deepreg test` to predict a voxel displacement field, converts
it to a SimpleITK `DisplacementFieldTransform`, and scores it like the others.
+## IMPACT pipeline (Docker) — one-time setup
+
+The `impact` pipeline runs the [`vboussot/elastix_impact`](https://hub.docker.com/r/vboussot/elastix_impact)
+image: **Elastix 5.2.0** with the [IMPACT](https://arxiv.org/abs/2503.24121)
+*semantic* similarity metric compiled in as a native Elastix metric, plus a
+TorchScript **TotalSegmentator** feature extractor. It is the only pipeline that
+is GPU-based and the only one that is Docker-based.
+
+Prerequisites (check with `RP.availability()`; a missing piece raises
+`PipelineUnavailable` with the exact command):
+
+```bash
+# 1. Docker (with NVIDIA container toolkit so --gpus works)
+docker --version
+
+# 2. Pull the image (~23.5 GB, one-time)
+docker pull vboussot/elastix_impact
+
+# 3. Fetch the TorchScript model (~123 MB, one-time) into the repo's model
+# cache (data/impact_models/). M730.pt is the recommended TotalSegmentator.
+mkdir -p /mnt/t24/Public/xfr/longitudinal/impact_models/TS
+curl -L -o /mnt/t24/Public/xfr/longitudinal/impact_models/TS/M730.pt \
+ https://huggingface.co/VBoussot/impact-torchscript-models/resolve/main/TS/M730.pt
+```
+
+Other models (M258/M291/M850, MIND, SAM2.1, …) are selectable with
+`--impact-model `; see the
+[impact-torchscript-models](https://huggingface.co/VBoussot/impact-torchscript-models)
+repo for the full list and FOV/layer tables.
+
+### How the adapter drives the image
+
+The image's `CMD.sh` only runs `elastix -f -m -p -out` (no `-t0`), so the
+adapter calls the in-image binary directly (`/lib/elastix-install/bin/elastix`)
+to add the shared centered start:
+
+1. **Head box** — resamples fixed=T1c and moving=CT into the same CT-space head
+ box (physical frame) the other pipelines use, so MI/IMPACT is computed on
+ head tissue, not neck/air.
+2. **Shared init via `-t0`** — the `centroid_offset` translation is written to a
+ `TranslationTransform` parameter file and passed with `-t0`; with
+ `HowToCombineTransforms "Compose"` the final transform is
+ `T = T_euler(T_init(p))`. (Elastix 5.x removed in-file multi-`Transform`
+ chains and `UseScalingFactors`, so the initial transform must be a separate
+ `-t0` file.)
+3. **Parameter map** — rigid `EulerTransform` (6-DOF), `Impact` metric in
+ **Jacobian** mode (differentiable, per the recommended config), 4 semantic
+ scales `ImpactVoxelSize` 6/3/1.5/1 mm (scaled to the box's base spacing) with
+ **no image resampling** (all-1 pyramid schedules, as the IMPACT docs require),
+ ASGD, `Random` sampler. GPU auto-spread across the host GPUs by pid
+ (`--impact-gpu -1` forces CPU).
+4. **Read-back** — the result `TransformParameters.0.itk.txt` is an
+ `Euler3DTransform` in the box/world physical frame; the adapter composes it
+ with the `-t0` translation and runs the usual `fix_orientation` gate.
+
## Notes / caveats
- The benchmark compares **standard single-init** configurations (centered
@@ -221,11 +301,11 @@ it to a SimpleITK `DisplacementFieldTransform`, and scores it like the others.
the head-to-head but is the config the dataset actually ships with.
- `deepreg` could not be executed here, so its row is "not installed", not
measured; its code path is implemented and unit-verified for import/convention.
-- `fix_orientation` (elastix/ants/deepreg) chooses between a transform and its
- inverse by skull-ring concentricity — a correctness gate that makes the result
- independent of each toolkit's transform-direction convention. In practice it
- never fired (all `auto_inverted: False`): elastix/ants returned the T1c→CT
- direction as expected.
+- `fix_orientation` (elastix/ants/deepreg/impact) chooses between a transform
+ and its inverse by skull-ring concentricity — a correctness gate that makes
+ the result independent of each toolkit's transform-direction convention. In
+ practice it never fired (all `auto_inverted: False`): elastix/ants/impact
+ returned the T1c→CT direction as expected.
- **Elastix specifics** (ITK-Elastix 0.25.4, elastix 5.3.1, in-process python
API — no CLI binary involved):
- Elastix 5.x renamed components vs the classic parameter files: the rigid
@@ -247,4 +327,28 @@ it to a SimpleITK `DisplacementFieldTransform`, and scores it like the others.
`AffineTransform(3)`.
- Registration wall time is ~1.8 s/case at full budget (ASGD 500/500/250
over 3 resolutions); each worker's first case pays a ~10 s warm-up, so the
- median wall is 3.2 s and the mean is inflated by warm-up.
\ No newline at end of file
+ median wall is 3.2 s and the mean is inflated by warm-up.
+- **IMPACT specifics** (Docker `vboussot/elastix_impact`, Elastix 5.2.0,
+ GPU, Jacobian mode):
+ - **Slow, by design.** Every ASGD iteration runs the TotalSegmentator
+ extractor on the sampled patches with backpropagation, so the recommended
+ 500-iter / 2000-sample budget is ~9 min/case (vs 3–15 s for the intensity
+ tools). `--impact-iters` / `--impact-samples` trade accuracy for speed.
+ - **Rugged single-init convergence.** The semantic landscape has more local
+ minima than Mattes-MI: the pipeline nails the hardest repositioned case
+ (`m6_22IRRKU6`, mid 8.4 mm — best of all) but lands in bad minima on some
+ others. A multi-start or higher-iteration budget would likely raise its
+ acceptance; that is out of scope for the single-init head-to-head.
+ - **Stochastic.** Jacobian mode resamples patches each iteration, so results
+ vary run to run (see Stochasticity).
+ - **Elastix 5.x differences vs the in-process `elastix` pipeline:** the image
+ bundles 5.2.0, so the in-file multi-`Transform` chain and `UseScalingFactors`
+ are gone — the shared centered start is a separate `-t0` translation file,
+ and the result is read back as an `Euler3DTransform` (`.itk.txt`) and
+ composed with `-t0`. The image's own `CMD.sh` doesn't pass `-t0`, so the
+ adapter invokes `/lib/elastix-install/bin/elastix` directly.
+ - **Models are not in the image.** The TorchScript model(s) must be present
+ under the mounted `/Data/Models` (the repo caches them in
+ `data/impact_models/`, bind-mounted read-only).
+ - **CPU fallback:** `--impact-gpu -1` runs without `--gpus` and with
+ `ImpactGPU -1` / mixed precision off (much slower).
\ No newline at end of file
diff --git a/scripts/bench_registration.py b/scripts/bench_registration.py
index 92649f8..3f1d837 100644
--- a/scripts/bench_registration.py
+++ b/scripts/bench_registration.py
@@ -28,15 +28,17 @@ see REGISTRATION_BENCHMARK.md). The `--merge` mode then combines
the per-pipeline JSON files (accumulated across envs) into one summary + plot.
Usage (repo root):
- # main env (SimpleITK + elastix; impact needs Docker + the image; deepreg unavailable)
+ # main env (SimpleITK + elastix + antspyx; impact needs Docker + the image;
+ # deepreg unavailable)
source /opt/conda/etc/profile.d/conda.sh && conda activate longitudinal-313
- python scripts/bench_registration.py --pipelines simpleitk elastix impact
+ python scripts/bench_registration.py --pipelines simpleitk elastix antspyx
- # isolated env that has antspyx (see REGISTRATION_BENCHMARK.md)
- source /opt/conda/etc/profile.d/conda.sh && conda activate
- python scripts/bench_registration.py --pipelines antspyx simpleitk
+ # impact (GPU, Docker) — one-time setup in REGISTRATION_BENCHMARK.md
+ python scripts/bench_registration.py --pipelines impact
- # combine everything produced so far into one table + plot
+ # any pipeline that is not importable in the current env is recorded as
+ # unavailable, so the script is env-portable; --merge combines the
+ # per-pipeline JSONs accumulated across envs into one table + plot
python scripts/bench_registration.py --merge
"""
import argparse
@@ -482,12 +484,12 @@ def parse_args():
ap.add_argument("--impact-gpu", type=int, default=None,
help="GPU device index for IMPACT (default: auto-spread over "
"the host GPUs by pid; -1 forces CPU)")
- ap.add_argument("--impact-iters", type=int, default=400,
- help="IMPACT per-resolution max iterations (default 400, a converged "
- "rigid budget; the authors' full recommended is 500)")
- ap.add_argument("--impact-samples", type=int, default=1500,
- help="IMPACT random spatial samples per iteration (default 1500; "
- "full recommended 2000)")
+ ap.add_argument("--impact-iters", type=int, default=500,
+ help="IMPACT per-resolution max iterations (default 500, the authors' "
+ "recommended Jacobian budget; lower to trade accuracy for speed)")
+ ap.add_argument("--impact-samples", type=int, default=2000,
+ help="IMPACT random spatial samples per iteration (default 2000, "
+ "the recommended budget)")
ap.add_argument("--qa-images", action="store_true",
help="also write a checkreg-style PNG (checkerboard blend + color "
"overlay of the inverse-warped CT vs the original MR) per case")
diff --git a/src/registration_pipelines.py b/src/registration_pipelines.py
index a66bfb2..344187a 100644
--- a/src/registration_pipelines.py
+++ b/src/registration_pipelines.py
@@ -746,7 +746,7 @@ def _impact_param_map(model_cont_path, gpu, iters, samples, voxel_sizes,
return "\n".join(L) + "\n"
-def register_impact(ct, t1c, model=None, gpu=None, iters=400, samples=1500,
+def register_impact(ct, t1c, model=None, gpu=None, iters=500, samples=2000,
threads=12, keep_scratch=False, scales=(6.0, 3.0, 1.5, 1.0),
downsample=False):
"""IMPACT rigid CT->T1c (fixed=T1c, moving=CT) in the Docker image
@@ -767,11 +767,11 @@ def register_impact(ct, t1c, model=None, gpu=None, iters=400, samples=1500,
Compose`` the final transform = ``T_main(T_init(p))``),
- uses a rigid 6-DOF ``EulerTransform`` (the 5.x name for the classic
3D rigid), ASGD, a 4-level scale pyramid (no image resampling).
- Budget: the default ``iters=400`` / ``samples=1500`` is a *converged rigid*
- budget (measured: recovers the inter-session tilt the intensity-metric
- pipelines miss). The authors' fully-recommended Jacobian budget is
- ``iters=500`` / ``samples=2000`` (deformable-oriented, ~25% slower); pass
- those for maximum fidelity. Expect minutes per case (GPU), not seconds.
+ Budget: the default ``iters=500`` / ``samples=2000`` is the authors'
+ recommended Jacobian budget. It is *much* slower than the intensity-metric
+ pipelines (minutes per case on GPU vs seconds) because every iteration runs
+ the TotalSegmentator feature extractor on the sampled patches with
+ backpropagation. Lower ``iters``/``samples`` to trade accuracy for speed.
The result is read back from ``TransformParameters.0.itk.txt``
(an ``Euler3DTransform`` in the box/world physical frame) and composed with
the ``-t0`` translation, then checked by :func:`fix_orientation`.