Skip to content

Dicom Reader

Master reader class to crawl through the directory and generate relevant DicomStudy and DicomSeries dataclasses

Source code in pygrpm/dicom/reader.py
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
class DicomReader:
    """
    Master reader class to crawl through the directory and generate relevant
    DicomStudy and DicomSeries dataclasses
    """

    def __init__(self, folderpath: Union[str, bytes, os.PathLike], verbose: int = 0):
        self.data_path = Path(folderpath)
        self.verbose = verbose
        # ensure we have an actual directory
        if not self.data_path.is_dir():
            raise NotADirectoryError(self.data_path)

        self._studies: List[DicomStudy] = []
        self._study_uids: List[str] = []
        self.dicoms: List[pydicom.FileDataset] = []

    @property
    def studies_UID(self) -> List[Union[str, pydicom.uid.UID]]:
        """
        property method to return a list of all instances uid within the series dataclass
        @return: An similarly ordered list of instances UID
        """
        return [study.study_UID for study in self.studies]

    @property
    def studies(self) -> List[DicomStudy]:
        """
        Property method to get an explicit list of the obtained studies
        @return: A list of DicomStudy dataclasses for the found studies
        """
        if len(self._studies) == 0:
            self.build_dicom_list()
            self.build_studies()

        return self._studies

    def yield_dicoms(self) -> Generator[pydicom.FileDataset, None, None]:
        """
        Generator utilized to obtain the pydicom FileDatasets within the data folder
        """
        for root, folders, files in os.walk(self.data_path):  # pylint: disable=unused-variable
            for file in files:
                # If file is not a proper dicom it will be set to None
                my_file = Path(root) / file
                my_dicom = self.load_dcm(my_file)

                # Append dataset to master list if appropriate
                if my_dicom:
                    yield my_dicom

    def build_dicom_list(self) -> List[pydicom.FileDataset]:
        """
        Method used to navigate through the class folder, ensure the files are dicom,
        then append the dicoms to a master list for further processing
        @return: The list of dicom datasets
        """
        for my_dicom in self.yield_dicoms():
            self.dicoms.append(my_dicom)

        return self.dicoms

    def load_dcm(
        self, filepath: Union[str, os.PathLike]
    ) -> Union[pydicom.FileDataset, None]:
        """
        Simple wrapper method to cleanly handle non-dicom files within the provided folder
        @param filepath: path to the potential dicom file
        @return: The pydicom FileDataset is appropriate, otherwise None
        """
        try:
            return pydicom.read_file(filepath)
        except (InvalidDicomError, TypeError):
            if self.verbose > 0:
                print(f"File {filepath} was not a dicom file, skipping.")
            return None

    def build_studies(self) -> List[DicomStudy]:
        """
        Main method used to buildup all the studies and series from the given folder
        @return: A -> List of studies as obtained from the folder
        """
        # Initial sanity check and warning
        if len(self.dicoms) < 1:
            warnings.warn("Attempted to build study with no dicoms loaded.")

        for dcm in self.dicoms:
            # Generate or load current dicom's Study
            if len(self._studies) == 0 or dcm.StudyInstanceUID not in self._study_uids:
                study = self.make_study(dcm)

                # Add the study to the class
                self._studies.append(study)
                self._study_uids.append(dcm.StudyInstanceUID)
            else:
                # get proper study
                study = self.get_study(dcm.StudyInstanceUID)

            # Generate or load current dicom's Serie
            if len(study.series) == 0 or dcm.SeriesInstanceUID not in study.series_UID:
                series = self.make_series(dcm)
            else:
                series = study.get_series(dcm.SeriesInstanceUID)

            # Add instance in series if not exist
            series = self.add_instance_in_series(series, dcm)
            # Add or update series in study
            self.add_or_update_series_in_study(study, series)

        return self._studies

    @staticmethod
    def add_or_update_series_in_study(
        study: DicomStudy, series: DicomSeries
    ) -> DicomStudy:
        """
        Method will either append series to the study if it is not already present.
        Should the series already exist in the study it will be updated
        @param study: The DicomStudy to be updated
        @param series: The DicomSeries to place/update within the study
        @return: The updated DicomStudy
        """
        # check if this serie is already in the study
        existing_series = [s for s in study.series if s.series_UID == series.series_UID]

        if len(existing_series) > 0:
            # Determine position in list, and update
            idx = study.series_UID.index(series.series_UID)
            study.series[idx] = series
        else:
            # Append to lists if the series does not already exist
            study.series.append(series)
            study.series_UID.append(series.series_UID)

        return study

    @staticmethod
    def add_instance_in_series(
        series: DicomSeries, dicom: pydicom.FileDataset
    ) -> DicomSeries:
        """
        Method used to add the dicom instance into the series should it not already be present
        @param series: The DicomSeries to be updated
        @param dicom: The pydicom filedataset containing the dicom instance
        @return: The updated DataSeries
        """

        # Sanity checks, perhaps should not raise errors
        if dicom.Modality != series.modality:
            raise ValueError(
                f"Dicom modality does not correspond with series modality, "
                f"{dicom.filename}"
            )
        if dicom.SeriesInstanceUID != series.series_UID:
            raise ValueError(
                f"Dicom SeriesInstanceUID does not correspond with series UID, "
                f"{dicom.filename}"
            )
        if dicom.StudyInstanceUID != series.study_UID:
            raise ValueError(
                f"Dicom StudyInstanceUID does not correspond with study UID, "
                f"{dicom.filename}"
            )

        # Check if this instance is already in this series
        for instance in series.instances:
            if instance.SOPInstanceUID == dicom.SOPInstanceUID:
                # If the instance was already in the series, leave it alone
                return series

        # Update and return if it wasn't in there
        series.instances.append(dicom)
        return series

    def get_study(self, study_UID: Union[str, pydicom.uid.UID]) -> Union[DicomStudy, None]:
        """
        Helper method to obtain a DicomStudy class from its StudyInstanceUID
        @param study_UID: StudyInstanceUID
        @return: The DicomStudy dataclass matching the provided UID
        """
        for study in self.studies:
            if study.study_UID == study_UID:
                return study

        return None

    def get_series(self, study: Union[DicomStudy, str, pydicom.uid.UID],
                   series_UID: Union[str, pydicom.uid.UID]) -> Union[DicomSeries, None]:
        """
        Helper method to obtain a DicomSeries class from its DicomStudy
        @param study: The reference DicomStudy dataclass or associated UID
        @param series_UID: SeriesInstanceUID
        @return: The DicomSeries dataclass matching the provided UID
        """
        if type(study) in [str, pydicom.uid.UID]:
            study = self.get_study(study)

        for series in study.series:
            if series.series_UID == series_UID:
                return series

        return None

    @staticmethod
    def make_study(dicom: pydicom.FileDataset) -> DicomStudy:
        """
        Method to create a DicomStudy dataclass based on the provided dicom dataset
        @param dicom: The reference dicom dataset
        @return: The DicomStudy dataclass
        """
        study = DicomStudy(
            study_UID=dicom.StudyInstanceUID,
            patientID=dicom.PatientID,
            series=[],
            reference_folder_paths=[Path(dicom.filename).parents[0]],
        )

        return study

    @staticmethod
    def make_series(dicom: pydicom.FileDataset) -> DicomSeries:
        """
        Method to create a DicomSeries dataclass based on the provided dicom dataset
        @param dicom: The reference dicom dataset
        @return: The DicomSeries dataclass
        """
        series = DicomSeries(
            series_UID=dicom.SeriesInstanceUID,
            study_UID=dicom.StudyInstanceUID,
            patientID=dicom.PatientID,
            modality=dicom.Modality,
            instances=[dicom],
            reference_folder_path=Path(dicom.filename).parents[0],
        )

        return series

