End-to-end IOS/CBCT landmark detection and registration pipeline.
The IOS and CBCT landmark detection is done through two independent and parallel branches:
- IOS-Normalizer + IOS-Landmarks;
- CBCT-Landmarks.
The final registration is performed by the L2L-Registration module.
Two isolated Python environments are used because the validated components require different PyTorch and CUDA runtime versions:
| Environment | Main components | PyTorch wheel |
|---|---|---|
.venvs/torch25 |
IOS-Normalizer, IOS-Landmarks, L2L-Registration | PyTorch 2.5.1, CUDA 12.4 |
.venvs/torch28 |
CBCT-Landmarks | PyTorch 2.8.0, CUDA 12.6 |
Quantitative Results on MICCAI STSR2026 Challenge
| Set | Mean Translation Error (mm) | Mean Rotation Error (°) |
|---|---|---|
| Validation | 4.9881 | 1.421 |
| Test | 6.7520 | 1.9762 |
![]() |
![]() |
![]() |
![]() |
|---|---|---|---|
| CBCT | IOS | Rigid Kabsch | ICP |
Registration example for Patient case 027.
Installation and inference must run in a Linux CUDA environment, the setup script does not support native Windows Python environments.
The repository is expected to contain:
L2L-Registration/
├── data/
│ ├── inputs/
│ └── outputs/
│
├── model/
│ ├── IOS-Normalizer/
│ ├── IOS-Landmarks/
│ ├── CBCT-Landmarks/
│ └── L2L-Registration/
|
├── requirements/
│ ├── requirements-torch25.txt
│ └── requirements-torch28.txt
│
├── weights/
| ├── IOS-Normalizer/
│ └── best.pt
│
├── IOS-Landmarks/
│ ├── segmentator_best.pth
│ └── heatmap_landmarks.pth
│
├── CBCT-Landmarks/
| ├── checkpoint_best.pth
| └── nnLM_results/
| └── Dataset002_STS2026/
| └── nnLandmark__nnUNetResEncUNetMPlans_torchres__3d_fullres/
| ├── dataset.json
| ├── dataset_fingerprint.json
| └── plans.json
|
├── .venvs/
| ├── torch25/
| └── torch28/
|
├── .gitattributes
├── .gitignore
├── .gitmodules
├── create_venvs_def.py
├── predict.sh
├── README.md
└── run_inference.py
IOS-Normalizer and IOS-Landmarks are Git submodules. Their exact commits are recorded by the parent repository.
git clone --recurse-submodules https://github.com/AImageLab-zip/L2L-Registration.git
cd L2L-RegistrationThe installation script expects:
- Python 3.10;
- Git;
- an NVIDIA GPU and compatible driver;
- CUDA Toolkit 12.4 with
nvcc; - GCC/G++;
- Ninja or compatible build tools;
- sparsehash development headers.
Verify the main tools:
python3.10 --version
nvidia-smi
nvcc --version
g++ --version
git --versionThe setup script requires Python 3.10.x.
Package names may vary between distributions. A typical Ubuntu/Debian installation includes:
sudo apt update
sudo apt install -y \
build-essential \
git \
ninja-build \
libsparsehash-dev \
python3.10 \
python3.10-dev \
python3.10-venvInstall or load CUDA Toolkit 12.4 separately so that:
nvcc --versionreports release 12.4.
The PyTorch 2.8 environment uses CUDA 12.6 wheels, but a separate local CUDA 12.6 Toolkit is not required by this setup script. The local Toolkit is used for compiling the PyTorch 2.5 custom extensions.
Run the setup script from the repository root:
python3.10 create_venvs_def.pyThis creates:
.venvs/torch25
.venvs/torch28
The script installs:
- PyTorch 2.5.1, torchvision 0.20.1 and torchaudio 2.5.1 from the CUDA 12.4 wheel index;
- PyTorch 2.8.0, torchvision 0.23.0 and torchaudio 2.8.0 from the CUDA 12.6 wheel index;
- OpenAI CLIP;
- the required PyTorch Geometric extension wheels;
pointgroup_ops;pointops;- CBCT-Landmarks and its
nnLM_predictcommand.
CBCT-Landmarks is installed in editable mode by default, so changes to its Python source code are immediately available in .venvs/torch28.
python3.10 create_venvs_def.py --recreateThis removes and recreates both environment directories.
To install CBCT-Landmarks as a normal, non-editable package:
python3.10 create_venvs_def.py --non-editable-cbctTo recreate everything in non-editable mode:
python3.10 create_venvs_def.py \
--recreate \
--non-editable-cbctThe default custom-extension architecture is:
8.6
Override it when required by the target GPU:
python3.10 create_venvs_def.py \
--cuda-arch-list 8.6Multiple architectures can be supplied using the syntax accepted by TORCH_CUDA_ARCH_LIST, for example:
python3.10 create_venvs_def.py \
--cuda-arch-list "8.0;8.6"python3.10 create_venvs_def.py \
--max-jobs 2Reducing this value can help on systems with limited RAM.
The validated extension build uses CUDA Toolkit 12.4. The setup script stops when another nvcc version is detected.
To attempt compilation anyway:
python3.10 create_venvs_def.py \
--allow-cuda-toolkit-mismatchThis only bypasses the version check. It does not guarantee that the extensions will compile or run correctly.
.venvs/torch25/bin/python - <<'PY'
import torch
import torchvision
import pointgroup_ops
import pointops
print("PyTorch:", torch.__version__)
print("PyTorch CUDA:", torch.version.cuda)
print("torchvision:", torchvision.__version__)
print("pointgroup_ops:", pointgroup_ops.__file__)
print("pointops:", pointops.__file__)
PYExpected PyTorch values:
PyTorch: 2.5.1
PyTorch CUDA: 12.4
.venvs/torch28/bin/python - <<'PY'
import torch
import torchvision
import nnlandmark
print("PyTorch:", torch.__version__)
print("PyTorch CUDA:", torch.version.cuda)
print("torchvision:", torchvision.__version__)
print("nnlandmark:", nnlandmark.__file__)
PYExpected PyTorch values:
PyTorch: 2.8.0
PyTorch CUDA: 12.6
Verify the command-line entry point:
.venvs/torch28/bin/nnLM_predict --helpDownload the model weights and check the expected structure:
weights/
├── IOS-Normalizer/
│ └── best.pt
│
├── IOS-Landmarks/
│ ├── segmentator_best.pth
│ └── heatmap_landmarks.pth
│
└── CBCT-Landmarks/
├── checkpoint_best.pth
└── nnLM_results/
└── Dataset002_STS2026/
└── nnLandmark__nnUNetResEncUNetMPlans_torchres__3d_fullres/
├── dataset.json
├── dataset_fingerprint.json
└── plans.json
The input directory must contain one top-level directory per case:
data/inputs/
├── case_001/
│ ├── CBCT.nii.gz
│ ├── lower.stl
│ └── upper.stl
|
├── case_002/
│ ├── CBCT.nii.gz
│ ├── lower.stl
│ └── upper.stl
│
└── ...
Important requirements:
- every case must be stored in its own top-level directory;
- every CBCT filename must end with
CBCT.nii.gz; - top-level case directory names must be unique;
- IOS data must follow the formats and naming conventions accepted by IOS-Normalizer.
Run the complete pipeline using predict.sh.
The two virtual environments do not need to be activated manually.
run_inference.py automatically launches:
- IOS-Normalizer with
.venvs/torch25/bin/python; - IOS-Landmarks with
.venvs/torch25/bin/python; - CBCT-Landmarks with
.venvs/torch28/bin/nnLM_predict; - L2L-Registration with
.venvs/torch25/bin/python.
The IOS and CBCT branches run in parallel for each split. L2L-Registration starts only after both branches finish successfully. Input splits are processed sequentially.
Display the available arguments:
.venvs/torch25/bin/python run_inference.py --helpBecause the default directories are repository-relative, this is sufficient when the standard layout is used:
.venvs/torch25/bin/python run_inference.pyDefault values:
--inputs data/inputs
--outputs data/outputs
--work-dir .work
--weights-dir weights
--num-splits 4
.venvs/torch25/bin/python run_inference.py \
--inputs /absolute/path/to/inputs \
--outputs /absolute/path/to/outputs \
--weights-dir /absolute/path/to/weights \
--work-dir /absolute/path/to/work \
--num-splits 4.venvs/torch25/bin/python run_inference.py \
--num-splits 2The number of non-empty splits cannot exceed the number of top-level cases.
Intermediate files are deleted after successful processing by default.
To preserve them for debugging:
KEEP_INTERMEDIATES=1 \
.venvs/torch25/bin/python run_inference.py \
--inputs data/inputs \
--outputs data/outputs \
--weights-dir weightsThe intermediate data will remain under:
.work/
By default, inference looks for:
.venvs/torch25/bin/python
.venvs/torch28/bin/python
.venvs/torch28/bin/nnLM_predict
Override these locations with environment variables:
export TORCH25_PYTHON=/absolute/path/to/torch25/bin/python
export TORCH28_PYTHON=/absolute/path/to/torch28/bin/python
export NNLM_PREDICT=/absolute/path/to/torch28/bin/nnLM_predictThe whole virtual-environment root can also be changed:
export VENV_ROOT=/absolute/path/to/venvsOther supported path overrides are:
export PROJECT_ROOT=/absolute/path/to/L2L-Registration
export INPUT_DIR=/absolute/path/to/inputs
export OUTPUT_DIR=/absolute/path/to/outputs
export WORK_DIR=/absolute/path/to/work
export WEIGHTS_DIR=/absolute/path/to/weightsEnvironment variables used to calculate module paths should be set before starting run_inference.py.
Final registration results are written to:
data/outputs/
or to the directory supplied through --outputs.
The pipeline verifies that L2L-Registration creates or modifies at least one output file for every processed split. A split is reported as failed when no output is produced.
Temporary intermediate files are stored under .work/ and removed unless KEEP_INTERMEDIATES=1 is set.
Install Python 3.10 or pass the correct executable:
python3 create_venvs_def.py --python /path/to/python3.10The selected interpreter must report Python 3.10.
Recreate both environments:
python3.10 create_venvs_def.py --recreateInstall or load CUDA Toolkit 12.4 and ensure its bin directory is in PATH.
Example:
export CUDA_HOME=/usr/local/cuda-12.4
export PATH="$CUDA_HOME/bin:$PATH"
export LD_LIBRARY_PATH="$CUDA_HOME/lib64:${LD_LIBRARY_PATH:-}"Then verify:
nvcc --versionOn Ubuntu/Debian:
sudo apt install libsparsehash-devCheck:
nvcc --version
g++ --version
echo "$CUDA_HOME"
echo "$TORCH_CUDA_ARCH_LIST"Then recreate the environments with fewer build jobs:
python3.10 create_venvs_def.py \
--recreate \
--max-jobs 2Verify:
ls -l .venvs/torch28/bin/nnLM_predictRecreate the environments when it is missing:
python3.10 create_venvs_def.py --recreateAt least one file under the selected input directory must end with:
CBCT.nii.gz
Check with:
find data/inputs -type f -iname '*CBCT.nii.gz'Compare the local weights/ directory with the structure in the Add the trained weights section. The inference script validates every required checkpoint before starting the relevant component.
The current CBCT-Landmarks command uses a patch batch size of 4. Reducing it requires changing the corresponding argument in run_inference.py.
Changing --num-splits controls how many cases are processed together, but it does not change the internal CBCT patch batch size.
- CBCT-Landmarks is editable by default.
- IOS-Normalizer, IOS-Landmarks, and L2L-Registration are executed directly from their source directories.
- Python source modifications in those directories are therefore used directly.
- Changes to C++ or CUDA files in
model/IOS-Landmarks/libs/require rebuilding the custom extensions. - Do not commit virtual environments, private inputs, generated outputs, temporary files, compiled artifacts, or model weights.
Rebuild after changing extension sources:
python3.10 create_venvs_def.py --recreateUpdate a submodule only after selecting and testing a specific component commit.
Example:
git -C model/IOS-Normalizer fetch
git -C model/IOS-Normalizer checkout <COMMIT_HASH>
git add model/IOS-Normalizer
git commit -m "Update IOS-Normalizer submodule"Repeat the same process for IOS-Landmarks.
Verify recorded revisions:
git submodule statusSee the license files in this repository and in each third-party component.
The source-code license does not automatically grant permission to redistribute trained weights, challenge data, clinical data, or third-party datasets.




