1
- # Copyright 2021 MONAI Consortium
1
+ # Copyright 2021-2023 MONAI Consortium
2
2
# Licensed under the Apache License, Version 2.0 (the "License");
3
3
# you may not use this file except in compliance with the License.
4
4
# You may obtain a copy of the License at
13
13
from os import getcwd , makedirs
14
14
from os .path import join
15
15
from pathlib import Path
16
+ from typing import Union
16
17
17
- from monai .deploy .core import Operator , OperatorSpec
18
+ import numpy as np
19
+
20
+ from monai .deploy .core import Fragment , Image , Operator , OperatorSpec
18
21
from monai .deploy .operators .dicom_data_loader_operator import DICOMDataLoaderOperator
19
22
from monai .deploy .operators .dicom_series_to_volume_operator import DICOMSeriesToVolumeOperator
20
23
from monai .deploy .utils .importutil import optional_import
21
24
22
25
PILImage , _ = optional_import ("PIL" , name = "Image" )
23
26
24
27
25
- # @md.env(pip_packages=["Pillow >= 8.0.0"])
28
+ # @md.env(pip_packages=["Pillow >= 8.0.0", "numpy" ])
26
29
class PNGConverterOperator (Operator ):
27
30
"""
28
- This operator writes out a 3D Volumetric Image to disk in a slice by slice manner
31
+ This operator writes out a 3D Volumetric Image to to a file folder in a slice by slice manner.
32
+
33
+ Named input:
34
+ image: Image object or numpy ndarray
35
+
36
+ Named output:
37
+ None
38
+
39
+ File output:
40
+ Generated PNG image file(s) saved in the provided output folder.
29
41
"""
30
42
31
- # The default output folder for saveing the generated DICOM instance file.
32
- # DEFAULT_OUTPUT_FOLDER = Path(os.path.join(os.path.dirname(__file__))) / "output"
43
+ # The default output folder for saving the generated DICOM instance file.
33
44
DEFAULT_OUTPUT_FOLDER = Path (getcwd ()) / "output"
34
45
35
- DEFAULT_OUTPUT_FOLDER = Path (getcwd ()) / "output"
46
+ def __init__ (
47
+ self ,
48
+ fragment : Fragment ,
49
+ * args ,
50
+ output_folder : Union [str , Path ],
51
+ ** kwargs ,
52
+ ):
53
+ """Class to write out a 3D Volumetric Image to a file folder in a slice by slice manner.
54
+
55
+ Args:
56
+ fragment (Fragment): An instance of the Application class which is derived from Fragment.
57
+ output_folder (str or Path): The folder for saving the generated DICOM instance file.
58
+ """
59
+
60
+ self .output_folder = output_folder if output_folder else PNGConverterOperator .DEFAULT_OUTPUT_FOLDER
61
+ self .input_name_image = "image"
62
+ # Need to call the base class constructor last
63
+ super ().__init__ (fragment , * args , ** kwargs )
64
+
65
+ def setup (self , spec : OperatorSpec ):
66
+ spec .input (self .input_name_image )
36
67
37
68
def compute (self , op_input , op_output , context ):
38
- input_path = op_input .receive ("image" )
39
- # output_dir = op_output.get().path
40
- output_dir .mkdir (parents = True , exist_ok = True )
41
- self .convert_and_save (input_path , output_dir )
69
+ input_image = op_input .receive (self .input_name_image )
70
+ self .output_folder .mkdir (parents = True , exist_ok = True )
71
+ self .convert_and_save (input_image , self .output_folder )
42
72
43
73
def convert_and_save (self , image , path ):
44
74
"""
45
75
extracts the slices in originally acquired direction (often axial)
46
- and saves then in PNG format slice by slice in the specified directory
76
+ and saves them in PNG format slice by slice in the specified directory
47
77
"""
48
- image_data = image .asnumpy ()
78
+
79
+ if isinstance (image , Image ):
80
+ image_data = image .asnumpy ()
81
+ elif isinstance (image , np .ndarray ):
82
+ image_data = image
83
+ else :
84
+ raise ValueError (f"Input is not Image or ndarray, { type (image )} ." )
49
85
image_shape = image_data .shape
50
86
51
87
num_images = image_shape [0 ]
@@ -62,31 +98,36 @@ def main():
62
98
from pathlib import Path
63
99
64
100
current_file_dir = Path (__file__ ).parent .resolve ()
65
- data_path = current_file_dir .joinpath ("../../../examples/ai_spleen_seg_data /dcm" )
66
- out_path = "png-output "
101
+ data_path = current_file_dir .joinpath ("../../../inputs/spleen_ct /dcm" )
102
+ out_path = "output_png "
67
103
makedirs (out_path , exist_ok = True )
68
104
69
105
files = []
70
- loader = DICOMDataLoaderOperator ()
106
+ fragment = Fragment ()
107
+ loader = DICOMDataLoaderOperator (fragment , name = "dcm_loader" )
71
108
loader ._list_files (
72
109
data_path ,
73
110
files ,
74
111
)
75
112
study_list = loader ._load_data (files )
76
113
series = study_list [0 ].get_all_series ()[0 ]
77
114
78
- op1 = DICOMSeriesToVolumeOperator ()
115
+ print (f"The loaded series object properties:\n { series } " )
116
+
117
+ op1 = DICOMSeriesToVolumeOperator (fragment , name = "series_to_vol" )
79
118
op1 .prepare_series (series )
80
119
voxels = op1 .generate_voxel_data (series )
81
120
metadata = op1 .create_metadata (series )
82
121
image = op1 .create_volumetric_image (voxels , metadata )
83
122
84
- op2 = PNGConverterOperator ()
85
- op2 .convert_and_save (image , out_path )
86
-
87
- print (f"The loaded series object properties:\n { series } " )
88
123
print (f"The converted Image object metadata:\n { metadata } " )
89
124
125
+ op2 = PNGConverterOperator (fragment , output_folder = out_path , name = "png_converter" )
126
+ # Not mocking the operator context, so bypassing compute
127
+ op2 .convert_and_save (image , op2 .output_folder )
128
+
129
+ print (f"The converted PNG files are in: { out_path } " )
130
+
90
131
91
132
if __name__ == "__main__" :
92
133
main ()
0 commit comments