studies: List[DicomStudy] property

Property method to get an explicit list of the obtained studies @return: A list of DicomStudy dataclasses for the found studies

studies_UID: List[Union[str, pydicom.uid.UID]] property

property method to return a list of all instances uid within the series dataclass @return: An similarly ordered list of instances UID

add_instance_in_series(series, dicom) staticmethod

Method used to add the dicom instance into the series should it not already be present @param series: The DicomSeries to be updated @param dicom: The pydicom filedataset containing the dicom instance @return: The updated DataSeries

Source code in pygrpm/dicom/reader.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
@staticmethod
def add_instance_in_series(
    series: DicomSeries, dicom: pydicom.FileDataset
) -> DicomSeries:
    """
    Method used to add the dicom instance into the series should it not already be present
    @param series: The DicomSeries to be updated
    @param dicom: The pydicom filedataset containing the dicom instance
    @return: The updated DataSeries
    """

    # Sanity checks, perhaps should not raise errors
    if dicom.Modality != series.modality:
        raise ValueError(
            f"Dicom modality does not correspond with series modality, "
            f"{dicom.filename}"
        )
    if dicom.SeriesInstanceUID != series.series_UID:
        raise ValueError(
            f"Dicom SeriesInstanceUID does not correspond with series UID, "
            f"{dicom.filename}"
        )
    if dicom.StudyInstanceUID != series.study_UID:
        raise ValueError(
            f"Dicom StudyInstanceUID does not correspond with study UID, "
            f"{dicom.filename}"
        )

    # Check if this instance is already in this series
    for instance in series.instances:
        if instance.SOPInstanceUID == dicom.SOPInstanceUID:
            # If the instance was already in the series, leave it alone
            return series

    # Update and return if it wasn't in there
    series.instances.append(dicom)
    return series

