Skip to content

Commit 3e4b839

Browse files
committed
Saving uncompress nii file to save 10s in execution time
Signed-off-by: M Q <mingmelvinq@nvidia.com>
1 parent 60ffd9e commit 3e4b839

File tree

1 file changed

+27
-20
lines changed

1 file changed

+27
-20
lines changed

examples/apps/ai_livertumor_seg_app/livertumor_seg_operator.py

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from numpy import uint8
1616

17-
from monai.deploy.core import Fragment, Operator, OperatorSpec
17+
from monai.deploy.core import ConditionType, Fragment, Operator, OperatorSpec
1818
from monai.deploy.operators.monai_seg_inference_operator import InMemImageReader, MonaiSegInferenceOperator
1919
from monai.transforms import (
2020
Activationsd,
@@ -30,9 +30,6 @@
3030
)
3131

3232

33-
# @md.input("image", Image, IOType.IN_MEMORY)
34-
# @md.output("seg_image", Image, IOType.IN_MEMORY)
35-
# @md.output("saved_images_folder", DataPath, IOType.DISK)
3633
# @md.env(pip_packages=["monai>=1.0.0", "torch>=1.5", "numpy>=1.21", "nibabel"])
3734
class LiverTumorSegOperator(Operator):
3835
"""Performs liver and tumor segmentation using a DL model with an image converted from a DICOM CT series.
@@ -41,14 +38,14 @@ class LiverTumorSegOperator(Operator):
4138
https://ngc.nvidia.com/catalog/models/nvidia:med:clara_pt_liver_and_tumor_ct_segmentation
4239
4340
Described in the downloaded model package, also called Medical Model Archive (MMAR), are the pre and post
44-
transforms before and after inference, and are using MONAI SDK transforms. As such, these transforms are
45-
simply ported to this operator, with changing SegmentationSaver handler to SaveImageD post transform.
41+
transforms and inference configurations. The MONAI Core transforms are used, as such, these transforms are
42+
simply ported to this operator, while changing SegmentationSaver "handler" to SaveImageD post "transform".
4643
47-
This operator makes use of the App SDK MonaiSegInferenceOperator in a compsition approach.
44+
This operator makes use of the App SDK MonaiSegInferenceOperator in a composition approach.
4845
It creates the pre-transforms as well as post-transforms with MONAI dictionary based transforms.
4946
Note that the App SDK InMemImageReader, derived from MONAI ImageReader, is passed to LoadImaged.
5047
This derived reader is needed to parse the in memory image object, and return the expected data structure.
51-
Loading of the model, and predicting using in-proc PyTorch inference is done by MonaiSegInferenceOperator.
48+
Loading of the model, and predicting using the in-proc PyTorch inference is done by MonaiSegInferenceOperator.
5249
5350
Named Input:
5451
image: Image object.
@@ -58,7 +55,7 @@ class LiverTumorSegOperator(Operator):
5855
saved_images_folder: Path to the folder with intermediate image output, not requiring a downstream receiver.
5956
"""
6057

61-
DEFAULT_OUTPUT_FOLDER = Path.cwd() / "saved_images_folder"
58+
DEFAULT_OUTPUT_FOLDER = Path.cwd() / "output/saved_images_folder"
6259

