Skip to content

Commit 9006298

Browse files
authored
Update UNETR inference application to publish images for Clara Render Server (#182)
* Added the transform to save the input image in nii.gz, for RS publishing Signed-off-by: mmelqin <mingmelvinq@nvidia.com> * Add publishing input and seg image for rendering Signed-off-by: mmelqin <mingmelvinq@nvidia.com> * Make use of the new publish operator for saving dataset for Render Server Signed-off-by: mmelqin <mingmelvinq@nvidia.com> * Update by run check --autofix Signed-off-by: mmelqin <mingmelvinq@nvidia.com> * Fix unused from os import path Signed-off-by: mmelqin <mingmelvinq@nvidia.com> * Change to use Path.mkdir Signed-off-by: mmelqin <mingmelvinq@nvidia.com>
1 parent 4d15f08 commit 9006298

File tree

2 files changed

+24
-5
lines changed

2 files changed

+24
-5
lines changed

examples/apps/ai_unetr_seg_app/app.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from monai.deploy.operators.dicom_seg_writer_operator import DICOMSegmentationWriterOperator
1919
from monai.deploy.operators.dicom_series_selector_operator import DICOMSeriesSelectorOperator
2020
from monai.deploy.operators.dicom_series_to_volume_operator import DICOMSeriesToVolumeOperator
21+
from monai.deploy.operators.publisher_operator import PublisherOperator
2122

2223

2324
@resource(cpu=1, gpu=1, memory="7Gi")
@@ -46,6 +47,9 @@ def compose(self):
4647
series_to_vol_op = DICOMSeriesToVolumeOperator()
4748
# Model specific inference operator, supporting MONAI transforms.
4849
unetr_seg_op = UnetrSegOperator()
50+
# Create the publisher operator
51+
publisher_op = PublisherOperator()
52+
4953
# Creates DICOM Seg writer with segment label name in a string list
5054
dicom_seg_writer = DICOMSegmentationWriterOperator(
5155
seg_labels=[
@@ -64,15 +68,19 @@ def compose(self):
6468
"lad",
6569
]
6670
)
67-
6871
# Create the processing pipeline, by specifying the upstream and downstream operators, and
6972
# ensuring the output from the former matches the input of the latter, in both name and type.
7073
self.add_flow(study_loader_op, series_selector_op, {"dicom_study_list": "dicom_study_list"})
7174
self.add_flow(series_selector_op, series_to_vol_op, {"dicom_series": "dicom_series"})
7275
self.add_flow(series_to_vol_op, unetr_seg_op, {"image": "image"})
7376
# Note below the dicom_seg_writer requires two inputs, each coming from a upstream operator.
77+
# Also note that the DICOMSegmentationWriterOperator may throw exception with some inputs.
78+
# Bug has been created to track the issue.
7479
self.add_flow(series_selector_op, dicom_seg_writer, {"dicom_series": "dicom_series"})
7580
self.add_flow(unetr_seg_op, dicom_seg_writer, {"seg_image": "seg_image"})
81+
# Add the publishing operator to save the input and seg images for Render Server.
82+
# Note the PublisherOperator has temp impl till a proper rendering module is created.
83+
self.add_flow(unetr_seg_op, publisher_op, {"saved_images_folder": "saved_images_folder"})
7684

7785
self._logger.debug(f"End {self.compose.__name__}")
7886

examples/apps/ai_unetr_seg_app/unetr_seg_operator.py

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

1212
import logging
13-
from os import path
1413

1514
from numpy import uint8
1615

1716
import monai.deploy.core as md
18-
from monai.deploy.core import ExecutionContext, Image, InputContext, IOType, Operator, OutputContext
17+
from monai.deploy.core import DataPath, ExecutionContext, Image, InputContext, IOType, Operator, OutputContext
1918
from monai.deploy.operators.monai_seg_inference_operator import InMemImageReader, MonaiSegInferenceOperator
2019
from monai.transforms import (
2120
Activationsd,
@@ -35,6 +34,7 @@
3534

3635
@md.input("image", Image, IOType.IN_MEMORY)
3736
@md.output("seg_image", Image, IOType.IN_MEMORY)
37+
@md.output("saved_images_folder", DataPath, IOType.DISK)
3838
@md.env(pip_packages=["monai==0.6.0", "torch>=1.5", "numpy>=1.17", "nibabel"])
3939
class UnetrSegOperator(Operator):
4040
"""Performs multi-organ segmentation using UNETR model with an image converted from a DICOM CT series.
@@ -62,12 +62,16 @@ def compute(self, op_input: InputContext, op_output: OutputContext, context: Exe
6262
# Get the output path from the execution context for saving file(s) to app output.
6363
# Without using this path, operator would be saving files to its designated path, e.g.
6464
# $PWD/.monai_workdir/operators/6048d75a-5de1-45b9-8bd1-2252f88827f2/0/output
65-
output_path = context.output.get().path
65+
op_output_folder_name = DataPath("saved_images_folder")
66+
op_output.set(op_output_folder_name, "saved_images_folder")
67+
op_output_folder_path = op_output.get("saved_images_folder").path
68+
op_output_folder_path.mkdir(parents=True, exist_ok=True)
69+
print(f"Operator output folder path: {op_output_folder_path}")
6670

6771
# This operator gets an in-memory Image object, so a specialized ImageReader is needed.
6872
_reader = InMemImageReader(input_image)
6973
pre_transforms = self.pre_process(_reader)
70-
post_transforms = self.post_process(pre_transforms, path.join(output_path, "prediction_output"))
74+
post_transforms = self.post_process(pre_transforms, op_output_folder_path)
7175

7276
# Delegates inference and saving output to the built-in operator.
7377
infer_operator = MonaiSegInferenceOperator(
@@ -115,5 +119,12 @@ def post_process(self, pre_transforms: Compose, out_dir: str = "./prediction_out
115119
keys=pred_key, transform=pre_transforms, orig_keys=self._input_dataset_key, nearest_interp=True
116120
),
117121
SaveImaged(keys=pred_key, output_dir=out_dir, output_postfix="seg", output_dtype=uint8, resample=False),
122+
SaveImaged(
123+
keys=self._input_dataset_key,
124+
output_dir=out_dir,
125+
output_postfix="",
126+
output_dtype=uint8,
127+
resample=False,
128+
),
118129
]
119130
)

0 commit comments

Comments
 (0)