add_or_update_series_in_study(study, series) staticmethod

Method will either append series to the study if it is not already present. Should the series already exist in the study it will be updated @param study: The DicomStudy to be updated @param series: The DicomSeries to place/update within the study @return: The updated DicomStudy

Source code in pygrpm/dicom/reader.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
@staticmethod
def add_or_update_series_in_study(
    study: DicomStudy, series: DicomSeries
) -> DicomStudy:
    """
    Method will either append series to the study if it is not already present.
    Should the series already exist in the study it will be updated
    @param study: The DicomStudy to be updated
    @param series: The DicomSeries to place/update within the study
    @return: The updated DicomStudy
    """
    # check if this serie is already in the study
    existing_series = [s for s in study.series if s.series_UID == series.series_UID]

    if len(existing_series) > 0:
        # Determine position in list, and update
        idx = study.series_UID.index(series.series_UID)
        study.series[idx] = series
    else:
        # Append to lists if the series does not already exist
        study.series.append(series)
        study.series_UID.append(series.series_UID)

    return study

build_dicom_list()

Method used to navigate through the class folder, ensure the files are dicom, then append the dicoms to a master list for further processing @return: The list of dicom datasets

Source code in pygrpm/dicom/reader.py
70
71
72
73
74
75
76
77
78
79
def build_dicom_list(self) -> List[pydicom.FileDataset]:
    """
    Method used to navigate through the class folder, ensure the files are dicom,
    then append the dicoms to a master list for further processing
    @return: The list of dicom datasets
    """
    for my_dicom in self.yield_dicoms():
        self.dicoms.append(my_dicom)

    return self.dicoms

build_studies()

Main method used to buildup all the studies and series from the given folder @return: A -> List of studies as obtained from the folder

Source code in pygrpm/dicom/reader.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def build_studies(self) -> List[DicomStudy]:
    """
    Main method used to buildup all the studies and series from the given folder
    @return: A -> List of studies as obtained from the folder
    """
    # Initial sanity check and warning
    if len(self.dicoms) < 1:
        warnings.warn("Attempted to build study with no dicoms loaded.")

    for dcm in self.dicoms:
        # Generate or load current dicom's Study
        if len(self._studies) == 0 or dcm.StudyInstanceUID not in self._study_uids:
            study = self.make_study(dcm)

            # Add the study to the class
            self._studies.append(study)
            self._study_uids.append(dcm.StudyInstanceUID)
        else:
            # get proper study
            study = self.get_study(dcm.StudyInstanceUID)

        # Generate or load current dicom's Serie
        if len(study.series) == 0 or dcm.SeriesInstanceUID not in study.series_UID:
            series = self.make_series(dcm)
        else:
            series = study.get_series(dcm.SeriesInstanceUID)

        # Add instance in series if not exist
        series = self.add_instance_in_series(series, dcm)
        # Add or update series in study
        self.add_or_update_series_in_study(study, series)

    return self._studies

get_series(study, series_UID)

Helper method to obtain a DicomSeries class from its DicomStudy @param study: The reference DicomStudy dataclass or associated UID @param series_UID: SeriesInstanceUID @return: The DicomSeries dataclass matching the provided UID

