pyCERR provides a convenient data structure for radiological imaging metadata and their associations: scans, segmentations, radiotherapy dose, treatment plans and deformable registrations for a patient are held together in a single container. Utilities are provided to extract, transform and organize this metadata, and to visualize the results of image processing.
On this foundation, pyCERR supports a wide range of applications in radiology and radiation oncology research:
- Image analysis: IBSI-compliant radiomics and texture maps, PET SUV computation, DCE-MRI uptake features and optimal mass transport (urOMT) analysis
- Segmentation: contouring, structure operations, consensus contours and auto-segmentation with pre-trained AI models
- Registration: deformable image registration (plastimatch, ANTs) with tools for registration quality assurance
- Dosimetry and outcomes: dose-volume histograms and metrics, gamma dose comparison, and normal tissue complication / tumor control probability models
- Treatment planning: IMRT dose calculation and fluence optimization, including lattice radiotherapy
- AI workflows: data preparation for model training and inference, on a workstation or in the cloud
Create a virtual environment with uv, install pyCERR with the desktop viewer, and launch Python:
uv venv --python 3.12
source .venv/bin/activate # Windows: .venv\Scripts\activate
uv pip install "pycerr[viewer]"
pythonIn Python, load an example dataset and show it in the desktop viewer:
from cerr import plan_container as pc
from cerr.viewer.pycerr_gui import show
from cerr import datasets
dcmDir = datasets.fetch_sample_data('head_and_neck') # local if present, else downloaded
planC = pc.loadDcmDir(dcmDir)
v = show(planC)The desktop viewer needs the viewer extra. A plain pycerr install covers
everything else, including the Jupyter viewer; see Install pyCERR.
All data for a patient lives in a single PlanC container — scans, structures
(segmentations), dose, treatment plans (beams) and deformations — defined in
cerr.plan_container. Around it, pyCERR provides:
Data import / export
- DICOM import of CT/MR/PT/US/NM scans plus RTSTRUCT, RTDOSE and RTPLAN —
pc.loadDcmDir - NIfTI import/export of scans, segmentations and dose —
pc.loadNiiScan/loadNiiStructure/loadNiiDose,.saveNii - Full-
PlanCHDF5 serialization —pc.saveToH5/pc.loadFromH5 - DICOM RTSTRUCT export —
cerr.dcm_export.rtstruct_iod
Segmentation & contours
- Lazy polygon → binary-mask rasterization —
cerr.contour.rasterseg.getStrMask - Import label maps / binary masks as structures —
cerr.dataclasses.structure.importStructureMask
Radiomics (IBSI-compliant)
- Scalar features: morphology, first-order, GLCM / GLRLM / GLSZM / GLDZM / NGTDM / NGLDM (IBSI-1)
- Convolutional texture / filter-response maps: mean, LoG, Laws, Gabor, wavelet (IBSI-2)
cerr.radiomics.ibsi1.computeScalarFeatures, configured via JSON settings
Dosimetry & outcomes
- Dose–volume histograms and metrics (Dx, Vx, MOHx, MOCx, mean dose) —
cerr.dvh - Radiotherapy outcome models (NTCP/TCP: LKB, logistic, Cox, Appelt) —
cerr.roe - IMRT planning / beamlet (influence-matrix) dose calculation —
cerr.imrtp
Image processing
- Deformable image registration via plastimatch / ANTs —
cerr.registration - Resampling, intensity preprocessing and masking —
cerr.utils - Semi-quantitative DCE-MRI features —
cerr.mri_metrics - Helpers for AI model training / inference pipelines —
cerr.utils.ai_pipeline
Visualization — three interchangeable viewers driven by the same planC
(a Qt5/Qt6 CERR-style desktop GUI, a Jupyter/Colab notebook viewer, and
napari 2D/3D); see visualize scan, dose and segmentation below.
- Example notebooks (grouped by topic): https://github.com/cerr/pyCERR-Notebooks
- API reference — https://pycerr.readthedocs.io (built from
docs/; build locally withcd docs && make html) - Desktop GUI scripting API —
cerr/viewer/API_pycerr_gui.md - Test suite & coverage —
tests/README.md
pyCERR is published on PyPI as pycerr and
supports Python 3.9–3.13 (3.12 recommended). Install it into an isolated
virtual environment. The steps below use uv;
install it first if you don't have it (curl -LsSf https://astral.sh/uv/install.sh | sh
on macOS/Linux, winget install --id=astral-sh.uv on Windows, or brew install uv).
uv venv --python 3.12 # creates .venv/, downloading Python 3.12 if needed
source .venv/bin/activate # Windows: .venv\Scripts\activateuv pip install pycerr # core: I/O, radiomics, DVH, registration, Jupyter viewer
uv pip install "pycerr[viewer]" # + Qt desktop viewer (pycerr_gui), ROE and IMRTP GUIs
uv pip install "pycerr[viewer,napari]" # + napari 2D/3D viewer| Extra | Adds | Needed for |
|---|---|---|
| (none) | core dependencies, ipywidgets |
Python API, headless/batch processing, containers, cerr.viewer.pycerr_nbviewer (Jupyter / Colab) |
viewer |
qtpy, PyQt5 (PyQt6 on aarch64 Linux), pyvista, pyvistaqt |
cerr.viewer.pycerr_gui desktop viewer (incl. 3D view), ROE and IMRTP GUIs |
napari |
napari[all], napari-animation |
cerr.viewer.pycerr_napari |
ants |
antspyx |
ANTs-based registration, cerr.registration.ants_reg |
gpu |
cupy-cuda13x and NVIDIA CUDA 13 runtime wheels |
optional GPU acceleration of the urOMT solver |
Extras can be combined, e.g. uv pip install "pycerr[viewer,napari,ants]".
The Qt GUIs go through qtpy, so they run
on Qt5 or Qt6 with PyQt5, PyQt6, PySide2 or PySide6. The viewer extra installs
PyQt5, or PyQt6 on aarch64 Linux, where PyQt5 has no wheel. To use Qt6
elsewhere, install PyQt6 and set QT_API=pyqt6 if PyQt5 is also present
(qtpy otherwise picks PyQt5 first). The extra is not in the base install so that
headless servers and containers need no Qt binding. Launching pycerr_gui
without a Qt binding raises an ImportError naming the install command.
AI segmentation models (cerr.ai_models) additionally need model_installer,
which is distributed from GitHub only:
uv pip install "model_installer @ git+https://github.com/cerr/model_installer.git"uv pip install "pycerr[viewer] @ git+https://github.com/cerr/pyCERR.git@main"or, to work on pyCERR itself, clone the repository and make an editable install:
git clone https://github.com/cerr/pyCERR.git
cd pyCERR
uv venv --python 3.12
source .venv/bin/activate
uv pip install -e ".[viewer,napari]" pytestInstall JupyterLab in the same environment to run the example notebooks:
uv pip install jupyterlab
jupyter labIn Google Colab, install with !pip install pycerr (the notebook viewer needs no extra).
Example notebooks are hosted at https://github.com/cerr/pyCERR-Notebooks/. Clone this repository to use notebooks as a starting point.
git clone https://github.com/cerr/pyCERR-Notebooks.gitRun Python from the virtual environment created above and try out the following code samples.
import numpy as np
from cerr import plan_container as pc
from cerr.viewer import pycerr_gui # desktop viewer (pycerr_gui.show, ...); needs pycerr[viewer]
dcmDir = r"\\path\to\Data\dicom\directory"
planC = pc.loadDcmDir(dcmDir)
scanNiiFileName = r"\\path\to\Data\scan.nii.gz"
planC = pc.loadNiiScan(scanNiiFileName, imageType = "CT SCAN")
planC = pc.loadNiiScan(scanNiiFileName, imageType = "CT SCAN", direction='LPS') # 3-letter orientation code, e.g. 'LPS', 'RAS'
planC = pc.loadNiiScan(scanNiiFileName, imageType = "CT SCAN", direction='LPS', initplanC=planC)
structNiiFileName = r"\\path\to\Data\structure.nii.gz"
assocScanNum = 0
labelDict = {'GTV_P': 1, 'GTV_N': 2} # structure name -> label value
planC = pc.loadNiiStructure(structNiiFileName, assocScanNum, planC, labelDict)
from cerr.dcm_export import rtstruct_iod
structDcmFileName = r"\\path\to\Data\structure.dcm"
structNums = [0,2,3]
exportOpts = {'seriesDescription': "Exported by pyCERR"}
rtstruct_iod.create(structNums,structDcmFileName,planC,exportOpts)
scanNiiFileName = r"\\path\to\Data\scan.nii.gz"
scanNum = 0
planC.scan[scanNum].saveNii(scanNiiFileName)
structNiiFileName = r"\\path\to\Data\structure.nii.gz"
structNum = 0
planC.structure[structNum].saveNii(structNiiFileName, planC)
doseNiiFileName = r"\\path\to\Data\dose.nii.gz"
doseNum = 0
planC.dose[doseNum].saveNii(doseNiiFileName)
pyCERR ships three interchangeable viewers under the cerr.viewer sub-package,
all driven by the same planC:
| Viewer | Module | Install | Best for |
|---|---|---|---|
| Qt desktop | cerr.viewer.pycerr_gui (show) |
pycerr[viewer] |
CERR-style slice viewer: contouring, registration QA, IMRTP/ROE, scripting API |
| notebook | cerr.viewer.pycerr_nbviewer (showNB) |
pycerr |
Jupyter / JupyterLab / VS Code / Google Colab |
| napari 2D/3D | cerr.viewer.pycerr_napari (showNapari) |
pycerr[napari] |
quick interactive review, 3D rendering |
from cerr.viewer import pycerr_gui
viewer = pycerr_gui.show(planC) # opens the CERR-style desktop GUI
# ... or launch empty and drag-and-drop a DICOM directory / NIfTI file in.
# The viewer exposes a scripting API (set_scan/set_dose/goto_structure,
# registration-QA setup, DVH export, save_screenshot, ...); see
# cerr/viewer/API_pycerr_gui.md for the full reference.
from cerr.viewer import pycerr_nbviewer
viewer = pycerr_nbviewer.showNB(planC, scan_nums=[0], struct_nums=strNumList,
dose_nums=[0])
from cerr.viewer import pycerr_napari
scanNumList = [0]
doseNumList = [0]
numStructs = len(planC.structure)
strNumList = np.arange(numStructs)
displayMode = '2d' # '2d' or '3d'
vectDict = {}
viewer, scan_layer, struct_layer, dose_layer, dvf_layer = \
pycerr_napari.showNapari(planC, scan_nums=scanNumList, struct_nums=strNumList,\
dose_nums=doseNumList, vectors_dict=vectDict, displayMode = '2d')
from cerr import dvh
structNum = 0
doseNum = 0
dosesV, volsV, isErr = dvh.getDVH(structNum, doseNum, planC)
binWidth = 0.025
doseBinsV,volHistV = dvh.doseHist(dosesV, volsV, binWidth)
percent = 70
dvh.MOHx(doseBinsV,volHistV,percent)
import os
from cerr import datasets
from cerr.radiomics import ibsi1
scanNum = 0
structNum = 0
# JSON settings file: resampling, intensity discretization and feature classes.
# A sample is shipped with pyCERR; copy and edit it for your own analysis.
settingsFile = os.path.join(os.path.dirname(datasets.__file__),
'radiomics_settings', 'original_settings.json')
featDict, diagDict = ibsi1.computeScalarFeatures(scanNum, structNum, settingsFile, planC)
# featDict maps feature names to values (IBSI-1: shape, first-order, GLCM, GLRLM, GLSZM, ...)
import os
from cerr import datasets
from cerr.radiomics import texture_utils
scanNum = 0
structNum = 0
# JSON settings file for a convolutional filter (IBSI-2). Samples shipped with pyCERR:
# mean_filter.json, LoG_filter.json, Rot_inv_laws_energy_filter.json, gabor_filter.json
settingsFile = os.path.join(os.path.dirname(datasets.__file__),
'convolutional_filter_settings', 'LoG_filter.json')
planC = texture_utils.generateTextureMapFromPlanC(planC, scanNum, structNum, settingsFile)
# The texture map is added as a new scan, cropped around the structure
textureScanNum = len(planC.scan) - 1
texture3M = planC.scan[textureScanNum].getScanArray()