Patients and studies

The Patient and Study classes allow multiple images, structure sets, doses and plans associated with a single patient to be read into one object. These classes can load DICOM and/or NIfTI files organised as described below (sorted data), and can load arbitrarily organised DICOM files (unsorted data). In the cases of both sorted and unsorted data, all files relating to a single patient should be placed under a separate directory, the name of which is taken as the patient’s identifier.

Sorted data

Patient and study directories

The top level directory represents the entire patient.

The next one or two levels represent studies. A study is identified by a directory whose name is a timestamp, which is a string with format YYYYMMDD_hhmmss. These directories can either appear within the patient directory, or be nested in a further level of directories, for example if you wished to separate groups of studies.

A valid file structure could look like this:

mypatient1
--- 20200416_120350
--- 20200528_100845

This would represent a patient with ID mypatient1, with two studies, one taken on 16/04/2020 and one taken on 28/05/2020.

Another valid file structure could be:

mypatient1
--- planning
------ 20200416_120350
------ 20200528_100845
--- relapse
------ 20211020_093028

This patient would have three studies, two in the “planning” category and two in the “relapse” category.

Files within a study

Each study can contain images of various imaging modalities, and associated structure sets. Within a study directory, there can be three “special” directories, named RTSTRUCT, RTDOSE, and RTPLAN, containing structure sets, dose fields, and radiotherapy plans, respectively.

Any other directories within the study directory are taken to represent an imaging modality. The structure sets associated with this modality should be nested inside the RTSTRUCT directory inside directories with the same name as the image directories. The images and structure sets themselves should be further nested inside a timestamp directory representing the time at which that image was taken.

For example, if a study containined both CT and MR images, as well as two structure sets associated with the CT image, the file structure should be as follows:

20200416_120350
--- CT
------ 20200416_120350
--------- 1.dcm
--------- 2.dcm ... etc
--- MR
------ 20200417_160329
--------- 1.dcm
--------- 2.dcm ... etc
--- RTSTRUCT
------ CT
---------- 20200416_120350
-------------- RTSTRUCT_20200512_134729.dcm
-------------- RTSTRUCT_20200517_162739.dcm

Patient class

A Patient object is created by providing the path to the top-level patient directory, and indicating if the data are unsorted DICOM data:

from skrt import Patient
p = Patient('mypatient1') # load sorted data
p = Patient('mypatient2', unsorted_dicom=True) # load unsorted DICOM data

A list of the patient’s associated studies is stored in p.studies.

Additional properties can be accessed:

  • Patient ID: p.id

  • Patient sex: p.get_sex()

  • Patient age: p.get_age()

  • Patient birth date: p.get_birth_date()

Sorted and unsorted DICOM files

If a patient’s data consists of sorted DICOM files, the data may be read both with unsorted_dicom=False (default) and with unsorted_dicom=True. Loading of sorted data tends to be faster, with object linking (for example, association of a structure set with an image) based on file organisation. When loading unsorted DICOM files, object linking is based on unique identifiers and references that the files contain.

Unsorted DICOM files can be copied as sorted files:

from skrt import Patient
# Load unsorted DICOM files.
p = Patient('mypatient2', unsorted_dicom=True)
# Write sorted DICOM files to sub-directory mypatient2 of directory my_sorted_dicom.
p.copy_dicom('my_sorted_dicom')

Study class

A Study object stores images and structure sets. A list of studies can be extracted from a Patient object via the property Patient.studies. You can access the study’s path via Study.path. If the study was nested inside a subdirectory, the name of that subdirectory is accessed via Study.subdir.

Images

For each imaging modalitiy subdirectory inside the study, a new class property will be created to contain a list of images of that modality, called {modality}_images, where the modality is taken from the subdirectory name (note, this is always converted to lower case). E.g. if there were directories called CT and MR, the Study object would have properties ct_images and mr_images containing lists of Image objects.

Structure sets

The study’s structure sets can be accessed in two ways. Firstly, the structure sets associated with an image can be extracted from the structure_sets property of the Image itself; this is a list of StructureSet objects. E.g. to get the newest structure set for the oldest CT image in the oldest study, you could run:

p = Patient('mypatient1')
s = p.studies[0]
structure_set = s.ct_images[0].structure_sets[-1]

In addition, structure sets associated with each imaginging modality will be stored in a property of the Study object called {modality}_structure_sets. E.g. to get the oldest CT-related structure set, you could run:

structure_set = s.ct_structure_sets[0]

The StructureSet object also has an associated image property (structure_set.image), which can be used to find out which Image is associated with that structure set.

Treatment plans

Data from radiotherapy treatment plans are made available as Plan objects. Study objects group Plan objects in lists with names of the form <machine>_plans, where <machine> identifies the treatment machine to which the plan relates. Image objects and StructureSet objects group together all plans associated with them, in a list plans. A Dose object is only ever derived from a single plan, referenced as plan. Different ways of accessing a Plan object are shown below.

# Access plan from Study.
plan = s.la3_plans[0]

# Access plan from Image to which it relates.
plan = s.ct_images[0].plans[0]

# Access plan from StructureSet to which it relates.
plan = s.ct_structure_sets[0].plans[0]

# Access plan from Dose (each of which can relate to only a single plan).
plan = s.ct_doses[0].plan

Planning information that can then be accessed include the following:

# Obtain plan name.
name = plan.get_name()

# Obtain plan approval status.
status = plan.get_approval_status()

# Obtain planned number of treatment fractions.
n_fraction = plan.get_n_fraction()

# Obtain planned dose to target (i.e. tumour).
target_dose = plan.get_target_dose()

# Obtain list of ROI objects defined as targets.
targets = plan.get_targets()

# For any target, obtain the planning constraints and weight.
target_constraint_weight = targets[0].constraint.weight
target_prescription_dose = targets[0].constraint.prescription_dose
target_maximum_dose = targets[0].constraint.maximum_dose
target_minimum_dose = targets[0].constraint.minimum_dose
target_underdose_volume_fraction = targets[0].constraint.underdose_volume_fraction

# Obtain list of ROI objects defined as organs at risk.
oars = plan.get_organs_at_risk()

# For any organ at risk, obtain the planning constraints and weight.
oar_constraint_weight = oars[0].constraint.weight
oar_maximum_dose = oars[0].constraint.maximum_dose
oar_full_volume_dose = oars[0].constraint.full_volume_dose
oar_overdose_volume_fraction = oars[0].constraint.overdose_volume_fraction

# For any target or organ at risk, obtain a Dose object, representing
# a dose objective passed to the optimiser.  Allowed values for the
# objective correspond to the constraints and weights.
# Data of the resulting object may be visualised in the same way
# as for any other Dose object.
max_dose = plan.get_dose_objective('maximum_dose')
max_dose.view()

The following is an Example plot of maximum-dose objective.

Plot of maximum-dose objective