cerr.imrtp package

IMRT planning (IMRTP): beamlet/influence-matrix dose calculation and a PyQt5 planning GUI that displays the optimized dose and beam geometry through the cerr.viewer.pycerr_gui desktop viewer.

Subpackages

Submodules

cerr.imrtp.imrtp module

imrtp

Driver for IMRT beamlet/dose computation in pyCERR; the role played by IMRTP.m + dose2CERR.m in Matlab CERR.

Matlab CERR shipped three dose engines (QIB, VMC++, DPM) that rely on large external data files / executables and have not been ported to Python. This module therefore exposes a small engine registry: any callable with the signature

doseArray = engine(im, planC, statusCallback)

can be registered under a name and selected through the GUI’s IM Parameters > algorithm field. A simple, self-contained 'PB-Demo' divergent pencil-beam engine is provided so that the GUI is testable end-to-end; it is for geometry/QA demonstration only and is not dosimetrically validated.

This file is part of pyCERR and is distributed under the terms of the Lesser GNU Public License (same terms as CERR).

cerr.imrtp.imrtp.registerEngine(name: str, fn: Callable)[source]

Register a dose engine: fn(im, planC, statusCallback) -> dose3M.

dose3M must be a numpy array on the associated scan grid (rows x cols x slices), in Gy or arbitrary units.

cerr.imrtp.imrtp.availableEngines()[source]

Names of registered dose engines (analog of VMCPresent/QIBPresent).

cerr.imrtp.imrtp.runIMRTP(im: IMRTProblem, planC, statusCallback: Callable[[str, float], None] | None = None)[source]

Compute dose for all beams of im using the selected engine (port of IMRTP.m’s top-level flow).

Returns:

total dose on the scan grid (rows x cols x slices).

Return type:

np.ndarray

cerr.imrtp.imrtp.doseToPlanC(dose3M: ndarray, im: IMRTProblem, planC, fractionGroupID: str | None = None) int[source]

Insert a computed dose into planC (port of dose2CERR.m).

Returns the new dose index.

cerr.imrtp.imrtp_gui module

imrtp_gui

pyCERR IMRTP GUI - a Qt port of Matlab CERR’s IMRTP/IMRTPGui.m.

Create a GUI to set up IM (intensity-modulated) beam/structure problems and calculate beamlet dose.

Usage:
>>> from cerr import plan_container as pc
>>> from cerr.imrtp import imrtp_gui
>>> planC = pc.loadDcmDir(r'path/to/dicom')
>>> imrtp_gui.IMRTPGui(planC)          # blocks until window closed

or, inside an existing Qt event loop (e.g. napari console): >>> gui = imrtp_gui.IMRTPGui(planC, block=False)

Panels (mirroring IMRTPGui.m):

Beams beam list, BEV check, New / Equispaced / Delete Geometry Preview axial CT at isocenter with draggable gantry lines Select Scan associated-scan picker Structures goal list: isTarg / marg / sampRate / remove Beam Parameters per-beam fields with auto-calculation checkboxes IM Parameters dose-engine parameters (algorithm, DoseTerm, …) VMC Parameters Monte-Carlo parameters IM Dosimetry set browse / delete / rename stored IM sets File Recompute/Copy/Overwrite/Revert actions, Go/Show/Exit Status status text + progress bar

Original Matlab author: JRA 4/30/04; JJW 07/05/06; APA 10/16/06. This file is part of pyCERR and is distributed under the terms of the Lesser GNU Public License (same terms as CERR).

class cerr.imrtp.imrtp_gui.GeometryPreview(*args: Any, **kwargs: Any)[source]

Bases: FigureCanvasQTAgg

Axial CT thumbnail with a 100 cm gantry circle and draggable beam lines; dragging the mouse sets the current beam’s gantry angle.

angleChanged

int = …, arguments: Sequence = …) -> PYQT_SIGNAL

types is normally a sequence of individual types. Each type is either a type object or a string that is the name of a C++ type. Alternatively each type could itself be a sequence of types each describing a different overloaded signal. name is the optional C++ name of the signal. If it is not specified then the name of the class attribute that is bound to the signal is used. revision is the optional revision of the signal that is exported to QML. If it is not specified then 0 is used. arguments is the optional sequence of the names of the signal’s arguments.

