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