Skip to content

Commit 6c74997

Browse files
Add Clara Viz operator.
Signed-off-by: Andreas Heumann <aheumann@nvidia.com>
1 parent 0aa7ef0 commit 6c74997

File tree

3 files changed

+1752
-1
lines changed

3 files changed

+1752
-1
lines changed

monai/deploy/operators/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2021 MONAI Consortium
1+
# Copyright 2021-2022 MONAI Consortium
22
# Licensed under the Apache License, Version 2.0 (the "License");
33
# you may not use this file except in compliance with the License.
44
# You may obtain a copy of the License at
@@ -12,6 +12,7 @@
1212
.. autosummary::
1313
:toctree: _autosummary
1414
15+
ClaraVizOperator
1516
DICOMDataLoaderOperator
1617
DICOMSegmentationWriterOperator
1718
DICOMSeriesSelectorOperator
@@ -22,6 +23,7 @@
2223
PublisherOperator
2324
"""
2425

26+
from .clara_viz_operator import ClaraVizOperator
2527
from .dicom_data_loader_operator import DICOMDataLoaderOperator
2628
from .dicom_seg_writer_operator import DICOMSegmentationWriterOperator
2729
from .dicom_series_selector_operator import DICOMSeriesSelectorOperator
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Copyright 2022 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+
import clara.viz.core
13+
import clara.viz.widgets
14+
import numpy as np
15+
from IPython.display import display
16+
17+
import monai.deploy.core as md
18+
from monai.deploy.core import ExecutionContext, Image, InputContext, IOType, Operator, OutputContext
19+
20+
21+
@md.input("image", Image, IOType.IN_MEMORY)
22+
@md.input("seg_image", Image, IOType.IN_MEMORY)
23+
class ClaraVizOperator(Operator):
24+
"""
25+
This operator uses Clara Viz to provide interactive view of a 3D volume including segmentation mask.
26+
"""
27+
28+
def __init__(self):
29+
"""Constructor of the operator."""
30+
super().__init__()
31+
32+
@staticmethod
33+
def _build_array(image, order):
34+
if order == "MXYZ":
35+
# @todo mask is transposed, see issue #171
36+
numpy_array = np.transpose(image.asnumpy(), (2, 1, 0))
37+
else:
38+
numpy_array = image.asnumpy()
39+
40+
array = clara.viz.core.DataDefinition.Array(array=numpy_array, order=order)
41+
array.element_size = [1.0]
42+
array.element_size.append(image.metadata().get("col_pixel_spacing", 1.0))
43+
array.element_size.append(image.metadata().get("row_pixel_spacing", 1.0))
44+
array.element_size.append(image.metadata().get("depth_pixel_spacing", 1.0))
45+
46+
# the renderer is expecting data in RIP order (Right Inferior Posterior) which results in
47+
# this matrix
48+
target_affine_transform = [
49+
[0.0, 1.0, 0.0, 0.0],
50+
[1.0, 0.0, 0.0, 0.0],
51+
[0.0, 0.0, -1.0, 0.0],
52+
[0.0, 0.0, 0.0, 1.0],
53+
]
54+
55+
dicom_affine_transform = image.metadata().get("dicom_affine_transform", np.identity(4))
56+
57+
affine_transform = np.matmul(target_affine_transform, dicom_affine_transform)
58+
59+
array.permute_axes = [
60+
0,
61+
max(range(3), key=lambda k: abs(affine_transform[0][k])) + 1,
62+
max(range(3), key=lambda k: abs(affine_transform[1][k])) + 1,
63+
max(range(3), key=lambda k: abs(affine_transform[2][k])) + 1,
64+
]
65+
66+
array.flip_axes = [
67+
False,
68+
affine_transform[0][array.permute_axes[1] - 1] < 0.0,
69+
affine_transform[1][array.permute_axes[2] - 1] < 0.0,
70+
affine_transform[2][array.permute_axes[3] - 1] < 0.0,
71+
]
72+
73+
return array
74+
75+
def compute(self, op_input: InputContext, op_output: OutputContext, context: ExecutionContext):
76+
"""Displays the input image and segmentation mask
77+
78+
Args:
79+
op_input (InputContext): An input context for the operator.
80+
op_output (OutputContext): An output context for the operator.
81+
context (ExecutionContext): An execution context for the operator.
82+
"""
83+
input_image = op_input.get("image")
84+
if not input_image:
85+
raise ValueError("Input image is not found.")
86+
input_seg_image = op_input.get("seg_image")
87+
if not input_seg_image:
88+
raise ValueError("Input segmentation image is not found.")
89+
90+
# build the data definition
91+
data_definition = clara.viz.core.DataDefinition()
92+
93+
data_definition.arrays.append(self._build_array(input_image, "DXYZ"))
94+
95+
data_definition.arrays.append(self._build_array(input_seg_image, "MXYZ"))
96+
97+
widget = clara.viz.widgets.Widget()
98+
widget.select_data_definition(data_definition)
99+
widget.settings["Views"][0]["mode"] = "SLICE_SEGMENTATION"
100+
widget.settings["Views"][0]["cameraName"] = "Top"
101+
widget.set_settings()
102+
display(widget)

0 commit comments

Comments
 (0)