14
14
15
15
from numpy import uint8
16
16
17
- from monai .deploy .core import Fragment , Operator , OperatorSpec
17
+ from monai .deploy .core import ConditionType , Fragment , Operator , OperatorSpec
18
18
from monai .deploy .operators .monai_seg_inference_operator import InMemImageReader , MonaiSegInferenceOperator
19
19
from monai .transforms import (
20
20
Activationsd ,
30
30
)
31
31
32
32
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)
36
33
# @md.env(pip_packages=["monai>=1.0.0", "torch>=1.5", "numpy>=1.21", "nibabel"])
37
34
class LiverTumorSegOperator (Operator ):
38
35
"""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):
41
38
https://ngc.nvidia.com/catalog/models/nvidia:med:clara_pt_liver_and_tumor_ct_segmentation
42
39
43
40
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" .
46
43
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.
48
45
It creates the pre-transforms as well as post-transforms with MONAI dictionary based transforms.
49
46
Note that the App SDK InMemImageReader, derived from MONAI ImageReader, is passed to LoadImaged.
50
47
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.
52
49
53
50
Named Input:
54
51
image: Image object.
@@ -58,7 +55,7 @@ class LiverTumorSegOperator(Operator):
58
55
saved_images_folder: Path to the folder with intermediate image output, not requiring a downstream receiver.
59
56
"""
60
57
61
- DEFAULT_OUTPUT_FOLDER = Path .cwd () / "saved_images_folder"
58
+ DEFAULT_OUTPUT_FOLDER = Path .cwd () / "output/ saved_images_folder"
62
59
63
60
def __init__ (
64
61
self , frament : Fragment , * args , model_path : Path , output_folder : Path = DEFAULT_OUTPUT_FOLDER , ** kwargs
@@ -73,29 +70,26 @@ def __init__(
73
70
self .fragement = frament # Cache and later pass the Fragment/Application to contained operator(s)
74
71
self .input_name_image = "image"
75
72
self .output_name_seg = "seg_image"
73
+ self .output_name_saved_images_folder = "saved_images_folder"
76
74
77
75
self .fragement = frament # Cache and later pass the Fragment/Application to contained operator(s)
78
76
super ().__init__ (frament , * args , ** kwargs )
79
77
80
78
def setup (self , spec : OperatorSpec ):
81
79
spec .input (self .input_name_image )
82
80
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
83
84
84
85
def compute (self , op_input , op_output , context ):
85
86
input_image = op_input .receive (self .input_name_image )
86
87
if not input_image :
87
88
raise ValueError ("Input image is not found." )
88
89
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
-
97
90
# This operator gets an in-memory Image object, so a specialized ImageReader is needed.
98
91
_reader = InMemImageReader (input_image )
92
+
99
93
# In this example, the input image, once loaded at the beginning of the pre-transforms, is
100
94
# saved on disk, so is the segmentation prediction image at the end of the post-transform.
101
95
# 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):
119
113
model_path = self .model_path ,
120
114
)
121
115
122
- # Setting the keys used in the dictironary based transforms may change.
116
+ # Setting the keys used in the dictionary based transforms
123
117
infer_operator .input_dataset_key = self ._input_dataset_key
124
118
infer_operator .pred_dataset_key = self ._pred_dataset_key
125
119
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
127
121
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 )
128
123
129
124
def pre_process (self , img_reader , out_dir : str = "./input_images" ) -> Compose :
130
125
"""Composes transforms for preprocessing input before predicting on a model."""
131
126
127
+ Path (out_dir ).mkdir (parents = True , exist_ok = True )
128
+
132
129
my_key = self ._input_dataset_key
133
130
return Compose (
134
131
[
@@ -139,6 +136,7 @@ def pre_process(self, img_reader, out_dir: str = "./input_images") -> Compose:
139
136
output_dir = out_dir ,
140
137
output_postfix = "" ,
141
138
resample = False ,
139
+ output_ext = ".nii" , # favor speed over size
142
140
),
143
141
Spacingd (keys = my_key , pixdim = (1.0 , 1.0 , 1.0 ), mode = ("bilinear" ), align_corners = True ),
144
142
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:
149
147
def post_process (self , pre_transforms : Compose , out_dir : str = "./prediction_output" ) -> Compose :
150
148
"""Composes transforms for postprocessing the prediction results."""
151
149
150
+ Path (out_dir ).mkdir (parents = True , exist_ok = True )
151
+
152
152
pred_key = self ._pred_dataset_key
153
153
return Compose (
154
154
[
@@ -157,6 +157,13 @@ def post_process(self, pre_transforms: Compose, out_dir: str = "./prediction_out
157
157
Invertd (
158
158
keys = pred_key , transform = pre_transforms , orig_keys = self ._input_dataset_key , nearest_interp = True
159
159
),
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
+ ),
161
168
]
162
169
)
0 commit comments