1313import os
1414import shutil
1515import tempfile
16+ from ast import Bytes
1617from pathlib import Path
1718from typing import Dict , Optional
1819
2930trimesh , _ = optional_import ("trimesh" )
3031
3132import monai .deploy .core as md
32- from monai .deploy .core import DataPath , ExecutionContext , Image , InputContext , IOType , Operator , OutputContext
33+ from monai .deploy .core import ExecutionContext , Image , InputContext , IOType , Operator , OutputContext
3334
3435__all__ = ["STLConversionOperator" , "STLConverter" ]
3536
3637
3738@md .input ("image" , Image , IOType .IN_MEMORY )
38- @md .output ("stl_output" , DataPath , IOType .DISK )
39+ @md .output ("stl_output" , Bytes , IOType .IN_MEMORY ) # Only available when run as non-leaf operator
3940# nibabel is required by the dependent class STLConverter.
4041@md .env (
4142 pip_packages = ["numpy>=1.21" , "nibabel >= 3.2.1" , "numpy-stl>=2.12.0" , "scikit-image>=0.17.2" , "trimesh>=3.8.11" ]
4243)
4344class STLConversionOperator (Operator ):
44- """Converts volumetric image to surface mesh in STL format, file output only."""
45+ """Converts volumetric image to surface mesh in STL format, file output only.
46+
47+ Only when used as a non-leaf operator is the output of STL binary stored in memory idenfied by the output label.
48+ If a file path is provided, the STL binary will be saved in the the application's output folder of the current run.
49+ """
4550
4651 def __init__ (
4752 self , output_file = None , class_id = None , is_smooth = True , keep_largest_connected_component = True , * args , ** kwargs
@@ -59,16 +64,17 @@ def __init__(
5964 self ._class_id = class_id
6065 self ._is_smooth = is_smooth
6166 self ._keep_largest_connected_component = keep_largest_connected_component
62- self ._output_file = output_file if output_file and len (output_file ) > 0 else None
67+ self ._output_file = output_file if output_file and len (str ( output_file ) ) > 0 else None
6368
6469 self ._converter = STLConverter (* args , ** kwargs )
6570
6671 def compute (self , op_input : InputContext , op_output : OutputContext , context : ExecutionContext ):
6772 """Gets the input (image), processes it and sets results in the output.
6873
6974 When used in a leaf operator, this function cannot set its output as in-memory object due to
70- current limitation, and only file output, for DataPath IOType_DISK, will be saved in the
71- op_output path, which is mapped to the application's output path by the execution engine.
75+ current limitation.
76+ If a file path is provided, the STL binary will be saved in the the application's output
77+ folder of the current run.
7278
7379 Args:
7480 op_input (InputContext): An input context for the operator.
@@ -80,20 +86,21 @@ def compute(self, op_input: InputContext, op_output: OutputContext, context: Exe
8086 if not input_image :
8187 raise ValueError ("Input is None." )
8288
83- op_output_config = op_output .get ()
84- if self ._output_file and len (self ._output_file ) > 0 :
85- # The file output folder is either the op_output or app's output depending on output types.
86- output_folder = (
87- op_output_config .path if isinstance (op_output_config , DataPath ) else context .output .get ().path
88- )
89- self ._output_file = output_folder / self ._output_file
90- self ._output_file .parent .mkdir (exist_ok = True )
91- self ._logger .info (f"Output will be saved in file { self ._output_file } ." )
89+ # Use the app's current run output folder as parent to the STL output path.
90+ if self ._output_file and len (str (self ._output_file )) > 0 :
91+ _output_file = context .output .get ().path / self ._output_file
92+ _output_file .parent .mkdir (parents = True , exist_ok = True )
93+ self ._logger .info (f"Output will be saved in file { _output_file } ." )
9294
93- stl_bytes = self ._convert (input_image , self . _output_file )
95+ stl_bytes = self ._convert (input_image , _output_file )
9496
95- if not isinstance (op_output_config , DataPath ):
96- op_output .set (stl_bytes )
97+ try :
98+ # TODO: Need a way to find if the operator is run as leaf node in order to
99+ # avoid setting in_memory object.
100+ if self .op_info .get_storage_type ("output" , "stl_output" ) == IOType .IN_MEMORY :
101+ op_output .set (stl_bytes )
102+ except Exception as ex :
103+ self ._logger .warn (f"In_memory output cannot be used when run as non-leaf operator. { ex } " )
97104
98105 def _convert (self , image : Image , output_file : Optional [Path ] = None ):
99106 """
@@ -152,12 +159,8 @@ def convert(
152159 if not image or not isinstance (image , Image ):
153160 raise ValueError ("image is not a Image object." )
154161
155- if not isinstance (output_file , Path ):
156- raise ValueError ("output_file is not a Path" )
157-
158- # Ensure output file's folder exists
159- if output_file .parent :
160- output_file .parent .mkdir (exist_ok = True )
162+ if isinstance (output_file , Path ):
163+ output_file .parent .mkdir (parents = True , exist_ok = True )
161164
162165 s_image = self .SpatialImage (image )
163166 nda = s_image .image_array
0 commit comments