End-to-end CBCT/IOS landmark detection and registration pipeline.
The pipeline runs four components:
- IOS-Normalizer
- IOS-Landmarks
- CBCT-Landmarks
- L2L-Registration
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 |
The local CUDA Toolkit is used to compile the custom IOS-Landmarks extensions. For the validated setup, nvcc must report CUDA Toolkit 12.4.
Installation and inference must run in a Linux CUDA environment.
Supported examples include:
- a native Linux workstation;
- a Linux server or cluster node;
- CUDA-enabled WSL2.
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.
Clone the project together with all submodules:
git clone --recurse-submodules https://github.com/AImageLab-zip/L2L-Registration.git
cd L2L-RegistrationFor an existing clone created without --recurse-submodules, initialize the submodules with:
git submodule update --init --recursiveVerify the checked-out submodule commits:
git submodule statusDo not run git submodule update --remote unless you intentionally want to test newer component revisions. The parent repository pins the validated commits.
The 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;
- sufficient free disk space for two PyTorch environments.
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 --helpThe inference script expects this 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 complete CBCT-Landmarks result directory must be provided. dataset.json alone is not sufficient.
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;
- CBCT files are found recursively;
- every CBCT filename must end with
CBCT.nii.gz; - top-level case directory names must be unique;
- two CBCT files must not resolve to the same case identifier;
- IOS data must follow the formats and naming conventions accepted by IOS-Normalizer.
Example CBCT names:
patient_001_CBCT.nii.gz
scan-CBCT.nii.gz
CBCT.nii.gz
The case identifier used by CBCT-Landmarks is normally derived from the parent case-directory name.
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.