# 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 `_plans`, where `` 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