Skip to content

Commit 89153e0

Browse files
committed
Migrated the bundle inference operator and the sample app using it.
Signed-off-by: M Q <mingmelvinq@nvidia.com>
1 parent 84af3e1 commit 89153e0

File tree

7 files changed

+869
-830
lines changed

7 files changed

+869
-830
lines changed
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
from app import AISpleenSegApp
22

3+
from monai.deploy.logger import load_env_log_level
4+
35
if __name__ == "__main__":
4-
AISpleenSegApp(do_run=True)
6+
load_env_log_level()
7+
AISpleenSegApp().run()

examples/apps/ai_spleen_seg_app/app.py

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@
1010
# limitations under the License.
1111

1212
import logging
13+
from pathlib import Path
1314

1415
# Required for setting SegmentDescription attributes. Direct import as this is not part of App SDK package.
1516
from pydicom.sr.codedict import codes
1617

17-
from monai.deploy.core import Application, resource
18+
from monai.deploy.conditions import CountCondition
19+
from monai.deploy.core import AppContext, Application
1820
from monai.deploy.core.domain import Image
1921
from monai.deploy.core.io_type import IOType
22+
from monai.deploy.logger import load_env_log_level
2023
from monai.deploy.operators.dicom_data_loader_operator import DICOMDataLoaderOperator
2124
from monai.deploy.operators.dicom_seg_writer_operator import DICOMSegmentationWriterOperator, SegmentDescription
2225
from monai.deploy.operators.dicom_series_selector_operator import DICOMSeriesSelectorOperator
@@ -26,9 +29,10 @@
2629
IOMapping,
2730
MonaiBundleInferenceOperator,
2831
)
32+
from monai.deploy.operators.stl_conversion_operator import STLConversionOperator
2933

3034

31-
@resource(cpu=1, gpu=1, memory="7Gi")
35+
# @resource(cpu=1, gpu=1, memory="7Gi")
3236
# pip_packages can be a string that is a path(str) to requirements.txt file or a list of packages.
3337
# The monai pkg is not required by this class, instead by the included operators.
3438
class AISpleenSegApp(Application):
@@ -48,10 +52,17 @@ def compose(self):
4852

4953
logging.info(f"Begin {self.compose.__name__}")
5054

55+
app_context = AppContext({}) # Let it figure out all the attributes without overriding
56+
app_input_path = Path(app_context.input_path)
57+
app_output_path = Path(app_context.output_path)
58+
model_path = Path(app_context.model_path)
59+
5160
# Create the custom operator(s) as well as SDK built-in operator(s).
52-
study_loader_op = DICOMDataLoaderOperator()
53-
series_selector_op = DICOMSeriesSelectorOperator(Sample_Rules_Text)
54-
series_to_vol_op = DICOMSeriesToVolumeOperator()
61+
study_loader_op = DICOMDataLoaderOperator(
62+
self, CountCondition(self, 1), input_folder=app_input_path, name="study_loader_op"
63+
)
64+
series_selector_op = DICOMSeriesSelectorOperator(self, rules=Sample_Rules_Text, name="series_selector_op")
65+
series_to_vol_op = DICOMSeriesToVolumeOperator(self, name="series_to_vol_op")
5566

5667
# Create the inference operator that supports MONAI Bundle and automates the inference.
5768
# The IOMapping labels match the input and prediction keys in the pre and post processing.
@@ -68,9 +79,12 @@ def compose(self):
6879
config_names = BundleConfigNames(config_names=["inference"]) # Same as the default
6980

7081
bundle_spleen_seg_op = MonaiBundleInferenceOperator(
82+
self,
7183
input_mapping=[IOMapping("image", Image, IOType.IN_MEMORY)],
7284
output_mapping=[IOMapping("pred", Image, IOType.IN_MEMORY)],
7385
bundle_config_names=config_names,
86+
bundle_path=model_path,
87+
name="bundle_spleen_seg_op",
7488
)
7589

7690
# Create DICOM Seg writer providing the required segment description for each segment with
@@ -91,25 +105,31 @@ def compose(self):
91105
custom_tags = {"SeriesDescription": "AI generated Seg, not for clinical use."}
92106

93107
dicom_seg_writer = DICOMSegmentationWriterOperator(
94-
segment_descriptions=segment_descriptions, custom_tags=custom_tags
108+
self,
109+
segment_descriptions=segment_descriptions,
110+
custom_tags=custom_tags,
111+
output_folder=app_output_path,
112+
name="dicom_seg_writer",
95113
)
96114

97115
# Create the processing pipeline, by specifying the source and destination operators, and
98116
# 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"})
117+
self.add_flow(study_loader_op, series_selector_op, {("dicom_study_list", "dicom_study_list")})
100118
self.add_flow(
101-
series_selector_op, series_to_vol_op, {"study_selected_series_list": "study_selected_series_list"}
119+
series_selector_op, series_to_vol_op, {("study_selected_series_list", "study_selected_series_list")}
102120
)
103-
self.add_flow(series_to_vol_op, bundle_spleen_seg_op, {"image": "image"})
121+
self.add_flow(series_to_vol_op, bundle_spleen_seg_op, {("image", "image")})
104122
# Note below the dicom_seg_writer requires two inputs, each coming from a source operator.
105123
self.add_flow(
106-
series_selector_op, dicom_seg_writer, {"study_selected_series_list": "study_selected_series_list"}
124+
series_selector_op, dicom_seg_writer, {("study_selected_series_list", "study_selected_series_list")}
107125
)
108-
self.add_flow(bundle_spleen_seg_op, dicom_seg_writer, {"pred": "seg_image"})
126+
self.add_flow(bundle_spleen_seg_op, dicom_seg_writer, {("pred", "seg_image")})
109127
# Create the surface mesh STL conversion operator and add it to the app execution flow, if needed, by
110128
# 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"})
129+
stl_conversion_op = STLConversionOperator(
130+
self, output_file=app_output_path.joinpath("stl/spleen.stl"), name="stl_conversion_op"
131+
)
132+
self.add_flow(bundle_spleen_seg_op, stl_conversion_op, {("pred", "image")})
113133

114134
logging.info(f"End {self.compose.__name__}")
115135

@@ -140,5 +160,5 @@ def compose(self):
140160
# e.g.
141161
# monai-deploy exec app.py -i input -m model/model.ts
142162
#
143-
logging.basicConfig(level=logging.DEBUG)
144-
app_instance = AISpleenSegApp(do_run=True)
163+
load_env_log_level()
164+
AISpleenSegApp().run()

monai/deploy/core/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@
3434
# from .env import env
3535
# from .execution_context import ExecutionContext
3636
# from .io_context import InputContext, OutputContext
37-
# from .io_type import IOType
37+
from .io_type import IOType
3838
from .models import Model, ModelFactory, NamedModel, TorchScriptModel, TritonModel
39+
from .runtime_env import RuntimeEnv
3940

4041
# from .operator import Operator, input, output
4142
# from .resource import resource

monai/deploy/operators/inference_operator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class InferenceOperator(Operator):
2222
a given model, post-transforms, and final results generation.
2323
"""
2424

25-
def __init__(self, fragment, *args, **kwargs):
25+
def __init__(self, fragment: Fragment, *args, **kwargs):
2626
"""Constructor of the operator."""
2727
super().__init__(fragment, *args, **kwargs)
2828

0 commit comments

Comments
 (0)