"""beams module.
The beams module defines metadata for RT Plan (RTPLAN).
The metadata are attributes of the Beams class.
"""
from dataclasses import dataclass, field
import numpy as np
from pydicom import dcmread
from cerr.utils import uid
import json
def get_empty_list():
return []
def get_empty_np_array():
return np.empty((0,0,0))
[docs]
@dataclass
class Beams:
"""This class defines data object for Beams in an RTPlan. The metadata is populated from DICOM.
Attributes:
PatientName (str): Patient's name
PatientID (str): Patient's ID
PatientBirthDate (str): Patient's birth date
PatientSex (str): Patient's gender
Manufacturer (str): Equipment manufacturer
ManufacturerModelName (str): Equipment model
PixelPaddingValue (float): Pixel padding value as defined in (0028,0120) DICOM tag
RTPlanLabel (str): User-defined label for treatment plan as defined by (300A,0002) DICOM tag
RTPlanDate (str): Date treatment plan was last modified as defined by (300A,0006) DICOM tag
RTPlanTime (str): Time treatment plan was last modified as defined by (300A,0007) DICOM tag
RTPlanGeometry (str): Describes whether RT Plan is based on patient or treatment device geometry (300A,000C) tag
TreatmentSites (np.array): A free-text label describing the anatomical treatment site. (3010,0077) tag
PrescriptionDescription (str): User-defined description of treatment prescription as defined in (300A,000E)
FractionGroupSequence (np.array): Sequence of Fraction Groups as per (300A,0070) DICOM tag
BeamSequence (np.array): Sequence of treatment beams for current RT Plan as per (300A,00B0) DICOM tag
PatientSetupSequence (np.array): Sequence of patient setup data for current plan as per (300A,0180) DICOM tag
ReferencedStructureSetSequence (np.array): The RT Structure Set on which the RT Plan is based as per (300C,0060) DICOM tag
ReferencedDoseSequence (np.array): Sequence of Dose References.
ApprovalStatus (str): Approval status at the time the SOP Instance was created as per (300E,0002) DICOM tag.
ReviewDate (str): Date plan was reviewed
ReviewTime (str): Time plan was reviewed
ReviewerName (str): Reviewer name
SOPInstanceUID (str): SOP Instance UID of the Plan
BeamUID (str): pyCERR's UID of RTPLAN
"""
PatientName: str = ""
PatientID: str = ""
PatientBirthDate: str = ""
PatientSex: str = ""
Manufacturer: str = ""
ManufacturerModelName: str = ""
RelationshipGroupLength: int = 0
ImagePresentationGroupLength: int = 0
PixelPaddingValue: float = 0
PlanGroupLength: int = 0
RTPlanLabel: str = ""
RTPlanDate: str = ""
RTPlanTime: str = ""
RTPlanGeometry: str = ""
TreatmentSites: np.array = field(default_factory=get_empty_np_array)
PrescriptionDescription: str = ""
DoseReferenceSequence: np.array = field(default_factory=get_empty_np_array)
FractionGroupSequence: np.array = field(default_factory=get_empty_np_array)
BeamSequence: np.array = field(default_factory=get_empty_np_array)
PatientSetupSequence: np.array = field(default_factory=get_empty_np_array)
ReferencedRTGroupLength: int = 0
ReferencedStructureSetSequence: np.array = field(default_factory=get_empty_np_array)
ReferencedDoseSequence: np.array = field(default_factory=get_empty_np_array)
ReviewGroupLength: int = 0
ApprovalStatus: str = ""
ReviewDate: str = ""
ReviewTime: str = ""
ReviewerName: str = ""
SOPInstanceUID: str = ""
BeamUID: str = ""
[docs]
@dataclass
class ReferenceSeq:
"""This class defines referenced sequence.
Attributes:
ReferencedSOPClassUID (str): Referenced SOP class UID
ReferencedSOPInstanceUID (str): Referenced SOP instance UID
"""
ReferencedSOPClassUID: str = ""
ReferencedSOPInstanceUID: str = ""
[docs]
@dataclass
class PatientSetupSeq:
"""This class defines patient setup sequence.
Attributes:
PatientSetupNumber (int): Identification number of the Patient Setup as per (300A,0182) DICOM tag
PatientPosition (str): Patient position descriptor relative to the equipment as per (0018,5100) DICOM tag
"""
PatientSetupNumber: int = 0
PatientPosition: str = ""
[docs]
@dataclass
class BeamLimitingDevicePositionSeq:
"""This class defines data model for Sequence of beam limiting device (collimator) jaw or leaf (element) positions.
Attributes:
RTBeamLimitingDeviceType (str): Type of beam limiting device (collimator) as per (300A,00B8) DICOM tag
LeafJawPositions (np.array): Positions of beam limiting device (collimator) leaf (element) or jaw pairs (in mm)
in IEC BEAM LIMITING DEVICE coordinate axis appropriate to RT Beam Limiting Device Type as defined in (300A,011C) DICOM tag
"""
RTBeamLimitingDeviceType: str = ""
LeafJawPositions: np.array = field(default_factory=get_empty_np_array)
[docs]
@dataclass
class ControlPointSequence:
"""This class defines data model for the Sequence of machine configurations describing treatment beam.
Attributes:
BeamLimitingDevicePositionSequence (np.array): Sequence of beam limiting device (collimator) jaw or leaf (element)
positions as per (300A,011A) DICOM tag.
ControlPointIndex (int): Index of current Control Point, starting at 0 for first Control Point as per (300A,0112) DICOM tag.
NominalBeamEnergy (float): Nominal Beam Energy at control point (MV/MeV) as per (300A,0114) DICOM tag
GantryAngle (float): Gantry angle of radiation source, i.e., orientation of IEC GANTRY coordinate system with respect to
IEC FIXED REFERENCE coordinate system (degrees) as per (300A,011E) DICOM tag.
GantryRotationDirection (str): Direction of Gantry Rotation when viewing gantry from isocenter,
for segment following Control Point as per (300A,011F) DICOM tag.
BeamLimitingDeviceAngle (float): Beam Limiting Device angle, i.e., orientation of IEC BEAM LIMITING DEVICE coordinate
system with respect to IEC GANTRY coordinate system (degrees) as per (300A,0120) DICOM tag.
BeamLimitingDeviceRotationDirection (str): Direction of Beam Limiting Device Rotation when viewing beam limiting device
(collimator) from radiation source, for segment following Control Point as per (300A,0121) DICOM tag
PatientSupportAngle (float): Patient Support angle, i.e., orientation of IEC PATIENT SUPPORT (turntable) coordinate
system with respect to IEC FIXED REFERENCE coordinate system (degrees) as per (300A,0122) DICOM tag
TableTopEccentricAngle (float): Table Top (non-isocentric) angle, i.e., orientation of IEC TABLE TOP ECCENTRIC coordinate
system with respect to IEC PATIENT SUPPORT coordinate system (degrees) as per (300A,0125) DICOM tag.
TableTopEccentricRotationDirection (str):
IsocenterPosition (np.array): Direction of Table Top Eccentric Rotation when viewing table from above,
for segment following Control Point as per (300A,0126) DICOM tag
SourceToSurfaceDistance (float): Source to Patient Surface (skin) distance (mm) as per (300A,0130) DICOM tag
CumulativeMetersetWeight (float): Cumulative weight to current control point as per (300A,0134) DICOM tag
"""
BeamLimitingDevicePositionSequence: np.array = field(default_factory=get_empty_np_array)
ControlPointIndex: int = 0
NominalBeamEnergy: float = 0
GantryAngle: float = 0
GantryRotationDirection: str = ""
BeamLimitingDeviceAngle: float = 0
BeamLimitingDeviceRotationDirection: str = ""
PatientSupportAngle: float = 0
TableTopEccentricAngle: float = 0
TableTopEccentricRotationDirection: str = ""
IsocenterPosition: np.array = field(default_factory=get_empty_np_array)
SourceToSurfaceDistance: float = 0
CumulativeMetersetWeight: float = 0
[docs]
@dataclass()
class RefBeamSeq:
"""This class defies data model for the sequence of Beams in current Fraction Group contributing to dose
as per (300C,0004) DICOM tag.
Attributes:
ReferencedBeamNumber (int): Uniquely identifies Beam specified by Beam Number as per (300C,0006) DICOM tag
BeamMeterset (float): Meterset duration over which image is to be acquired, specified in Monitor units (MU)
as per (3002,0032) DICOM tag
"""
ReferencedBeamNumber: int = 0
BeamMeterset: float = 0
[docs]
@dataclass
class FractionGroupSeq:
"""This class defines data model for Sequence of Fraction Groups in current Fraction Scheme as per (300A,0070) DICOM tag.
Attributes:
FractionGroupNumber (int): Identification number of the Fraction Group as per (300A,0071)
NumberOfFractionsPlanned (int): Total number of treatments (Fractions) prescribed for current Fraction Group as per (300A,0078)
NumberOfBeams (int): Number of Beams in current Fraction Group as per (300A,0080)
NumberOfBrachyApplicationSetups (int): Number of Brachy Application Setups in current Fraction Group as per (300A,00A0)
RadiationType (str): Particle type of Beam as per (300A,00C6)
RefBeamSeq (np.array): Sequence of Beams in current Fraction Group contributing to dose as per (300C,0004)
"""
FractionGroupNumber: int = 0
NumberOfFractionsPlanned: int = 0
NumberOfBeams: int = 0
NumberOfBrachyApplicationSetups: int = 0
RadiationType: str = ""
RefBeamSeq: np.array = field(default_factory=get_empty_np_array)
[docs]
@dataclass
class BeamSeq:
"""This class defines data model for Sequence of treatment beams for current RT Plan as per (300A,00B0) DICOM tag.
Attributes:
Manufacturer (str): Manufacturer of the equipment to be used for beam delivery as per (0008,0070)
BeamName (str): primary beam identifier (often referred to as "field identifier") as per (300A,00C2)
BeamType (str): Motion characteristic of Beam as per (300A,00C4)
BeamDescription (str): User-defined description for Beam as per (300A,00C3)
BeamNumber (int): Identification number of the Beam as per (300A,00C0)
SourceAxisDistance (float): Radiation source to Gantry rotation axis distance of the equipment that is to be
used for beam delivery (mm) as per (300A,00B4)
BeamLimitingDevicePositionSeq (np.array): Sequence of beam limiting device (collimator) jaw or leaf (element) sets
as per (300A,00B6)
RadiationType (str): Particle type of Beam as per (300A,00C6)
TreatmentDeliveryType (str): Delivery Type of treatment as per (300A,00CE)
NumberOfWedges (float): Number of wedges associated with current Beam as per (300A,00D0)
NumberOfBoli (float): Number of boli associated with current Beam as per (300A,00ED)
NumberOfCompensators (float): Number of compensators associated with current Beam as per (300A,00E0)
NumberOfBlocks (float): Number of shielding blocks associated with Beam as per (300A,00F0)
NumberOfControlPoints (float): Number of control points in Beam as per (300A,0110)
ControlPointSequence (np.array): Sequence of machine configurations describing treatment beam as per (300A,0111)
"""
Manufacturer: str = ""
BeamName: str = ""
BeamType: str = ""
BeamDescription: str = ""
BeamNumber: int = 0
SourceAxisDistance: float = 0
BeamLimitingDevicePositionSeq: np.array = field(default_factory=get_empty_np_array)
RadiationType: str = ""
TreatmentDeliveryType: str = ""
NumberOfWedges: float = 0
NumberOfBoli: float = 0
NumberOfCompensators: float = 0
NumberOfBlocks: float = 0
NumberOfControlPoints: float = 0
ControlPointSequence: np.array = field(default_factory=get_empty_np_array)
class json_serialize(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Beams):
return {'beams':obj.BeamUID}
return "" #json.JSONEncoder.default(self, obj)
[docs]
def load_beams(file_list):
"""This routine parses a list of DICOM files and imports metadata from RTPLAN modality into a list of
pyCERR's Beams objects
Args:
file_list (List[str]): List of DICOM file paths.
Returns:
List[cerr.dataclasses.beams.Beams]: List of pyCERR's Beam objects.
"""
beams_list = []
for file in file_list:
ds = dcmread(file)
if ds.Modality == "RTPLAN":
beams_meta = Beams()
beams_meta.patientName = ds.PatientName
beams_meta.PatientID = ds.PatientID
beams_meta.PatientBirthDate = ds.PatientBirthDate
beams_meta.PatientSex = ds.PatientSex
beams_meta.SOPInstanceUID = ds.SOPInstanceUID
ref_dose_seq_list = np.array([],dtype=ReferenceSeq)
ref_str_seq_list = np.array([],dtype=ReferenceSeq)
if hasattr(ds,"ReferencedStructureSetSequence"):
#ref_str_seq_list = np.array([],dtype=ReferenceSeq)
refStrSetSeq = ReferenceSeq()
refStrSetSeq.ReferencedSOPClassUID = ds.ReferencedStructureSetSequence[0].ReferencedSOPClassUID
refStrSetSeq.ReferencedSOPInstanceUID = ds.ReferencedStructureSetSequence[0].ReferencedSOPInstanceUID
ref_str_seq_list = np.append(ref_str_seq_list,refStrSetSeq)
beams_meta.ReferencedStructureSetSequence = ref_str_seq_list
if hasattr(ds,"ReferencedDoseSequence"):
#ref_dose_seq_list = np.array([],dtype=ReferenceSeq)
refDoseSetSeq = ReferenceSeq()
refDoseSetSeq.ReferencedSOPClassUID = ds.ReferencedDoseSequence[0].ReferencedSOPClassUID
refDoseSetSeq.ReferencedSOPInstanceUID = ds.ReferencedDoseSequence[0].ReferencedSOPInstanceUID
ref_dose_seq_list = np.append(ref_dose_seq_list,refDoseSetSeq)
beams_meta.ReferencedDoseSequence = ref_dose_seq_list
if hasattr(ds,"PatientSetupSequence"):
patient_setup_seq_list = np.array([],dtype=PatientSetupSeq)
patientSetupSeq = PatientSetupSeq()
patientSetupSeq.PatientPosition = ds.PatientSetupSequence[0].PatientPosition
patientSetupSeq.PatientSetupNumber = ds.PatientSetupSequence[0].PatientSetupNumber
ref_dose_seq_list = np.append(patient_setup_seq_list,patientSetupSeq)
beams_meta.PatientSetupSequence = patient_setup_seq_list
if hasattr(ds,"FractionGroupSequence"):
fx_grp_list = np.array([])
for fx_grp in ds.FractionGroupSequence:
f = FractionGroupSeq()
f.NumberOfBeams = fx_grp.NumberOfBeams
f.NumberOfFractionsPlanned = fx_grp.NumberOfFractionsPlanned
f.NumberOfBeams = fx_grp.NumberOfBeams
f.NumberOfBrachyApplicationSetups = fx_grp.NumberOfBrachyApplicationSetups
if hasattr(fx_grp,"RadiationType"): f.RadiationType = fx_grp.RadiationType
ref_beam_seq = RefBeamSeq()
if hasattr(fx_grp,"ReferencedBeamSequence"):
if hasattr(fx_grp.ReferencedBeamSequence[0],"BeamMeterset"):
ref_beam_seq.BeamMeterset = fx_grp.ReferencedBeamSequence[0].BeamMeterset
ref_beam_seq.ReferencedBeamNumber = fx_grp.ReferencedBeamSequence[0].ReferencedBeamNumber
f.RefBeamSeq = ref_beam_seq
# add code to read ReferencedBrachyApplicationSetupSequence
fx_grp_list = np.append(fx_grp_list,f)
beams_meta.FractionGroupSequence = fx_grp_list
if hasattr(ds,"BeamSequence"):
beam_item_list = np.array([],dtype=BeamSeq)
num_beams = len(ds.BeamSequence)
for beam in ds.BeamSequence:
bs = BeamSeq()
bs.Manufacturer: beam.Manufacturer
bs.BeamName: beam.BeamName
bs.BeamType: beam.BeamType
bs.BeamDescription: beam.BeamDescription
bs.BeamNumber: beam.BeamNumber
bs.SourceAxisDistance: beam.SourceAxisDistance
limit_sevice_seq = np.array([])
for limitDevSeq in beam.BeamLimitingDeviceSequence:
seq = BeamLimitingDevicePositionSeq()
seq.RTBeamLimitingDeviceType = limitDevSeq.RTBeamLimitingDeviceType
if hasattr(limitDevSeq, "LeafJawPositions"):
seq.LeafJawPositions = limitDevSeq.LeafJawPositions
limit_sevice_seq = np.append(limit_sevice_seq,seq)
bs.BeamLimitingDevicePositionSeq = limit_sevice_seq
bs.RadiationType = beam.RadiationType
bs.TreatmentDeliveryType = beam.TreatmentDeliveryType
bs.NumberOfWedges = beam.NumberOfWedges
bs.NumberOfBoli = beam.NumberOfBoli
bs.NumberOfCompensators = beam.NumberOfCompensators
bs.NumberOfBlocks = beam.NumberOfBlocks
bs.NumberOfControlPoints = beam.NumberOfControlPoints
ctr_pt_seq = np.array([])
for ctr_pt in beam.ControlPointSequence:
c = ControlPointSequence()
beam_lim_pos_seq_list = np.array([])
if hasattr(ctr_pt, "BeamLimitingDevicePositionSequence"):
for lim_seq in ctr_pt.BeamLimitingDevicePositionSequence:
beam_lim_pos_seq = BeamLimitingDevicePositionSeq()
beam_lim_pos_seq.RTBeamLimitingDeviceType = lim_seq.RTBeamLimitingDeviceType
beam_lim_pos_seq.LeafJawPositions = lim_seq.LeafJawPositions
beam_lim_pos_seq_list = np.append(beam_lim_pos_seq_list,beam_lim_pos_seq)
c.BeamLimitingDevicePositionSequence = beam_lim_pos_seq_list
c.ControlPointIndex = ctr_pt.ControlPointIndex
if hasattr(ctr_pt, "NominalBeamEnergy"):
c.NominalBeamEnergy = ctr_pt.NominalBeamEnergy
if hasattr(ctr_pt, "GantryAngle"):
c.GantryAngle = ctr_pt.GantryAngle
if hasattr(ctr_pt, "GantryRotationDirection"):
c.GantryRotationDirection = ctr_pt.GantryRotationDirection
if hasattr(ctr_pt, "BeamLimitingDeviceAngle"):
c.BeamLimitingDeviceAngle = ctr_pt.BeamLimitingDeviceAngle
if hasattr(ctr_pt, "BeamLimitingDeviceRotationDirection"):
c.BeamLimitingDeviceRotationDirection = ctr_pt.BeamLimitingDeviceRotationDirection
if hasattr(ctr_pt, "PatientSupportAngle"):
c.PatientSupportAngle = ctr_pt.PatientSupportAngle
if hasattr(ctr_pt, "TableTopEccentricAngle"):
c.TableTopEccentricAngle = ctr_pt.TableTopEccentricAngle
if hasattr(ctr_pt, "TableTopEccentricRotationDirection"):
c.TableTopEccentricRotationDirection = ctr_pt.TableTopEccentricRotationDirection
if hasattr(ctr_pt, "IsocenterPosition"):
c.IsocenterPosition = ctr_pt.IsocenterPosition
if hasattr(ctr_pt, "SourceToSurfaceDistance"):
c.SourceToSurfaceDistance = ctr_pt.SourceToSurfaceDistance
if hasattr(ctr_pt, "CumulativeMetersetWeight"):
c.CumulativeMetersetWeight = ctr_pt.CumulativeMetersetWeight
ctr_pt_seq = np.append(ctr_pt_seq,c)
bs.ControlPointSequence = ctr_pt_seq
beam_item_list = np.append(beam_item_list,bs)
beams_meta.BeamSequence = beam_item_list
beams_meta.ReferencedDoseSequence = ref_dose_seq_list
if hasattr(ds,"ReviewerName"): beams_meta.ReviewerName = ds.ReviewerName
if hasattr(ds,"ReviewDate"): beams_meta.ReviewDate = ds.ReviewDate
if hasattr(ds,"ReviewTime"): beams_meta.ReviewTime = ds.ReviewTime
if hasattr(ds,"ApprovalStatus"): beams_meta.ApprovalStatus = ds.ApprovalStatus
if hasattr(ds,"PixelPaddingValue"): beams_meta.PixelPaddingValue = ds.PixelPaddingValue
if hasattr(ds,"Manufacturer"): beams_meta.Manufacturer = ds.Manufacturer
if hasattr(ds,"ManufacturerModelName"): beams_meta.ManufacturerModelName = ds.ManufacturerModelName
beams_meta.RTPlanLabel = ds.RTPlanLabel
beams_meta.RTPlanDate = ds.RTPlanDate
beams_meta.RTPlanDate = ds.RTPlanTime
beams_meta.RTPlanGeometry = ds.RTPlanGeometry
if hasattr(ds,"TreatmentSites"): beams_meta.TreatmentSites = ds.TreatmentSites
if hasattr(ds,"PrescriptionDescription"): beams_meta.PrescriptionDescription = ds.PrescriptionDescription
beams_meta.BeamUID = uid.createUID("beams")
beams_list = np.append(beams_list, beams_meta)
return beams_list