11
11
12
12
import logging
13
13
14
- from monai .deploy .core import Application , env , resource
14
+ # Required for setting SegmentDescription attributes. Direct import as this is not part of App SDK package.
15
+ from pydicom .sr .codedict import codes
16
+
17
+ import monai .deploy .core as md
18
+ from monai .deploy .core import Application , resource
15
19
from monai .deploy .core .domain import Image
16
20
from monai .deploy .core .io_type import IOType
17
21
from monai .deploy .operators .dicom_data_loader_operator import DICOMDataLoaderOperator
18
- from monai .deploy .operators .dicom_seg_writer_operator import DICOMSegmentationWriterOperator
22
+ from monai .deploy .operators .dicom_seg_writer_operator import DICOMSegmentationWriterOperator , SegmentDescription
19
23
from monai .deploy .operators .dicom_series_selector_operator import DICOMSeriesSelectorOperator
20
24
from monai .deploy .operators .dicom_series_to_volume_operator import DICOMSeriesToVolumeOperator
21
- from monai .deploy .operators .monai_bundle_inference_operator import IOMapping , MonaiBundleInferenceOperator
22
- from monai .deploy .operators .stl_conversion_operator import STLConversionOperator # import as needed.
25
+ from monai .deploy .operators .monai_bundle_inference_operator import (
26
+ BundleConfigNames ,
27
+ IOMapping ,
28
+ MonaiBundleInferenceOperator ,
29
+ )
23
30
24
31
25
32
@resource (cpu = 1 , gpu = 1 , memory = "7Gi" )
28
35
# The Bundle Inference Operator as of now only requires torch>=1.10.2, and does not yet dynamically
29
36
# parse the MONAI bundle to get the required pip package or ver on initialization, hence it does not set
30
37
# its own @env decorator accordingly when the app is being packaged into a MONAI Package.
31
- @env (pip_packages = ["torch>=1.12.0" ])
38
+ @md . env (pip_packages = ["torch>=1.12.0" ])
32
39
# pip_packages can be a string that is a path(str) to requirements.txt file or a list of packages.
33
40
# The monai pkg is not required by this class, instead by the included operators.
34
41
class App (Application ):
@@ -50,7 +57,7 @@ class App(Application):
50
57
<parent_fodler>
51
58
├── pancreas_ct_dints
52
59
│ └── model.ts
53
- └── spleen_model
60
+ └── spleen_ct
54
61
└── model.ts
55
62
56
63
Note:
@@ -91,20 +98,71 @@ def compose(self):
91
98
# for it to install the packages in the MAP docker image.
92
99
# Setting output IOType to DISK only works only for leaf operators, not the case in this example.
93
100
# When multiple models/bundles are supported, create an inference operator for each.
101
+ #
102
+ # Pertinent MONAI Bundle:
103
+ # https://github.com/Project-MONAI/model-zoo/tree/dev/models/spleen_ct_segmentation, v0.3.2
104
+ # https://github.com/Project-MONAI/model-zoo/tree/dev/models/pancreas_ct_dints_segmentation, v0.3
105
+
106
+ config_names = BundleConfigNames (config_names = ["inference" ]) # Same as the default
107
+
108
+ # This is the inference operator for the spleen_model bundle. Note the model name.
94
109
bundle_spleen_seg_op = MonaiBundleInferenceOperator (
95
110
input_mapping = [IOMapping ("image" , Image , IOType .IN_MEMORY )],
96
111
output_mapping = [IOMapping ("pred" , Image , IOType .IN_MEMORY )],
97
- model_name = "spleen_model" ,
112
+ bundle_config_names = config_names ,
113
+ model_name = "spleen_ct" ,
98
114
)
99
115
100
- bundle_pancrea_seg_op = MonaiBundleInferenceOperator (
116
+ # This is the inference operator for the pancreas_ct_dints bundle. Note the model name.
117
+ bundle_pancreas_seg_op = MonaiBundleInferenceOperator (
101
118
input_mapping = [IOMapping ("image" , Image , IOType .IN_MEMORY )],
102
119
output_mapping = [IOMapping ("pred" , Image , IOType .IN_MEMORY )],
103
120
model_name = "pancreas_ct_dints" ,
104
121
)
105
122
106
- # Create DICOM Seg writer with segment label name in a string list
107
- dicom_seg_writer = DICOMSegmentationWriterOperator (seg_labels = ["Spleen" ])
123
+ # Create DICOM Seg writer providing the required segment description for each segment with
124
+ # the actual algorithm and the pertinent organ/tissue. The segment_label, algorithm_name,
125
+ # and algorithm_version are of DICOM VR LO type, limited to 64 chars.
126
+ # https://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
127
+ #
128
+ # NOTE: Each generated DICOM Seg will be a dcm file with the name based on the SOP instance UID.
129
+
130
+ # Description for the Spleen seg, and the seg writer obj
131
+ seg_descriptions_spleen = [
132
+ SegmentDescription (
133
+ segment_label = "Spleen" ,
134
+ segmented_property_category = codes .SCT .Organ ,
135
+ segmented_property_type = codes .SCT .Spleen ,
136
+ algorithm_name = "volumetric (3D) segmentation of the spleen from CT image" ,
137
+ algorithm_family = codes .DCM .ArtificialIntelligence ,
138
+ algorithm_version = "0.3.2" ,
139
+ )
140
+ ]
141
+
142
+ custom_tags_spleen = {"SeriesDescription" : "AI Spleen Seg for research use only. Not for clinical use." }
143
+ dicom_seg_writer_spleen = DICOMSegmentationWriterOperator (
144
+ segment_descriptions = seg_descriptions_spleen , custom_tags = custom_tags_spleen
145
+ )
146
+
147
+ # Description for the Pancreas seg, and the seg writer obj
148
+ seg_descriptions_pancreas = [
149
+ SegmentDescription (
150
+ segment_label = "Pancreas" ,
151
+ segmented_property_category = codes .SCT .Organ ,
152
+ segmented_property_type = codes .SCT .Pancreas ,
153
+ algorithm_name = "volumetric (3D) segmentation of the pancreas from CT image" ,
154
+ algorithm_family = codes .DCM .ArtificialIntelligence ,
155
+ algorithm_version = "0.3.0" ,
156
+ )
157
+ ]
158
+ custom_tags_pancreas = {"SeriesDescription" : "AI Pancreas Seg for research use only. Not for clinical use." }
159
+
160
+ dicom_seg_writer_pancreas = DICOMSegmentationWriterOperator (
161
+ segment_descriptions = seg_descriptions_pancreas , custom_tags = custom_tags_pancreas
162
+ )
163
+
164
+ # NOTE: Sharp eyed readers can alreay see that the above instantiation of object can be simply parameterized.
165
+ # Very true, but leaving them as if for easy reading. In fact the whole app can be parameterized for general use.
108
166
109
167
# Create the processing pipeline, by specifying the upstream and downstream operators, and
110
168
# ensuring the output from the former matches the input of the latter, in both name and type.
@@ -116,21 +174,21 @@ def compose(self):
116
174
# Feed the input image to all inference operators
117
175
self .add_flow (series_to_vol_op , bundle_spleen_seg_op , {"image" : "image" })
118
176
# The Pancreas CT Seg bundle requires PyTorch 1.12.0 to avoid failure to load.
119
- self .add_flow (series_to_vol_op , bundle_pancrea_seg_op , {"image" : "image" })
177
+ self .add_flow (series_to_vol_op , bundle_pancreas_seg_op , {"image" : "image" })
120
178
121
179
# Create DICOM Seg for one of the inference output
122
180
# Note below the dicom_seg_writer requires two inputs, each coming from a upstream operator.
123
181
self .add_flow (
124
- series_selector_op , dicom_seg_writer , {"study_selected_series_list" : "study_selected_series_list" }
182
+ series_selector_op , dicom_seg_writer_spleen , {"study_selected_series_list" : "study_selected_series_list" }
125
183
)
126
- self .add_flow (bundle_spleen_seg_op , dicom_seg_writer , {"pred" : "seg_image" })
184
+ self .add_flow (bundle_spleen_seg_op , dicom_seg_writer_spleen , {"pred" : "seg_image" })
127
185
128
- # Create the surface mesh STL conversion operator and add it to the app execution flow, if needed, by
129
- # uncommenting the following lines .
130
- stl_conversion_op = STLConversionOperator (
131
- output_file = "stl/pancreas_tumor.stl" ,
186
+ # Create DICOM Seg for one of the inference output
187
+ # Note below the dicom_seg_writer requires two inputs, each coming from a upstream operator .
188
+ self . add_flow (
189
+ series_selector_op , dicom_seg_writer_pancreas , { "study_selected_series_list" : "study_selected_series_list" }
132
190
)
133
- self .add_flow (bundle_pancrea_seg_op , stl_conversion_op , {"pred" : "image " })
191
+ self .add_flow (bundle_pancreas_seg_op , dicom_seg_writer_pancreas , {"pred" : "seg_image " })
134
192
135
193
logging .info (f"End { self .compose .__name__ } " )
136
194
0 commit comments