Type:

pyqtSignal(*types, name

Type:

str = …, revision

dragFinished

int = …, arguments: Sequence = …) -> PYQT_SIGNAL

types is normally a sequence of individual types. Each type is either a type object or a string that is the name of a C++ type. Alternatively each type could itself be a sequence of types each describing a different overloaded signal. name is the optional C++ name of the signal. If it is not specified then the name of the class attribute that is bound to the signal is used. revision is the optional revision of the signal that is exported to QML. If it is not specified then 0 is used. arguments is the optional sequence of the names of the signal’s arguments.

Type:

pyqtSignal(*types, name

Type:

str = …, revision

refresh(planC, im, currentBeam)[source]
cerr.imrtp.imrtp_gui.beamGeometry(beam, im, planC)[source]

Compute a beam’s display geometry in world coords (cm, pyCERR virtual): polylines - the divergent field pyramid edges, the iso/exit field rectangles and the central axis (used for the 3D view); apex and corners - the pyramid source point and 4 far field corners (used to compute the per-slice beam cross-section in the 2D views).

Mirrors Matlab CERR’s beam’s-eye-view geometry: gantry in the axial plane (couch 0), source at SAD from the isocenter.

cerr.imrtp.imrtp_gui.beamPolylines(beam, im, planC)[source]

Backward-compatible: just the polylines of beamGeometry().

class cerr.imrtp.imrtp_gui.IMRTPGuiWindow(planC, im=None, saveIndex=None, parent=None, viewer=None)[source]

Bases: QMainWindow

closeEvent(self, a0: QCloseEvent | None)[source]
statusbarMsg(msg)[source]
waitbar(frac)[source]
markStale()[source]
refreshScan()[source]
refreshStructs()[source]
refreshBeams()[source]
refreshPreview()[source]
refreshBeamParams()[source]
refreshIMParams()[source]
refreshMCParams()[source]
refreshBrowser()[source]
beamParamChanged(fName)[source]
autoCheckChanged()[source]
newBeam()[source]
newEquispaced()[source]
delBeam()[source]
selectBeam()[source]
previewAngleChanged(angle)[source]
previewDragFinished()[source]
selScan(scanIdx)[source]
addGoal(relStrNum)[source]
delGoal(goalNum)[source]
pbMarginText(goalNum, editor)[source]
strSampleRate(goalNum, editor)[source]
targBoxClicked(goalNum)[source]
imParamChanged(pName)[source]
mcParamChanged(pName)[source]
browseIM(idx)[source]
deleteIM()[source]
renameIM()[source]
save()[source]

‘Go’ button: dispatch on the File action popup.

showDose()[source]

‘Show’ button: display the computed dose. Uses the pyCERR Qt viewer (pycerr_gui) when one is attached; otherwise falls back to the napari viewer.

cerr.imrtp.imrtp_gui.IMRTPGui(planC, im=None, saveIndex=None, block=True, viewer=None)[source]

Open the IMRTP GUI for planC.

Parameters:
  • planC – pyCERR plan container with at least one scan loaded.

  • im – optional existing IMRTProblem to edit (analog of IMRTPGui('init', IM, index) in Matlab).

  • saveIndex – index of im within planC.im when editing a stored set.

  • block – when True (script usage) run the Qt event loop until the window is closed; when False (napari / IPython with an active event loop) just show the window and return it.

  • viewer – optional PyCerrViewer to show the computed dose and the beam’s-eye-view overlays in (instead of opening napari).

Returns:

the window instance.

Return type:

IMRTPGuiWindow

cerr.imrtp.imrtp_problem module

imrtp_problem

Python port of CERR’s IMRTP/initIMRTProblem.m data structures for pyCERR.

Defines the IMRT problem container (IMRTProblem) together with its beam (IMBeam), goal (IMGoal), solution and parameter sub-structures, mirroring the Matlab IM struct used by IMRTPGui.m.

Coordinates are in pyCERR’s virtual coordinate system (cm), consistent with planC.scan[scanNum].getScanXYZVals().

Based on:

https://github.com/cerr/CERR/blob/master/IMRTP/initIMRTProblem.m https://github.com/cerr/CERR/blob/master/IMRTP/IMRTPGui.m

This file is part of pyCERR and is distributed under the terms of the Lesser GNU Public License (same terms as CERR).

cerr.imrtp.imrtp_problem.BEAM_FIELD_NAMES = [('beamNum',), ('beamModality',), ('beamEnergy',), ('isocenter', 'x'), ('isocenter', 'y'), ('isocenter', 'z'), ('isodistance',), ('arcAngle',), ('couchAngle',), ('collimatorAngle',), ('gantryAngle',), ('beamDescription',), ('beamletDelta_x',), ('beamletDelta_y',), ('dateOfCreation',), ('beamType',), ('zRel',), ('xRel',), ('yRel',), ('sigma_100',)]

Beam-parameter fields shown in the “Beam Parameters” panel. Nested fields (isocenter.x etc.) are expressed as tuples.

cerr.imrtp.imrtp_problem.BEAM_FIELD_EDITABLE = [0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1]

1 -> user editable, 0 -> auto-computed (checkbox toggles auto calculation)

cerr.imrtp.imrtp_problem.PARAM_NAMES = [('algorithm',), ('DoseTerm',), ('ScatterMethod',), ('Scatter', 'Threshold'), ('Scatter', 'RandomStep'), ('xyDownsampleIndex',), ('numCTSamplePts',), ('cutoffDistance',)]

QIB / DPM pencil-beam dose-calculation parameters

cerr.imrtp.imrtp_problem.MC_PARAM_NAMES = [('NumParticles',), ('NumBatches',), ('scoreDoseToWater',), ('includeError',), ('monoEnergy',), ('spectrum',), ('repeatHistory',), ('splitPhotons',), ('photonSplitFactor',), ('base',), ('dimension',), ('skip',)]

VMC++ Monte-Carlo parameters

cerr.imrtp.imrtp_problem.GOAL_FIELDS = ['structNum', 'strUID', 'structName', 'isTarget', 'PBMargin', 'xySampleRate']

Goal (structure) fields

class cerr.imrtp.imrtp_problem.Isocenter(x: float | str = 'COM', y: float | str = 'COM', z: float | str = 'COM')[source]

Bases: object

Beam isocenter. Any coordinate may be the string 'COM' meaning “auto-compute from the center-of-mass of the target structures”.

x: float | str = 'COM'
y: float | str = 'COM'
z: float | str = 'COM'
class cerr.imrtp.imrtp_problem.Beamlet(structureName: str = '', format: str = 'uint8', influence: ndarray | None = None, beamNum: int = 0, fullLength: int = 0, indexV: ndarray | None = None, maxInfluenceVal: float = 0.0, lowDosePoints: ndarray | None = None, sampleRate: int = 1, strUID: str = '')[source]

Bases: object

Sparse per-structure beamlet influence record.

One Beamlet instance corresponds to (structure i, pencil-beam j) and holds the non-zero dose contributions of that pencil beam to that structure, in the compressed format used by Matlab CERR.

structureName: str = ''
format: str = 'uint8'
influence: ndarray | None = None
beamNum: int = 0
fullLength: int = 0
indexV: ndarray | None = None
maxInfluenceVal: float = 0.0
lowDosePoints: ndarray | None = None
sampleRate: int = 1
strUID: str = ''
class cerr.imrtp.imrtp_problem.IMBeam(beamNum: int = 1, beamModality: str = 'photons', beamEnergy: float = 18, isocenter: Isocenter = <factory>, isodistance: float = 100.0, arcAngle: float = 0.0, couchAngle: float = 0.0, collimatorAngle: float = 0.0, gantryAngle: float = 0.0, beamDescription: str = 'IMRT beam', beamletDelta_x: float = 1.0, beamletDelta_y: float = 1.0, dateOfCreation: str = <factory>, beamType: str = 'IM', zRel: float = 0.0, xRel: float = 0.0, yRel: float = 0.0, sigma_100: float = 0.4, beamUID: str = <factory>, beamlets: list = <factory>, RTOGPBVectorsM: ndarray | None = None, xPBPosV: ndarray | None = None, yPBPosV: ndarray | None = None)[source]

Bases: object

One IMRT beam; port of IM.beams in initIMRTProblem.m.

beamNum: int = 1
beamModality: str = 'photons'
beamEnergy: float = 18
isocenter: Isocenter
isodistance: float = 100.0
arcAngle: float = 0.0
couchAngle: float = 0.0
collimatorAngle: float = 0.0
gantryAngle: float = 0.0
beamDescription: str = 'IMRT beam'
beamletDelta_x: float = 1.0
beamletDelta_y: float = 1.0
dateOfCreation: str
beamType: str = 'IM'
zRel: float = 0.0
xRel: float = 0.0
yRel: float = 0.0
sigma_100: float = 0.4
beamUID: str
beamlets: list
RTOGPBVectorsM: ndarray | None = None
xPBPosV: ndarray | None = None
yPBPosV: ndarray | None = None
sourcePos()[source]

Return absolute source position [x, y, z] (cm).

class cerr.imrtp.imrtp_problem.IMGoal(structNum: int = 1, strUID: str = '', structName: str = '', isTarget: str = 'no', PBMargin: float = 0.0, xySampleRate: int = 2)[source]

Bases: object

One structure entry (“goal”); port of IM.goals.

structNum: int = 1
strUID: str = ''
structName: str = ''
isTarget: str = 'no'
PBMargin: float = 0.0
xySampleRate: int = 2
is_target() bool[source]
class cerr.imrtp.imrtp_problem.ScatterParams(Threshold: 'float' = 0.01, RandomStep: 'float' = 30)[source]

Bases: object

Threshold: float = 0.01
RandomStep: float = 30
class cerr.imrtp.imrtp_problem.VMCParams(NumParticles: 'int' = 50000, NumBatches: 'int' = 10, scoreDoseToWater: 'str' = 'Yes', includeError: 'str' = 'No', monoEnergy: 'Optional[float]' = None, spectrum: 'str' = '', repeatHistory: 'float' = 0.251, splitPhotons: 'str' = 'Yes', photonSplitFactor: 'int' = -40, base: 'int' = 2, dimension: 'int' = 60, skip: 'int' = 1)[source]

Bases: object

NumParticles: int = 50000
NumBatches: int = 10
scoreDoseToWater: str = 'Yes'
includeError: str = 'No'
monoEnergy: float | None = None
spectrum: str = ''
repeatHistory: float = 0.251
splitPhotons: str = 'Yes'
photonSplitFactor: int = -40
base: int = 2
dimension: int = 60
skip: int = 1
class cerr.imrtp.imrtp_problem.IMParams(algorithm: str = 'QIB', DoseTerm: str = 'GaussPrimary+scatter', ScatterMethod: str = 'exponential', Scatter: ScatterParams = <factory>, xyDownsampleIndex: int = 1, numCTSamplePts: int = 300, cutoffDistance: float = 4, VMC: VMCParams = <factory>)[source]

Bases: object

Dose-calculation parameters; port of IM.params.

algorithm: str = 'QIB'
DoseTerm: str = 'GaussPrimary+scatter'
ScatterMethod: str = 'exponential'
Scatter: ScatterParams
xyDownsampleIndex: int = 1
numCTSamplePts: int = 300
cutoffDistance: float = 4
VMC: VMCParams
class cerr.imrtp.imrtp_problem.IMSolution(beamletWeights: ndarray | None = None, doseScale: float = 1.0, doseArray: ndarray | None = None, solutionDescription: str = '')[source]

Bases: object

Optimization output placeholder; port of IM.solutions.

beamletWeights: ndarray | None = None
doseScale: float = 1.0
doseArray: ndarray | None = None
solutionDescription: str = ''
class cerr.imrtp.imrtp_problem.IMRTProblem(name: str = 'IM doseSet 1', beams: List[IMBeam] = <factory>, goals: List[IMGoal] = <factory>, params: IMParams = <factory>, solutions: List[IMSolution] = <factory>, assocScanUID: str = '', isFresh: bool = True, IMUID: str = <factory>)[source]

Bases: object

The IM problem container; port of the Matlab IM struct.

name: str = 'IM doseSet 1'
beams: List[IMBeam]
goals: List[IMGoal]
params: IMParams
solutions: List[IMSolution]
assocScanUID: str = ''
isFresh: bool = True
IMUID: str
assocScanNum(planC) int | None[source]
targetGoals() List[IMGoal][source]
clearBeamlets()[source]

Invalidate all computed beamlets (geometry/goals changed).

cerr.imrtp.imrtp_problem.initIMRTProblem(planC=None) IMRTProblem[source]

Create a fresh, empty IMRT problem (port of initIMRTProblem.m).

Parameters:

planC – optional pyCERR plan container. When given, the problem is associated with the first scan and named IM doseSet n+1.

cerr.imrtp.imrtp_problem.createDefaultBeam(beamNum: int, planC=None, scanNum: int = 0) IMBeam[source]

Create a beam populated with GUI defaults (port of createDefaultBeam).

cerr.imrtp.imrtp_problem.resolveIsocenter(beam: IMBeam, im: IMRTProblem, planC) Isocenter[source]

Resolve ‘COM’ isocenter coordinates from the targets’ center of mass.

cerr.imrtp.imrtp_problem.conditionBeam(beam: IMBeam, im: IMRTProblem, planC, autoFields=None) IMBeam[source]

Fill in auto-computed beam fields (port of conditionBeam).

Auto fields (when enabled): beamNum is left as set by the caller, isocenter is resolved from target COM, dateOfCreation refreshed, and the relative source position is derived from the gantry angle:

xRel = isodistance * sin(gantry) yRel = isodistance * cos(gantry) zRel = 0

cerr.imrtp.imrtp_problem.addEquispacedBeams(im: IMRTProblem, numBeams: int, startAngle: float, planC=None, templateBeam: IMBeam | None = None)[source]

Add numBeams equispaced beams starting at startAngle degrees (port of the ‘NEWEQUISPACED’ GUI command).

cerr.imrtp.imrtp_problem.addGoal(im: IMRTProblem, relStrNum: int, planC) IMGoal[source]

Append a goal for the relStrNum-th structure (0-based, relative to the structures associated with im.assocScanUID); port of ‘ADDGOAL’.

cerr.imrtp.imrtp_problem.getIMList(planC) list[source]

Return the list of IM problems stored on the plan container, creating it if necessary (pyCERR’s PlanC has no native im attribute yet).

cerr.imrtp.imrtp_problem.saveIMToPlan(im: IMRTProblem, planC, index: int | None = None) int[source]

Store im on planC; append when index is None, else overwrite.

Returns the (0-based) index at which the problem was stored.

cerr.imrtp.imrtp_problem.asDict(im: IMRTProblem) dict[source]

JSON/pickle-friendly representation of the problem.

Module contents

cerr.imrtp

IMRT planning (IMRTP) module for pyCERR - Python port of Matlab CERR’s IMRTP directory (IMRTPGui.m, initIMRTProblem.m et al.).

Quick start:

from cerr import plan_container as pc
from cerr.imrtp import IMRTPGui

planC = pc.loadDcmDir(r'C:/data/myPlan')
IMRTPGui(planC)

After pressing Go with a Recompute action selected, the resulting dose is appended to planC.dose and the IM problem is stored on planC.im.

Dose engines: the QIB photon pencil-beam algorithm is ported in cerr.imrtp.dosecalc (with DPM / VMC++ Monte-Carlo placeholders) and registered automatically. Register additional engines with cerr.imrtp.imrtp.registerEngine(); a self-contained 'PB-Demo' engine is included for end-to-end testing.

cerr.imrtp.IMRTPGui(*args, **kwargs)[source]

Lazy wrapper so that headless installs can import cerr.imrtp without Qt being present.