Source code for monai.deploy.operators.clara_viz_operator

# Copyright 2022 MONAI Consortium
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#     http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import numpy as np

import monai.deploy.core as md
from monai.deploy.core import ExecutionContext, Image, InputContext, IOType, Operator, OutputContext
from monai.deploy.utils.importutil import optional_import

DataDefinition, _ = optional_import("clara.viz.core", name="DataDefinition")
Widget, _ = optional_import("clara.viz.widgets", name="Widget")
display, _ = optional_import("IPython.display", name="display")
interactive, _ = optional_import("ipywidgets", name="interactive")
Dropdown, _ = optional_import("ipywidgets", name="Dropdown")
Box, _ = optional_import("ipywidgets", name="Box")
VBox, _ = optional_import("ipywidgets", name="VBox")


[docs]@md.input("image", Image, IOType.IN_MEMORY) @md.input("seg_image", Image, IOType.IN_MEMORY) @md.env(pip_packages=["clara.viz.core", "clara.viz.widgets", "IPython"]) class ClaraVizOperator(Operator): """ This operator uses Clara Viz to provide interactive view of a 3D volume including segmentation mask. """
[docs] def __init__(self): """Constructor of the operator.""" super().__init__()
@staticmethod def _build_array(image, order): numpy_array = image.asnumpy() array = DataDefinition.Array(array=numpy_array, order=order) array.element_size = [1.0] array.element_size.append(image.metadata().get("col_pixel_spacing", 1.0)) array.element_size.append(image.metadata().get("row_pixel_spacing", 1.0)) array.element_size.append(image.metadata().get("depth_pixel_spacing", 1.0)) # the renderer is expecting data in RIP order (Right Inferior Posterior) which results in # this matrix target_affine_transform = [ [-1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, -1.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0], ] dicom_affine_transform = image.metadata().get("dicom_affine_transform", np.identity(4)) affine_transform = np.matmul(target_affine_transform, dicom_affine_transform) array.permute_axes = [ 0, max(range(3), key=lambda k: abs(affine_transform[0][k])) + 1, max(range(3), key=lambda k: abs(affine_transform[1][k])) + 1, max(range(3), key=lambda k: abs(affine_transform[2][k])) + 1, ] array.flip_axes = [ False, affine_transform[0][array.permute_axes[1] - 1] < 0.0, affine_transform[1][array.permute_axes[2] - 1] < 0.0, affine_transform[2][array.permute_axes[3] - 1] < 0.0, ] return array
[docs] def compute(self, op_input: InputContext, op_output: OutputContext, context: ExecutionContext): """Displays the input image and segmentation mask Args: op_input (InputContext): An input context for the operator. op_output (OutputContext): An output context for the operator. context (ExecutionContext): An execution context for the operator. """ input_image = op_input.get("image") if not input_image: raise ValueError("Input image is not found.") input_seg_image = op_input.get("seg_image") if not input_seg_image: raise ValueError("Input segmentation image is not found.") # build the data definition data_definition = DataDefinition() data_definition.arrays.append(self._build_array(input_image, "DXYZ")) data_definition.arrays.append(self._build_array(input_seg_image, "MXYZ")) widget = Widget() widget.select_data_definition(data_definition) # default view mode is 'CINEMATIC' switch to 'SLICE_SEGMENTATION' since we have no transfer functions defined widget.settings["Views"][0]["mode"] = "SLICE_SEGMENTATION" widget.settings["Views"][0]["cameraName"] = "Top" widget.set_settings() # add controls def set_view_mode(view_mode): widget.settings["Views"][0]["mode"] = view_mode if view_mode == "CINEMATIC": widget.settings["Views"][0]["cameraName"] = "Perspective" elif widget.settings["Views"][0]["cameraName"] == "Perspective": widget.settings["Views"][0]["cameraName"] = "Top" widget.set_settings() widget_view_mode = interactive( set_view_mode, view_mode=Dropdown( options=[("Cinematic", "CINEMATIC"), ("Slice", "SLICE"), ("Slice Segmentation", "SLICE_SEGMENTATION")], value="SLICE_SEGMENTATION", description="View mode", ), ) def set_camera(camera): if widget.settings["Views"][0]["mode"] != "CINEMATIC": widget.settings["Views"][0]["cameraName"] = camera widget.set_settings() widget_camera = interactive( set_camera, camera=Dropdown(options=["Top", "Right", "Front"], value="Top", description="Camera") ) display(Box([widget, VBox([widget_view_mode, widget_camera])]))