Source code in pygrpm/dicom/reader.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
def get_series(self, study: Union[DicomStudy, str, pydicom.uid.UID],
               series_UID: Union[str, pydicom.uid.UID]) -> Union[DicomSeries, None]:
    """
    Helper method to obtain a DicomSeries class from its DicomStudy
    @param study: The reference DicomStudy dataclass or associated UID
    @param series_UID: SeriesInstanceUID
    @return: The DicomSeries dataclass matching the provided UID
    """
    if type(study) in [str, pydicom.uid.UID]:
        study = self.get_study(study)

    for series in study.series:
        if series.series_UID == series_UID:
            return series

    return None

get_study(study_UID)

Helper method to obtain a DicomStudy class from its StudyInstanceUID @param study_UID: StudyInstanceUID @return: The DicomStudy dataclass matching the provided UID

Source code in pygrpm/dicom/reader.py
193
194
195
196
197
198
199
200
201
202
203
def get_study(self, study_UID: Union[str, pydicom.uid.UID]) -> Union[DicomStudy, None]:
    """
    Helper method to obtain a DicomStudy class from its StudyInstanceUID
    @param study_UID: StudyInstanceUID
    @return: The DicomStudy dataclass matching the provided UID
    """
    for study in self.studies:
        if study.study_UID == study_UID:
            return study

    return None

load_dcm(filepath)

Simple wrapper method to cleanly handle non-dicom files within the provided folder @param filepath: path to the potential dicom file @return: The pydicom FileDataset is appropriate, otherwise None

Source code in pygrpm/dicom/reader.py
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def load_dcm(
    self, filepath: Union[str, os.PathLike]
) -> Union[pydicom.FileDataset, None]:
    """
    Simple wrapper method to cleanly handle non-dicom files within the provided folder
    @param filepath: path to the potential dicom file
    @return: The pydicom FileDataset is appropriate, otherwise None
    """
    try:
        return pydicom.read_file(filepath)
    except (InvalidDicomError, TypeError):
        if self.verbose > 0:
            print(f"File {filepath} was not a dicom file, skipping.")
        return None

make_series(dicom) staticmethod

Method to create a DicomSeries dataclass based on the provided dicom dataset @param dicom: The reference dicom dataset @return: The DicomSeries dataclass

Source code in pygrpm/dicom/reader.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
@staticmethod
def make_series(dicom: pydicom.FileDataset) -> DicomSeries:
    """
    Method to create a DicomSeries dataclass based on the provided dicom dataset
    @param dicom: The reference dicom dataset
    @return: The DicomSeries dataclass
    """
    series = DicomSeries(
        series_UID=dicom.SeriesInstanceUID,
        study_UID=dicom.StudyInstanceUID,
        patientID=dicom.PatientID,
        modality=dicom.Modality,
        instances=[dicom],
        reference_folder_path=Path(dicom.filename).parents[0],
    )

    return series

make_study(dicom) staticmethod

Method to create a DicomStudy dataclass based on the provided dicom dataset @param dicom: The reference dicom dataset @return: The DicomStudy dataclass

Source code in pygrpm/dicom/reader.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
@staticmethod
def make_study(dicom: pydicom.FileDataset) -> DicomStudy:
    """
    Method to create a DicomStudy dataclass based on the provided dicom dataset
    @param dicom: The reference dicom dataset
    @return: The DicomStudy dataclass
    """
    study = DicomStudy(
        study_UID=dicom.StudyInstanceUID,
        patientID=dicom.PatientID,
        series=[],
        reference_folder_paths=[Path(dicom.filename).parents[0]],
    )

    return study

yield_dicoms()

Generator utilized to obtain the pydicom FileDatasets within the data folder

Source code in pygrpm/dicom/reader.py
56
57
58
59
60
61
62
63
64
65
66
67
68
def yield_dicoms(self) -> Generator[pydicom.FileDataset, None, None]:
    """
    Generator utilized to obtain the pydicom FileDatasets within the data folder
    """
    for root, folders, files in os.walk(self.data_path):  # pylint: disable=unused-variable
        for file in files:
            # If file is not a proper dicom it will be set to None
            my_file = Path(root) / file
            my_dicom = self.load_dcm(my_file)

            # Append dataset to master list if appropriate
            if my_dicom:
                yield my_dicom

Module containing dataclasses to represent and store dicom information

DicomSeries dataclass

Dataclass meant to contain a Dicom series along with easy access to key values

