Skip to content

Commit 5f16e1b

Browse files
committed
Migrate more apps
Signed-off-by: M Q <mingmelvinq@nvidia.com>
1 parent fff394d commit 5f16e1b

File tree

7 files changed

+122
-36
lines changed

7 files changed

+122
-36
lines changed

examples/apps/ai_pancrea_seg_app/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
# Copyright 2021-2023 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+
112
import os
213
import sys
314

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
1+
# Copyright 2021-2023 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+
112
from app import AIPancreasSegApp
213

14+
from monai.deploy.logger import load_env_log_level
15+
316
if __name__ == "__main__":
4-
AIPancreasSegApp(do_run=True)
17+
load_env_log_level()
18+
AIPancreasSegApp().run()

examples/apps/ai_pancrea_seg_app/app.py

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2021-2022 MONAI Consortium
1+
# Copyright 2021-2023 MONAI Consortium
22
# Licensed under the Apache License, Version 2.0 (the "License");
33
# you may not use this file except in compliance with the License.
44
# You may obtain a copy of the License at
@@ -10,14 +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-
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
1920
from monai.deploy.core.domain import Image
2021
from monai.deploy.core.io_type import IOType
22+
from monai.deploy.logger import load_env_log_level
2123
from monai.deploy.operators.dicom_data_loader_operator import DICOMDataLoaderOperator
2224
from monai.deploy.operators.dicom_seg_writer_operator import DICOMSegmentationWriterOperator, SegmentDescription
2325
from monai.deploy.operators.dicom_series_selector_operator import DICOMSeriesSelectorOperator
@@ -28,12 +30,28 @@
2830
MonaiBundleInferenceOperator,
2931
)
3032

33+
# from monai.deploy.operators.stl_conversion_operator import STLConversionOperator # uncomment if need be used
3134

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"])
3437
# pip_packages can be a string that is a path(str) to requirements.txt file or a list of packages.
3538
# The monai pkg is not required by this class, instead by the included operators.
3639
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+
3755
def __init__(self, *args, **kwargs):
3856
"""Creates an application instance."""
3957
self._logger = logging.getLogger("{}.{}".format(__name__, type(self).__name__))
@@ -50,10 +68,17 @@ def compose(self):
5068

5169
logging.info(f"Begin {self.compose.__name__}")
5270

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+
5376
# 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")
5782

5883
# Create the inference operator that supports MONAI Bundle and automates the inference.
5984
# The IOMapping labels match the input and prediction keys in the pre and post processing.
@@ -62,15 +87,15 @@ def compose(self):
6287
# environment, so when the app is packaged into a MAP, the operator can complete the bundle parsing
6388
# during init to provide the optional packages info, parsed from the bundle, to the packager
6489
# 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
6990

70-
bundle_spleen_seg_op = MonaiBundleInferenceOperator(
91+
config_names = BundleConfigNames(config_names=["inference"]) # Same as the default
92+
bundle_seg_op = MonaiBundleInferenceOperator(
93+
self,
7194
input_mapping=[IOMapping("image", Image, IOType.IN_MEMORY)],
7295
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",
7499
)
75100

76101
# Create DICOM Seg writer providing the required segment description for each segment with
@@ -91,25 +116,31 @@ def compose(self):
91116
custom_tags = {"SeriesDescription": "AI generated Seg for research use only. Not for clinical use."}
92117

93118
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",
95124
)
96125

97126
# Create the processing pipeline, by specifying the source and destination operators, and
98127
# 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")})
100129
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")}
102131
)
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")})
104133
# Note below the dicom_seg_writer requires two inputs, each coming from a source operator.
105134
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")}
107136
)
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")})
109138
# Create the surface mesh STL conversion operator and add it to the app execution flow, if needed, by
110139
# 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")})
113144

114145
logging.info(f"End {self.compose.__name__}")
115146

@@ -140,5 +171,5 @@ def compose(self):
140171
# e.g.
141172
# monai-deploy exec app.py -i input -m model/model.ts
142173
#
143-
logging.basicConfig(level=logging.DEBUG)
144-
app_instance = AIPancreasSegApp(do_run=True)
174+
load_env_log_level()
175+
AIPancreasSegApp().run()

