Skip to content

Commit e2591dc

Browse files
Add NiftiDataWriter operator and update NiftiDataLoader to return SimpleITK image
Signed-off-by: Simone Bendazzoli <[email protected]>
1 parent d587183 commit e2591dc

File tree

4 files changed

+114
-2
lines changed

4 files changed

+114
-2
lines changed

monai/deploy/operators/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,4 @@
5454
from .png_converter_operator import PNGConverterOperator
5555
from .publisher_operator import PublisherOperator
5656
from .stl_conversion_operator import STLConversionOperator, STLConverter
57+
from .nii_data_writer_operator import NiftiDataWriter

monai/deploy/operators/monai_bundle_inference_operator.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
import numpy as np
2525

26+
import SimpleITK
27+
from SimpleITK import Image as SimpleITKImage
2628
from monai.deploy.core import AppContext, Fragment, Image, IOType, OperatorSpec
2729
from monai.deploy.utils.importutil import optional_import
2830

@@ -703,6 +705,16 @@ def _receive_input(self, name: str, op_input, context):
703705
logging.debug(f"Metadata of the converted input image: {metadata}")
704706
elif isinstance(value, np.ndarray):
705707
value = torch.from_numpy(value).to(self._device)
708+
elif isinstance(value, SimpleITKImage):
709+
metadata = {}
710+
metadata["pixdim"] = np.asarray(value.GetSpacing())
711+
metadata["origin"] = np.asarray(value.GetOrigin())
712+
metadata["direction"] = np.asarray(value.GetDirection())
713+
if len(value.GetSize()) == 3:
714+
metadata["pixdim"] = np.insert(np.asarray(value.GetSpacing()), 0, 1.0)
715+
value = np.transpose(SimpleITK.GetArrayFromImage(value), [2, 1, 0])
716+
else:
717+
value = np.transpose(SimpleITK.GetArrayFromImage(value), [0, 3, 2, 1])
706718

707719
# else value is some other object from memory
708720

monai/deploy/operators/nii_data_loader_operator.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,7 @@ def convert_and_save(self, nii_path):
8080
image_reader = SimpleITK.ImageFileReader()
8181
image_reader.SetFileName(str(nii_path))
8282
image = image_reader.Execute()
83-
image_np = np.transpose(SimpleITK.GetArrayFromImage(image), [2, 1, 0])
84-
return image_np
83+
return image
8584

8685

8786
def test():
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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+
12+
13+
import logging
14+
from pathlib import Path
15+
16+
import numpy as np
17+
18+
from monai.deploy.core import ConditionType, Fragment, Operator, OperatorSpec
19+
from monai.deploy.utils.importutil import optional_import
20+
21+
SimpleITK, _ = optional_import("SimpleITK")
22+
23+
24+
# @md.env(pip_packages=["SimpleITK>=2.0.2"])
25+
class NiftiDataWriter(Operator):
26+
27+
def __init__(self, fragment: Fragment, *args, output_file: Path, **kwargs) -> None:
28+
"""Creates an instance with the file path to load image from.
29+
30+
Args:
31+
fragment (Fragment): An instance of the Application class which is derived from Fragment.
32+
input_path (Path): The file Path to read from, overridden by valid named input on compute.
33+
"""
34+
self._logger = logging.getLogger("{}.{}".format(__name__, type(self).__name__))
35+
36+
self.output_file = output_file
37+
self.input_name_seg = "seg_image"
38+
self.input_name_output_file = "output_file"
39+
40+
# Need to call the base class constructor last
41+
super().__init__(fragment, *args, **kwargs)
42+
43+
def setup(self, spec: OperatorSpec):
44+
spec.input(self.input_name_seg)
45+
spec.input(self.input_name_output_file).condition(ConditionType.NONE) # Optional input not requiring sender.
46+
47+
def compute(self, op_input, op_output, context):
48+
"""Performs computation with the provided context."""
49+
50+
51+
seg_image = op_input.receive(self.input_name_seg)
52+
53+
54+
# If the optional named input, output_folder, has content, use it instead of the one set on the object.
55+
# Since this input is optional, must check if data present and if Path or str.
56+
output_file = None
57+
try:
58+
output_file = op_input.receive(self.input_name_output_file)
59+
except Exception:
60+
pass
61+
62+
if not output_file or not isinstance(output_file, (Path, str)):
63+
output_file = self.output_file
64+
65+
self.convert_and_save(seg_image, output_file)
66+
67+
def convert_and_save(self, seg_image, nii_path):
68+
"""
69+
reads the nifti image and returns a numpy image array
70+
"""
71+
image_writer = SimpleITK.ImageFileWriter()
72+
73+
image = SimpleITK.GetImageFromArray(seg_image._data)
74+
image.SetSpacing(seg_image.metadata()["pixdim"])
75+
76+
if len(seg_image.metadata()["direction"]) == 16:
77+
direction = []
78+
direction.extend(seg_image.metadata()["direction"][0:3])
79+
direction.extend(seg_image.metadata()["direction"][4:7])
80+
direction.extend(seg_image.metadata()["direction"][8:11])
81+
image.SetDirection(direction)
82+
else:
83+
image.SetDirection(seg_image.metadata()["direction"])
84+
85+
image.SetOrigin(seg_image.metadata()["origin"])
86+
87+
image_writer.SetFileName(nii_path)
88+
image_writer.Execute(image)
89+
90+
91+
def test():
92+
...
93+
94+
95+
def main():
96+
test()
97+
98+
99+
if __name__ == "__main__":
100+
main()

0 commit comments

Comments
 (0)