Skip to content

Commit 58da261

Browse files
committed
Updated the liver tumor example
Signed-off-by: M Q <mingmelvinq@nvidia.com>
1 parent b697b0e commit 58da261

File tree

3 files changed

+53
-50
lines changed

3 files changed

+53
-50
lines changed

examples/apps/ai_livertumor_seg_app/app.py

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@
1313
from pathlib import Path
1414

1515
from livertumor_seg_operator import LiverTumorSegOperator
16-
17-
# Required for setting SegmentDescription attributes. Direct import as this is not part of App SDK package.
18-
from pydicom.sr.codedict import codes
16+
from pydicom.sr.codedict import codes # Required for setting SegmentDescription attributes.
1917

2018
from monai.deploy.conditions import CountCondition
2119
from monai.deploy.core import AppContext, Application
@@ -26,33 +24,12 @@
2624
from monai.deploy.operators.dicom_series_to_volume_operator import DICOMSeriesToVolumeOperator
2725
from monai.deploy.operators.stl_conversion_operator import STLConversionOperator
2826

29-
# from monai.deploy.operators.publisher_operator import PublisherOperator
30-
31-
# This is a sample series selection rule in JSON, simply selecting CT series.
32-
# If the study has more than 1 CT series, then all of them will be selected.
33-
# Please see more detail in DICOMSeriesSelectorOperator.
34-
# For list of string values, e.g. "ImageType": ["PRIMARY", "ORIGINAL"], it is a match if all elements
35-
# are all in the multi-value attribute of the DICOM series.
36-
37-
Sample_Rules_Text = """
38-
{
39-
"selections": [
40-
{
41-
"name": "CT Series",
42-
"conditions": {
43-
"Modality": "(?i)CT",
44-
"ImageType": ["PRIMARY", "ORIGINAL"],
45-
"PhotometricInterpretation": "MONOCHROME2"
46-
}
47-
}
48-
]
49-
}
50-
"""
27+
# This sample example completes the processing of a DICOM series with around 600 instances within 45 seconds,
28+
# and time reduces to about 23 seconds if the STL generation is disabled,
29+
# on a desktop with Ubuntu 20.04, 32GB of RAM, and a Nvidia GPU GV100 with 32GB of memory.
5130

5231

5332
# @resource(cpu=1, gpu=1, memory="7Gi")
54-
# pip_packages can be a string that is a path(str) to requirements.txt file or a list of packages.
55-
# The MONAI pkg is not required by this class, instead by the included operators.
5633
class AILiverTumorApp(Application):
5734
def __init__(self, *args, **kwargs):
5835
"""Creates an application instance."""
@@ -62,9 +39,9 @@ def __init__(self, *args, **kwargs):
6239

6340
def run(self, *args, **kwargs):
6441
# This method calls the base class to run. Can be omitted if simply calling through.
65-
self._logger.debug(f"Begin {self.run.__name__}")
42+
self._logger.info(f"Begin {self.run.__name__}")
6643
super().run(*args, **kwargs)
67-
self._logger.debug(f"End {self.run.__name__}")
44+
self._logger.info(f"End {self.run.__name__}")
6845

6946
def compose(self):
7047
"""Creates the app specific operators and chain them up in the processing DAG."""
@@ -88,7 +65,7 @@ def compose(self):
8865
# self, model_path=model_path, output_folder=app_output_path, name="seg_op"
8966
# )
9067

91-
# Create the publisher operator
68+
# Create the surface mesh STL conversion operator
9269
stl_op = STLConversionOperator(self, output_file=app_output_path.joinpath("stl/mesh.stl"), name="stl_op")
9370

9471
# Create DICOM Seg writer providing the required segment description for each segment with
@@ -144,6 +121,27 @@ def compose(self):
144121
self._logger.info(f"End {self.compose.__name__}")
145122

146123