examples/apps/ai_spleen_seg_app/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
# Copyright 2021-2023 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+
112
import os
213
import sys
314

examples/apps/ai_spleen_seg_app/__main__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
# Copyright 2021-2023 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+
112
from app import AISpleenSegApp
213

314
from monai.deploy.logger import load_env_log_level

examples/apps/ai_spleen_seg_app/app.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2021-2022 MONAI Consortium
1+
# Copyright 2021-2023 MONAI Consortium
22
# Licensed under the Apache License, Version 2.0 (the "License");
33
# you may not use this file except in compliance with the License.
44
# You may obtain a copy of the License at
@@ -36,6 +36,20 @@
3636
# pip_packages can be a string that is a path(str) to requirements.txt file or a list of packages.
3737
# The monai pkg is not required by this class, instead by the included operators.
3838
class AISpleenSegApp(Application):
39+
"""Demonstrates inference with built-in MONAI Bundle inference operator with DICOM files as input/output
40+
41+
This application loads a set of DICOM instances, select the appropriate series, converts the series to
42+
3D volume image, performs inference with the built-in MONAI Bundle inference operator, including pre-processing
43+
and post-processing, save the segmentation image in a DICOM Seg OID in an instance file, and optionally the
44+
surface mesh in STL format.
45+
46+
Pertinent MONAI Bundle:
47+
https://github.com/Project-MONAI/model-zoo/tree/dev/models/spleen_ct_segmentation
48+
49+
Execution Time Estimate:
50+
With a Nvidia GV100 32GB GPU, for a input of 500 DICOM instances, execution time is around 25 seconds.
51+
"""
52+
3953
def __init__(self, *args, **kwargs):
4054
"""Creates an application instance."""
4155
self._logger = logging.getLogger("{}.{}".format(__name__, type(self).__name__))
@@ -71,10 +85,6 @@ def compose(self):
7185
# environment, so when the app is packaged into a MAP, the operator can complete the bundle parsing
7286
# during init to provide the optional packages info, parsed from the bundle, to the packager
7387
# for it to install the packages in the MAP docker image.
74-
# Setting output IOType to DISK only works only for leaf operators, not the case in this example.
75-
#
76-
# Pertinent MONAI Bundle:
77-
# https://github.com/Project-MONAI/model-zoo/tree/dev/models/spleen_ct_segmentation
7888

7989
config_names = BundleConfigNames(config_names=["inference"]) # Same as the default
8090

monai/deploy/operators/monai_bundle_inference_operator.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -294,11 +294,7 @@ class MonaiBundleInferenceOperator(InferenceOperator):
294294
the `DICOMSeriesToVolumeOperator`, and passing a segmentation `Image` to the `DICOMSegmentationWriterOperator`.
295295
In such cases, the I/O storage type can only be `IN_MEMORY` due to the restrictions imposed by the application executor.
296296
297-
The following capabilites has been deprecated:
298-
However, when used as the first operator in an application, its input storage type needs to be `DISK`, and the file needs
299-
to be a Python pickle file, e.g. containing an `Image` instance. When used as the last operator, its output storage type
300-
also needs to `DISK` with the path being the application's output folder, and the operator's output will be saved as
301-
a pickle file whose name is the same as the output name.
297+
For the time being, the input and output to this operator are limited to in_memory object.
302298
"""
303299

304300
known_io_data_types = {
@@ -526,7 +522,9 @@ def _add_outputs(self, output_mapping: List[IOMapping]):
526522

527523
def setup(self, spec: OperatorSpec):
528524
[spec.input(v.label) for v in self._input_mapping]
529-
[spec.output(v.label) for v in self._output_mapping]
525+
for v in self._output_mapping:
526+
if v.storage_type == IOType.IN_MEMORY: # As of now the output port type can only be in_memory object.
527+
spec.output(v.label)
530528

531529
def compute(self, op_input, op_output, context):
532530
"""Infers with the input(s) and saves the prediction result(s) to output

0 commit comments

Comments
 (0)