Source code in pygrpm/dicom/structures.py
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
@dataclass
class DicomSeries:
    """
    Dataclass meant to contain a Dicom series along with easy access to key values
    """

    series_UID: str
    study_UID: str
    patientID: str
    modality: str
    instances: List[pydicom.FileDataset]
    reference_folder_path: Union[str, os.PathLike]

    _numpy: np.ndarray = field(init=False, repr=False)

    @property
    def instances_UID(self) -> List[Union[str, pydicom.uid.UID]]:
        """
        property method to return a list of all instances uid within the series dataclass
        @return: An similarly ordered list of instances UID
        """
        return [instance.SOPInstanceUID for instance in self.instances]

    @property
    def numpy(self) -> Union[np.ndarray, None]:
        """
        Returns the volume represented in the list of Instances' pixel_array
        Volume is in a "slices last" format
        @return: The associated numpy array if found
        """
        # If already generated, return it
        if hasattr(self, "_numpy"):
            return self._numpy

        if "PixelData" not in self.first_instance:
            return None

        # If we have a case like CT images, many 2D arrays making a 3D
        if self.first_instance.pixel_array.ndim == 2 and len(self.instances) > 1:
            self._make_3D_from_2D()

        # If we just have a 3D array
        elif self.first_instance.pixel_array.ndim == 3:
            self._numpy = self.first_instance.pixel_array

        return self._numpy

    def _make_3D_from_2D(self) -> None:
        # Sort by Z
        self.sort_instances_by_tag("ImagePositionPatient", 2)

        volume_stack = []
        for instance in self.instances:
            # Check PixelData and not pixel_array as the latter is a getter, not a property
            if "PixelData" not in instance:
                warnings.warn("Instance within the series has no pixel array.")

            volume_stack.append(instance.pixel_array)

        self._numpy = np.stack(volume_stack, axis=-1)

    @property
    def first_instance(self) -> pydicom.FileDataset:
        """
        Provide a sample dicom file for tag referencing
        @return:
        """
        # Chosen by fair random dice roll
        return self.instances[0]

    def sort_instances_by_tag(self, tag: str, index: int = None) -> None:
        """
        Sort the dicom instance list based on tag
        @param tag: Dicom tag as represented by pydicom
        @param index: Index in the tag's tuple/list if relevent
        @return: None
        """
        if len(self.instances) > 0:
            if index:
                self.instances.sort(key=lambda dcm: dcm[tag].value[index])
            else:
                self.instances.sort(key=lambda dcm: dcm[tag].value)

    def get_instance(self, instance_UID: Union[str, pydicom.uid.UID]):
        """
        Helper method to obtain a Dicom instance (pydicom dataset) from its DicomSeries
        @param series_UID: SeriesInstanceUID
        @return: The DicomSeries dataclass matching the provided UID
        """
        for instance in self.instances:
            if instance.SOPInstanceUID == instance_UID:
                return instance
        return None

first_instance: pydicom.FileDataset property

Provide a sample dicom file for tag referencing @return:

instances_UID: List[Union[str, pydicom.uid.UID]] property

property method to return a list of all instances uid within the series dataclass @return: An similarly ordered list of instances UID

numpy: Union[np.ndarray, None] property

Returns the volume represented in the list of Instances' pixel_array Volume is in a "slices last" format @return: The associated numpy array if found

get_instance(instance_UID)

Helper method to obtain a Dicom instance (pydicom dataset) from its DicomSeries @param series_UID: SeriesInstanceUID @return: The DicomSeries dataclass matching the provided UID

Source code in pygrpm/dicom/structures.py
 98
 99
100
101
102
103
104
105
106
107
def get_instance(self, instance_UID: Union[str, pydicom.uid.UID]):
    """
    Helper method to obtain a Dicom instance (pydicom dataset) from its DicomSeries
    @param series_UID: SeriesInstanceUID
    @return: The DicomSeries dataclass matching the provided UID
    """
    for instance in self.instances:
        if instance.SOPInstanceUID == instance_UID:
            return instance
    return None

sort_instances_by_tag(tag, index=None)

Sort the dicom instance list based on tag @param tag: Dicom tag as represented by pydicom @param index: Index in the tag's tuple/list if relevent @return: None

