Skip to content

Commit f79454e

Browse files
committed
Add domain classes for study selected series and selection operator
1 parent 1ce007c commit f79454e

File tree

7 files changed

+534
-104
lines changed

7 files changed

+534
-104
lines changed

monai/deploy/core/domain/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@
1919
DICOMStudy
2020
DICOMSeries
2121
DICOMSOPInstance
22+
SelectedSeries
23+
StudySelectedSeries
2224
"""
2325

2426
from .datapath import DataPath, NamedDataPath
2527
from .dicom_series import DICOMSeries
28+
from .dicom_series_selection import SelectedSeries, StudySelectedSeries
2629
from .dicom_sop_instance import DICOMSOPInstance
2730
from .dicom_study import DICOMStudy
2831
from .domain import Domain

monai/deploy/core/domain/dicom_series.py

Lines changed: 49 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -50,70 +50,85 @@ def add_sop_instance(self, sop_instance):
5050
def get_sop_instances(self):
5151
return self._sop_instances
5252

53+
# Properties named after DICOM Series module attribute keywords
54+
# There are two required (Type 1) attrbutes for a series:
55+
# Keyword: SeriesInstanceUID, Tag: (0020,000E)
56+
# Keyword: Modality, Tag: (0008,0060)
57+
#
5358
@property
54-
def series_date(self):
59+
def SeriesInstanceUID(self):
60+
return self._series_instance_uid
61+
62+
@SeriesInstanceUID.setter
63+
def SeriesInstanceUID(self, val):
64+
self._series_instance_uid = val
65+
66+
@property
67+
def SeriesDate(self):
5568
return getattr(self, "_series_date", None)
5669

57-
@series_date.setter
58-
def series_date(self, val):
70+
@SeriesDate.setter
71+
def SeriesDate(self, val):
5972
self._series_date = val
6073

6174
@property
62-
def series_time(self):
75+
def SeriesTime(self):
6376
return getattr(self, "_series_time", None)
6477

65-
@series_time.setter
66-
def series_time(self, val):
78+
@SeriesTime.setter
79+
def SeriesTime(self, val):
6780
self._series_time = val
6881

6982
@property
70-
def modality(self):
83+
def Modality(self):
7184
return getattr(self, "_modality", None)
7285

73-
@modality.setter
74-
def modality(self, val):
86+
@Modality.setter
87+
def Modality(self, val):
7588
self._modality = val
7689

7790
@property
78-
def series_description(self):
91+
def SeriesDescription(self):
7992
return getattr(self, "_series_description", None)
8093

81-
@series_description.setter
82-
def series_description(self, val):
94+
@SeriesDescription.setter
95+
def SeriesDescription(self, val):
8396
self._series_description = val
8497

8598
@property
86-
def body_part_examined(self):
99+
def BodyPartExamined(self):
87100
return getattr(self, "_body_part_examined", None)
88101

89-
@body_part_examined.setter
90-
def body_part_examined(self, val):
102+
@BodyPartExamined.setter
103+
def BodyPartExamined(self, val):
91104
self._body_part_examined = val
92105

93106
@property
94-
def patient_position(self):
107+
def PatientPosition(self):
95108
return getattr(self, "_patient_position", None)
96109

97-
@patient_position.setter
98-
def patient_position(self, val):
110+
@PatientPosition.setter
111+
def PatientPosition(self, val):
99112
self._patient_position = val
100113

101114
@property
102-
def series_number(self):
115+
def SeriesNumber(self):
103116
return getattr(self, "_series_number", None)
104117

105-
@series_number.setter
106-
def series_number(self, val):
118+
@SeriesNumber.setter
119+
def SeriesNumber(self, val):
107120
self._series_number = val
108121

109122
@property
110-
def laterality(self):
111-
return getattr(self, "_laterality", None)
123+
def Laterality(self):
124+
return getattr(self, "_Laterality", None)
112125

113-
@laterality.setter
114-
def laterality(self, val):
126+
@Laterality.setter
127+
def Laterality(self, val):
115128
self._laterality = val
116129

130+
# Derived properties based on image module attributes
131+
#
117132
@property
118133
def row_pixel_spacing(self):
119134
return getattr(self, "_row_pixel_spacing", None)
@@ -179,28 +194,28 @@ def nifti_affine_transform(self, val):
179194
self._nifti_affine_transform = val
180195

181196
def __str__(self):
182-
result = "---------------" + "\n"
197+
result = "\n---------------" + "\n"
183198

184199
series_instance_uid_attr = "Series Instance UID: " + self._series_instance_uid + "\n"
185200
result += series_instance_uid_attr
186201

187202
num_sop_instances = "Num SOP Instances: " + str(len(self._sop_instances)) + "\n"
188203
result += num_sop_instances
189204

190-
if self.series_date is not None:
191-
series_date_attr = "Series Date: " + self.series_date + "\n"
205+
if self.SeriesDate is not None:
206+
series_date_attr = "Series Date: " + self.SeriesDate + "\n"
192207
result += series_date_attr
193208

194-
if self.series_time is not None:
195-
series_time_attr = "Series Time: " + self.series_time + "\n"
209+
if self.SeriesTime is not None:
210+
series_time_attr = "Series Time: " + self.SeriesTime + "\n"
196211
result += series_time_attr
197212

198-
if self.modality is not None:
199-
modality_attr = "Modality: " + self.modality + "\n"
213+
if self.Modality is not None:
214+
modality_attr = "Modality: " + self.Modality + "\n"
200215
result += modality_attr
201216

202-
if self.series_description is not None:
203-
series_desc_attr = "Series Description: " + self.series_description + "\n"
217+
if self.SeriesDescription is not None:
218+
series_desc_attr = "Series Description: " + self.SeriesDescription + "\n"
204219
result += series_desc_attr
205220

206221
if self.row_pixel_spacing is not None:
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Copyright 2021 MONAI Consortium
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
# Unless required by applicable law or agreed to in writing, software
7+
# distributed under the License is distributed on an "AS IS" BASIS,
8+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
# See the License for the specific language governing permissions and
10+
# limitations under the License.
11+
12+
from typing import Dict, List, Optional, Union
13+
14+
from .dicom_series import DICOMSeries
15+
from .dicom_study import DICOMStudy
16+
from .domain import Domain
17+
from .image import Image
18+
19+
20+
class SelectedSeries(Domain):
21+
"""This class encapsulates a DICOM series that has been selected with a given selection name.
22+
23+
It references the DICOMSeries object with the name associated with selection, and a Image if applicable.
24+
"""
25+
26+
def __init__(
27+
self,
28+
selection_name: str,
29+
dicom_series: DICOMSeries,
30+
image: Optional[Image] = None,
31+
metadata: Optional[Dict] = None,
32+
) -> None:
33+
"""Creates an instance of this class
34+
35+
Args:
36+
selection_name (str): Name given to this selection, if None, then series instance UID is used.
37+
dicom_series (DICOMSeries): The referenced DICOMSeries object.
38+
image (Optional[Image], optional): Converted image of the series, if applicable. Defaults to None.
39+
metadata (Optional[Dict], optional): Metadata dictionary for the instance. Defaults to None.
40+
41+
Raises:
42+
ValueError: If argument dicom_series is not a DICOMSeries object.
43+
"""
44+
if not isinstance(dicom_series, DICOMSeries):
45+
raise ValueError("Argument dicom_series must be a DICOMSeries object.")
46+
super().__init__(metadata)
47+
self._series = dicom_series
48+
self._image = image
49+
50+
if not selection_name or not selection_name.strip():
51+
self._name = str(dicom_series.SeriesInstanceUID)
52+
else:
53+
self._name = selection_name.strip()
54+
55+
@property
56+
def series(self) -> DICOMSeries:
57+
return self._series
58+
59+
@property
60+
def slection_name(self) -> str:
61+
return self._name
62+
63+
@property
64+
def image(self) -> Union[Image, None]:
65+
return self._image
66+
67+
@image.setter
68+
def image(self, val):
69+
self._image = val
70+
71+
72+
class StudySelectedSeries(Domain):
73+
"""This class encapsulates a DICOM study and a list of selected DICOM series within it.
74+
75+
It references the DICOMStudy object and a dictionary of SelectedSeries objects.
76+
"""
77+
78+
def __init__(self, study: DICOMStudy, metadata: Optional[Dict] = None) -> None:
79+
"""Creates a instance with a DICOMStudy object.
80+
81+
Args:
82+
study (DICOMStudy): The DICOMStudy object referenced.
83+
metadata (Optional[Dict], optional): Metadata dictionary for the instance. Defaults to None.
84+
85+
Raises:
86+
ValueError: If argument study is not a DICOMStudy object.
87+
"""
88+
if not isinstance(study, DICOMStudy):
89+
raise ValueError("A DICOMStudy object is required.")
90+
super().__init__(metadata)
91+
self._study: DICOMStudy = study
92+
self._select_series_dict: Dict = {} # "selection_name": [SelectedSeries]
93+
94+
@property
95+
def study(self) -> DICOMStudy:
96+
"""Returns the DICOMStudy object referenced.
97+
98+
Returns:
99+
DICOMStudy: The referenced DICOMStudy object.
100+
"""
101+
return self._study
102+
103+
@property
104+
def selected_series(self) -> List[SelectedSeries]:
105+
"""Returns a view of the list of all the SelectedSeries objects.
106+
107+
Returns:
108+
List[SelectedSeries]: A view of the flat list of all SelectedSeries objects.
109+
"""
110+
list_of_sublists = list(self._select_series_dict.values())
111+
return [item for sublist in list_of_sublists for item in sublist]
112+
113+
@property
114+
def series_by_selection_name(self) -> Dict:
115+
"""Returns the list of SelectedSeries by selection names in a dictionary.
116+
117+
Returns:
118+
Dict: Dictionary with selection name as key and list of SelectedSeries as value.
119+
"""
120+
return self._select_series_dict
121+
122+
def add_selected_series(self, selected_series: SelectedSeries) -> None:
123+
"""Adds a SelectedSeries object in the referenced DICOMStudy.
124+
125+
The SelectedSeries object is grouped by the selection name in a list,
126+
so there could be one or more objects for a given selection name.
127+
128+
Args:
129+
selected_series (SelectedSeries): The reference of the SelectedSeries object.
130+
131+
Raises:
132+
ValueError: If argument selected_series is not a SelectedSeries object.
133+
"""
134+
if not isinstance(selected_series, SelectedSeries):
135+
raise ValueError("A SelectedSeries object is required.")
136+
selected_series_list = self._select_series_dict.get(selected_series.slection_name, [])
137+
selected_series_list.append(selected_series)
138+
self._select_series_dict[selected_series.slection_name] = selected_series_list

0 commit comments

Comments
 (0)