124+
# This is a sample series selection rule in JSON, simply selecting CT series.
125+
# If the study has more than 1 CT series, then all of them will be selected.
126+
# Please see more detail in DICOMSeriesSelectorOperator.
127+
# For list of string values, e.g. "ImageType": ["PRIMARY", "ORIGINAL"], it is a match if all elements
128+
# are all in the multi-value attribute of the DICOM series.
129+
130+
Sample_Rules_Text = """
131+
{
132+
"selections": [
133+
{
134+
"name": "CT Series",
135+
"conditions": {
136+
"Modality": "(?i)CT",
137+
"ImageType": ["PRIMARY", "ORIGINAL"],
138+
"PhotometricInterpretation": "MONOCHROME2"
139+
}
140+
}
141+
]
142+
}
143+
"""
144+
147145
if __name__ == "__main__":
148146
# Creates the app and test it standalone. When running is this mode, please note the following:
149147
# -m <model file>, for model file path

examples/apps/ai_livertumor_seg_app/livertumor_seg_operator.py

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,12 @@ def compute(self, op_input, op_output, context):
9090
# This operator gets an in-memory Image object, so a specialized ImageReader is needed.
9191
_reader = InMemImageReader(input_image)
9292

93-
# In this example, the input image, once loaded at the beginning of the pre-transforms, is
94-
# saved on disk, so is the segmentation prediction image at the end of the post-transform.
93+
# In this example, the input image, once loaded at the beginning of the pre-transforms, can
94+
# be saved on disk, so can the segmentation prediction image at the end of the post-transform.
9595
# They are both saved in the same subfolder of the application output folder, with names
96-
# distinguished by postfix. They can also be save in different subfolder if need be.
96+
# distinguished by the postfix. They can also be saved in different subfolder if need be.
9797
# These images files can then be packaged for rendering.
98+
# In the code below, saving of the image files are disabled to save 10 seconds if nii, and 20 if nii.gz
9899
pre_transforms = self.pre_process(_reader, str(self.output_folder))
99100
post_transforms = self.post_process(pre_transforms, str(self.output_folder))
100101

@@ -131,13 +132,15 @@ def pre_process(self, img_reader, out_dir: str = "./input_images") -> Compose:
131132
[
132133
LoadImaged(keys=my_key, reader=img_reader),
133134
EnsureChannelFirstd(keys=my_key),
134-
SaveImaged(
135-
keys=my_key,
136-
output_dir=out_dir,
137-
output_postfix="",
138-
resample=False,
139-
output_ext=".nii", # favor speed over size
140-
),
135+
# The SaveImaged transform can be commented out to save 5 seconds.
136+
# Uncompress NIfTI file, nii, is used favoring speed over size, but can be changed to nii.gz
137+
# SaveImaged(
138+
# keys=my_key,
139+
# output_dir=out_dir,
140+
# output_postfix="",
141+
# resample=False,
142+
# output_ext=".nii",
143+
# ),
141144
Spacingd(keys=my_key, pixdim=(1.0, 1.0, 1.0), mode=("bilinear"), align_corners=True),
142145
ScaleIntensityRanged(my_key, a_min=-21, a_max=189, b_min=0.0, b_max=1.0, clip=True),
143146
CropForegroundd(my_key, source_key=my_key),
@@ -157,13 +160,15 @@ def post_process(self, pre_transforms: Compose, out_dir: str = "./prediction_out
157160
Invertd(
158161
keys=pred_key, transform=pre_transforms, orig_keys=self._input_dataset_key, nearest_interp=True
159162
),
160-
SaveImaged(
161-
keys=pred_key,
162-
output_dir=out_dir,
163-
output_postfix="seg",
164-
output_dtype=uint8,
165-
resample=False,
166-
output_ext=".nii", # favor speed over size
167-
),
163+
# The SaveImaged transform can be commented out to save 5 seconds.
164+
# Uncompress NIfTI file, nii, is used favoring speed over size, but can be changed to nii.gz
165+
# SaveImaged(
166+
# keys=pred_key,
167+
# output_dir=out_dir,
168+
# output_postfix="seg",
169+
# output_dtype=uint8,
170+
# resample=False,
171+
# output_ext=".nii",
172+
# ),
168173
]
169174
)

setup.cfg

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ show_column_numbers = True
9898
show_error_codes = True
9999
# Use visually nicer output in error messages: use soft word wrap, show source code snippets, and show error location markers.
100100
pretty = False
101-
# Exclude certail files/folders
102-
exclude = (dist|notebooks)/$
101+
# Exclude certain files/folders
102+
exclude = (dist|notebooks|platforms)/$
103103

104104
[mypy-versioneer]
105105
# Ignores all non-fatal errors.

0 commit comments

Comments
 (0)