1
- # Copyright 2021-2022 MONAI Consortium
1
+ # Copyright 2021-2023 MONAI Consortium
2
2
# Licensed under the Apache License, Version 2.0 (the "License");
3
3
# you may not use this file except in compliance with the License.
4
4
# You may obtain a copy of the License at
10
10
# limitations under the License.
11
11
12
12
import logging
13
+ from pathlib import Path
13
14
14
15
# Required for setting SegmentDescription attributes. Direct import as this is not part of App SDK package.
15
16
from pydicom .sr .codedict import codes
16
17
17
- import monai .deploy .core as md
18
- from monai .deploy .core import Application , resource
18
+ from monai .deploy .conditions import CountCondition
19
+ from monai .deploy .core import AppContext , Application
19
20
from monai .deploy .core .domain import Image
20
21
from monai .deploy .core .io_type import IOType
22
+ from monai .deploy .logger import load_env_log_level
21
23
from monai .deploy .operators .dicom_data_loader_operator import DICOMDataLoaderOperator
22
24
from monai .deploy .operators .dicom_seg_writer_operator import DICOMSegmentationWriterOperator , SegmentDescription
23
25
from monai .deploy .operators .dicom_series_selector_operator import DICOMSeriesSelectorOperator
28
30
MonaiBundleInferenceOperator ,
29
31
)
30
32
33
+ # from monai.deploy.operators.stl_conversion_operator import STLConversionOperator # uncomment if need be used
31
34
32
- @resource (cpu = 1 , gpu = 1 , memory = "7Gi" )
33
- @md .env (pip_packages = ["torch>=1.12.0" ])
35
+ # @resource(cpu=1, gpu=1, memory="7Gi")
36
+ # @md.env(pip_packages=["torch>=1.12.0"])
34
37
# pip_packages can be a string that is a path(str) to requirements.txt file or a list of packages.
35
38
# The monai pkg is not required by this class, instead by the included operators.
36
39
class AIPancreasSegApp (Application ):
40
+ """Demonstrates inference with built-in MONAI Bundle inference operator with DICOM files as input/output
41
+
42
+ This application loads a set of DICOM instances, select the appropriate series, converts the series to
43
+ 3D volume image, performs inference with the built-in MONAI Bundle inference operator, including pre-processing
44
+ and post-processing, saves the segmentation image in a DICOM Seg OID in an instance file, and optionally the
45
+ surface mesh in STL format.
46
+
47
+ Pertinent MONAI Bundle:
48
+ https://github.com/Project-MONAI/model-zoo/tree/dev/models/pancreas_ct_dints_segmentation
49
+
50
+ Execution Time Estimate:
51
+ With a Nvidia GV100 32GB GPU, for a input of 200 DICOM instances, execution time is around 90 seconds, and
52
+ for 500 instance 180 seconds.
53
+ """
54
+
37
55
def __init__ (self , * args , ** kwargs ):
38
56
"""Creates an application instance."""
39
57
self ._logger = logging .getLogger ("{}.{}" .format (__name__ , type (self ).__name__ ))
@@ -50,10 +68,17 @@ def compose(self):
50
68
51
69
logging .info (f"Begin { self .compose .__name__ } " )
52
70
71
+ app_context = AppContext ({}) # Let it figure out all the attributes without overriding
72
+ app_input_path = Path (app_context .input_path )
73
+ app_output_path = Path (app_context .output_path )
74
+ model_path = Path (app_context .model_path )
75
+
53
76
# Create the custom operator(s) as well as SDK built-in operator(s).
54
- study_loader_op = DICOMDataLoaderOperator ()
55
- series_selector_op = DICOMSeriesSelectorOperator (Sample_Rules_Text )
56
- series_to_vol_op = DICOMSeriesToVolumeOperator ()
77
+ study_loader_op = DICOMDataLoaderOperator (
78
+ self , CountCondition (self , 1 ), input_folder = app_input_path , name = "study_loader_op"
79
+ )
80
+ series_selector_op = DICOMSeriesSelectorOperator (self , rules = Sample_Rules_Text , name = "series_selector_op" )
81
+ series_to_vol_op = DICOMSeriesToVolumeOperator (self , name = "series_to_vol_op" )
57
82
58
83
# Create the inference operator that supports MONAI Bundle and automates the inference.
59
84
# The IOMapping labels match the input and prediction keys in the pre and post processing.
@@ -62,15 +87,15 @@ def compose(self):
62
87
# environment, so when the app is packaged into a MAP, the operator can complete the bundle parsing
63
88
# during init to provide the optional packages info, parsed from the bundle, to the packager
64
89
# for it to install the packages in the MAP docker image.
65
- # Setting output IOType to DISK only works only for leaf operators, not the case in this example.
66
- #
67
- # Pertinent MONAI Bundle:
68
- # https://github.com/Project-MONAI/model-zoo/tree/dev/models/spleen_ct_segmentation
69
90
70
- bundle_spleen_seg_op = MonaiBundleInferenceOperator (
91
+ config_names = BundleConfigNames (config_names = ["inference" ]) # Same as the default
92
+ bundle_seg_op = MonaiBundleInferenceOperator (
93
+ self ,
71
94
input_mapping = [IOMapping ("image" , Image , IOType .IN_MEMORY )],
72
95
output_mapping = [IOMapping ("pred" , Image , IOType .IN_MEMORY )],
73
- bundle_config_names = BundleConfigNames (config_names = ["inference" ]), # Same as the default
96
+ bundle_config_names = config_names ,
97
+ bundle_path = model_path ,
98
+ name = "bundle_seg_op" ,
74
99
)
75
100
76
101
# Create DICOM Seg writer providing the required segment description for each segment with
@@ -91,25 +116,31 @@ def compose(self):
91
116
custom_tags = {"SeriesDescription" : "AI generated Seg for research use only. Not for clinical use." }
92
117
93
118
dicom_seg_writer = DICOMSegmentationWriterOperator (
94
- segment_descriptions = segment_descriptions , custom_tags = custom_tags
119
+ self ,
120
+ segment_descriptions = segment_descriptions ,
121
+ custom_tags = custom_tags ,
122
+ output_folder = app_output_path ,
123
+ name = "dicom_seg_writer" ,
95
124
)
96
125
97
126
# Create the processing pipeline, by specifying the source and destination operators, and
98
127
# ensuring the output from the former matches the input of the latter, in both name and type.
99
- self .add_flow (study_loader_op , series_selector_op , {"dicom_study_list" : "dicom_study_list" })
128
+ self .add_flow (study_loader_op , series_selector_op , {( "dicom_study_list" , "dicom_study_list" ) })
100
129
self .add_flow (
101
- series_selector_op , series_to_vol_op , {"study_selected_series_list" : "study_selected_series_list" }
130
+ series_selector_op , series_to_vol_op , {( "study_selected_series_list" , "study_selected_series_list" ) }
102
131
)
103
- self .add_flow (series_to_vol_op , bundle_spleen_seg_op , {"image" : "image" })
132
+ self .add_flow (series_to_vol_op , bundle_seg_op , {( "image" , "image" ) })
104
133
# Note below the dicom_seg_writer requires two inputs, each coming from a source operator.
105
134
self .add_flow (
106
- series_selector_op , dicom_seg_writer , {"study_selected_series_list" : "study_selected_series_list" }
135
+ series_selector_op , dicom_seg_writer , {( "study_selected_series_list" , "study_selected_series_list" ) }
107
136
)
108
- self .add_flow (bundle_spleen_seg_op , dicom_seg_writer , {"pred" : "seg_image" })
137
+ self .add_flow (bundle_seg_op , dicom_seg_writer , {( "pred" , "seg_image" ) })
109
138
# Create the surface mesh STL conversion operator and add it to the app execution flow, if needed, by
110
139
# uncommenting the following couple lines.
111
- # stl_conversion_op = STLConversionOperator(output_file="stl/spleen.stl")
112
- # self.add_flow(bundle_spleen_seg_op, stl_conversion_op, {"pred": "image"})
140
+ # stl_conversion_op = STLConversionOperator(
141
+ # self, output_file=app_output_path.joinpath("stl/spleen.stl"), name="stl_conversion_op"
142
+ # )
143
+ # self.add_flow(bundle_seg_op, stl_conversion_op, {("pred", "image")})
113
144
114
145
logging .info (f"End { self .compose .__name__ } " )
115
146
@@ -140,5 +171,5 @@ def compose(self):
140
171
# e.g.
141
172
# monai-deploy exec app.py -i input -m model/model.ts
142
173
#
143
- logging . basicConfig ( level = logging . DEBUG )
144
- app_instance = AIPancreasSegApp (do_run = True )
174
+ load_env_log_level ( )
175
+ AIPancreasSegApp (). run ( )
0 commit comments