Source code in pygrpm/dicom/structures.py
85
86
87
88
89
90
91
92
93
94
95
96
def sort_instances_by_tag(self, tag: str, index: int = None) -> None:
    """
    Sort the dicom instance list based on tag
    @param tag: Dicom tag as represented by pydicom
    @param index: Index in the tag's tuple/list if relevent
    @return: None
    """
    if len(self.instances) > 0:
        if index:
            self.instances.sort(key=lambda dcm: dcm[tag].value[index])
        else:
            self.instances.sort(key=lambda dcm: dcm[tag].value)

DicomStudy dataclass

Dataclass to represent a Dicom patient study

Source code in pygrpm/dicom/structures.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
@dataclass
class DicomStudy:
    """
    Dataclass to represent a Dicom patient study
    """

    study_UID: str
    patientID: str
    series: List[DicomSeries]
    reference_folder_paths: List[Union[str, os.PathLike]]

    _modalities: List = field(init=False, repr=False)

    @property
    def series_UID(self) -> List[Union[str, pydicom.uid.UID]]:
        """
        property method to return a list of all series uid within the study dataclass
        @return: An similarly ordered list of series UID
        """
        return [series.series_UID for series in self.series]

    def filter_by_modality(self, modality: str) -> List[DicomSeries]:
        """
        Helper method to grab only series of a specific modality from the study
        @param modality: The dicom modality as it appears in pydicom
        @return: List of relevant DicomSeries dataclasses
        """
        return [series for series in self.series if series.modality == modality]

    @property
    def modalities(self) -> List[str]:
        """
        Property method to obtain all modalities present in the study
        @return: List of modalities as they appear in pydicom
        """
        return [series.modality for series in self.series]

    def get_series(self, series_UID: Union[str, pydicom.uid.UID]):
        """
        Helper method to obtain a DicomSeries class from its DicomStudy
        @param series_UID: SeriesInstanceUID
        @return: The DicomSeries dataclass matching the provided UID
        """
        for series in self.series:
            if series.series_UID == series_UID:
                return series
        return None

modalities: List[str] property

Property method to obtain all modalities present in the study @return: List of modalities as they appear in pydicom

series_UID: List[Union[str, pydicom.uid.UID]] property

property method to return a list of all series uid within the study dataclass @return: An similarly ordered list of series UID

filter_by_modality(modality)

Helper method to grab only series of a specific modality from the study @param modality: The dicom modality as it appears in pydicom @return: List of relevant DicomSeries dataclasses

Source code in pygrpm/dicom/structures.py
130
131
132
133
134
135
136
def filter_by_modality(self, modality: str) -> List[DicomSeries]:
    """
    Helper method to grab only series of a specific modality from the study
    @param modality: The dicom modality as it appears in pydicom
    @return: List of relevant DicomSeries dataclasses
    """
    return [series for series in self.series if series.modality == modality]

get_series(series_UID)

Helper method to obtain a DicomSeries class from its DicomStudy @param series_UID: SeriesInstanceUID @return: The DicomSeries dataclass matching the provided UID

Source code in pygrpm/dicom/structures.py
146
147
148
149
150
151
152
153
154
155
def get_series(self, series_UID: Union[str, pydicom.uid.UID]):
    """
    Helper method to obtain a DicomSeries class from its DicomStudy
    @param series_UID: SeriesInstanceUID
    @return: The DicomSeries dataclass matching the provided UID
    """
    for series in self.series:
        if series.series_UID == series_UID:
            return series
    return None

UID Generation module

generate_uid(entropy_sources=None)

Generate a unique DICOM UID with the GRPM prefix.

Parameters:

Name Type Description Default
entropy_sources Optional[List[str]]

The GRPM prefix will be appended with a SHA512 hash of the given list which means the result is deterministic and should make the original data unrecoverable.

None

Returns:

Type Description
str

A DICOM UID of up to 64 characters.

Source code in pygrpm/dicom/uid.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def generate_uid(entropy_sources: Optional[List[str]] = None) -> uid.UID:
    """Generate a unique DICOM UID with the GRPM prefix.

    Parameters
    ----------
    entropy_sources
        The GRPM prefix will be appended with a SHA512 hash of the given list
        which means the result is deterministic and should make the original
        data unrecoverable.

    Returns
    -------
    str
        A DICOM UID of up to 64 characters.

    """
    return uid.generate_uid(prefix=GRPM_PREFIX, entropy_srcs=entropy_sources)