6360
def __init__(
6461
self, frament: Fragment, *args, model_path: Path, output_folder: Path = DEFAULT_OUTPUT_FOLDER, **kwargs
@@ -73,29 +70,26 @@ def __init__(
7370
self.fragement = frament # Cache and later pass the Fragment/Application to contained operator(s)
7471
self.input_name_image = "image"
7572
self.output_name_seg = "seg_image"
73+
self.output_name_saved_images_folder = "saved_images_folder"
7674

7775
self.fragement = frament # Cache and later pass the Fragment/Application to contained operator(s)
7876
super().__init__(frament, *args, **kwargs)
7977

8078
def setup(self, spec: OperatorSpec):
8179
spec.input(self.input_name_image)
8280
spec.output(self.output_name_seg)
81+
spec.output(self.output_name_saved_images_folder).condition(
82+
ConditionType.NONE
83+
) # Output not requiring a receiver
8384

8485
def compute(self, op_input, op_output, context):
8586
input_image = op_input.receive(self.input_name_image)
8687
if not input_image:
8788
raise ValueError("Input image is not found.")
8889

89-
# Get the output path from the execution context for saving file(s) to app output.
90-
# Without using this path, operator would be saving files to its designated path
91-
# op_output_folder_name = "saved_images_folder"
92-
# op_output.set(op_output_folder_name, "saved_images_folder")
93-
# op_output_folder_path = Path(op_output_folder_name) # op_output.get("saved_images_folder").path
94-
# op_output_folder_path.mkdir(parents=True, exist_ok=True)
95-
# print(f"Operator output folder path: {op_output_folder_path}")
96-
9790
# This operator gets an in-memory Image object, so a specialized ImageReader is needed.
9891
_reader = InMemImageReader(input_image)
92+
9993
# In this example, the input image, once loaded at the beginning of the pre-transforms, is
10094
# saved on disk, so is the segmentation prediction image at the end of the post-transform.
10195
# They are both saved in the same subfolder of the application output folder, with names
@@ -119,16 +113,19 @@ def compute(self, op_input, op_output, context):
119113
model_path=self.model_path,
120114
)
121115

122-
# Setting the keys used in the dictironary based transforms may change.
116+
# Setting the keys used in the dictionary based transforms
123117
infer_operator.input_dataset_key = self._input_dataset_key
124118
infer_operator.pred_dataset_key = self._pred_dataset_key
125119

126-
# Now let the built-in operator handle the work with the I/O spec and execution context.
120+
# Now emit data to the output ports of this operator
127121
op_output.emit(infer_operator.compute_impl(input_image, context), self.output_name_seg)
122+
op_output.emit(self.output_folder, self.output_name_saved_images_folder)
128123

129124
def pre_process(self, img_reader, out_dir: str = "./input_images") -> Compose:
130125
"""Composes transforms for preprocessing input before predicting on a model."""
131126

127+
Path(out_dir).mkdir(parents=True, exist_ok=True)
128+
132129
my_key = self._input_dataset_key
133130
return Compose(
134131
[
@@ -139,6 +136,7 @@ def pre_process(self, img_reader, out_dir: str = "./input_images") -> Compose:
139136
output_dir=out_dir,
140137
output_postfix="",
141138
resample=False,
139+
output_ext=".nii", # favor speed over size
142140
),
143141
Spacingd(keys=my_key, pixdim=(1.0, 1.0, 1.0), mode=("bilinear"), align_corners=True),
144142
ScaleIntensityRanged(my_key, a_min=-21, a_max=189, b_min=0.0, b_max=1.0, clip=True),
@@ -149,6 +147,8 @@ def pre_process(self, img_reader, out_dir: str = "./input_images") -> Compose:
149147
def post_process(self, pre_transforms: Compose, out_dir: str = "./prediction_output") -> Compose:
150148
"""Composes transforms for postprocessing the prediction results."""
151149

150+
Path(out_dir).mkdir(parents=True, exist_ok=True)
151+
152152
pred_key = self._pred_dataset_key
153153
return Compose(
154154
[
@@ -157,6 +157,13 @@ def post_process(self, pre_transforms: Compose, out_dir: str = "./prediction_out
157157
Invertd(
158158
keys=pred_key, transform=pre_transforms, orig_keys=self._input_dataset_key, nearest_interp=True
159159
),
160-
SaveImaged(keys=pred_key, output_dir=out_dir, output_postfix="seg", output_dtype=uint8, resample=False),
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+
),
161168
]
162169
)

0 commit comments

Comments
 (0)