11
11
12
12
import logging
13
13
import os
14
- from ast import Bytes
15
14
from io import BytesIO
16
15
from pathlib import Path
17
- from typing import Dict , List , Optional , Union
16
+ from typing import Dict , Optional , Union
18
17
19
18
from monai .deploy .utils .importutil import optional_import
20
19
27
26
Sequence , _ = optional_import ("pydicom.sequence" , name = "Sequence" )
28
27
PdfReader , _ = optional_import ("PyPDF2" , name = "PdfReader" )
29
28
30
- from monai .deploy .core import Operator , OperatorSpec
29
+ from monai .deploy .core import ConditionType , Fragment , Operator , OperatorSpec
31
30
from monai .deploy .core .domain .dicom_series import DICOMSeries
32
31
from monai .deploy .core .domain .dicom_series_selection import StudySelectedSeries
33
- from monai .deploy .exceptions import ItemNotExistsError
34
32
from monai .deploy .operators .dicom_utils import EquipmentInfo , ModelInfo , save_dcm_file , write_common_modules
35
33
from monai .deploy .utils .version import get_sdk_semver
36
34
37
35
38
36
# @md.env(pip_packages=["pydicom >= 1.4.2", "PyPDF2 >= 2.11.1", "monai"])
39
37
class DICOMEncapsulatedPDFWriterOperator (Operator ):
38
+ """Class to write DICOM Encapsulated PDF Instance with provided PDF bytes in memory.
39
+
40
+ Named inputs:
41
+ pdf_bytes: Bytes of the the PDF content.
42
+ study_selected_series_list: Optional, DICOM series for copying metadata from.
43
+
44
+ Named output:
45
+ None
46
+
47
+ File output:
48
+ Generaed DICOM instance file in the provided output folder.
49
+ """
50
+
40
51
# File extension for the generated DICOM Part 10 file.
41
52
DCM_EXTENSION = ".dcm"
42
53
# The default output folder for saveing the generated DICOM instance file.
43
- # DEFAULT_OUTPUT_FOLDER = Path(os.path.join(os.path.dirname(__file__))) / "output"
44
54
DEFAULT_OUTPUT_FOLDER = Path (os .getcwd ()) / "output"
45
55
46
56
def __init__ (
47
57
self ,
58
+ fragment : Fragment ,
48
59
* args ,
49
60
output_folder : Union [str , Path ],
50
61
copy_tags : bool ,
@@ -56,6 +67,7 @@ def __init__(
56
67
"""Class to write DICOM Encapsulated PDF Instance with PDF bytes in memory or in a file.
57
68
58
69
Args:
70
+ fragment (Fragment): An instance of the Application class which is derived from Fragment.
59
71
output_folder (str or Path): The folder for saving the generated DICOM instance file.
60
72
copy_tags (bool): True for copying DICOM attributes from a provided DICOMSeries.
61
73
If True and no DICOMSeries obj provided, runtime exception is thrown.
@@ -81,6 +93,8 @@ def __init__(
81
93
self .model_info = model_info if model_info else ModelInfo ()
82
94
self .equipment_info = equipment_info if equipment_info else EquipmentInfo ()
83
95
self .custom_tags = custom_tags
96
+ self .input_name_bytes = "pdf_bytes"
97
+ self .input_name_dcm_series = "study_selected_series_list"
84
98
85
99
# Set own Modality and SOP Class UID
86
100
# Modality, e.g.,
@@ -101,7 +115,7 @@ def __init__(
101
115
except Exception :
102
116
self .software_version_number = ""
103
117
self .operators_name = f"AI Algorithm { self .model_info .name } "
104
- super ().__init__ (* args , ** kwargs )
118
+ super ().__init__ (fragment , * args , ** kwargs )
105
119
106
120
def setup (self , spec : OperatorSpec ):
107
121
"""Set up the named input(s), and output(s) if applicable.
@@ -112,8 +126,8 @@ def setup(self, spec: OperatorSpec):
112
126
spec (OperatorSpec): The Operator specification for inputs and outputs etc.
113
127
"""
114
128
115
- spec .input ("pdf_bytes" )
116
- spec .input ("study_selected_series_list" )
129
+ spec .input (self . input_name_bytes )
130
+ spec .input (self . input_name_dcm_series ). condition ( ConditionType . NONE ) # Optional input
117
131
118
132
def compute (self , op_input , op_output , context ):
119
133
"""Performs computation for this operator and handles I/O.
@@ -133,24 +147,13 @@ def compute(self, op_input, op_output, context):
133
147
134
148
# Gets the input, prepares the output folder, and then delegates the processing.
135
149
pdf_bytes : bytes = b""
136
- try :
137
- pdf_bytes = op_input .receive ("pdf_bytes" )
138
- except ItemNotExistsError :
139
- # try:
140
- # file_path = op_input.receive("pdf_file")
141
- # except ItemNotExistsError:
142
- # raise ValueError("None of the named inputs can be found.") from None
143
- # # Read file, and if exception, let it bubble up
144
- # with open(file_path.path, "rb") as f:
145
- # pdf_bytes = f.read().strip()
146
- pass
147
-
150
+ pdf_bytes = op_input .receive (self .input_name_bytes )
148
151
if not pdf_bytes or not len (pdf_bytes .strip ()):
149
152
raise IOError ("Input is read but blank." )
150
153
151
154
try :
152
- study_selected_series_list = op_input .receive ("study_selected_series_list" )
153
- except ItemNotExistsError :
155
+ study_selected_series_list = op_input .receive (self . input_name_dcm_series )
156
+ except Exception :
154
157
study_selected_series_list = None
155
158
156
159
dicom_series = None # It can be None if not to copy_tags.
@@ -245,24 +248,27 @@ def _is_pdf_bytes(self, content: bytes):
245
248
return True
246
249
247
250
248
- def test ():
251
+ def test (test_copy_tags : bool = True ):
249
252
from monai .deploy .operators .dicom_data_loader_operator import DICOMDataLoaderOperator
250
253
from monai .deploy .operators .dicom_series_selector_operator import DICOMSeriesSelectorOperator
251
254
252
255
current_file_dir = Path (__file__ ).parent .resolve ()
253
256
dcm_folder = current_file_dir .joinpath ("../../../inputs/livertumor_ct/dcm/1-CT_series_liver_tumor_from_nii014" )
254
257
pdf_file = current_file_dir .joinpath ("../../../inputs/pdf/TestPDF.pdf" )
255
- out_path = "output_pdf_op"
258
+ out_path = Path ( "output_pdf_op" ). absolute ()
256
259
pdf_bytes = b"Not PDF bytes."
257
- test_copy_tags = False
258
260
259
- loader = DICOMDataLoaderOperator ()
260
- series_selector = DICOMSeriesSelectorOperator ()
261
+ fragment = Fragment ()
262
+ loader = DICOMDataLoaderOperator (fragment , name = "loader_op" )
263
+ series_selector = DICOMSeriesSelectorOperator (fragment , name = "selector_op" )
261
264
sr_writer = DICOMEncapsulatedPDFWriterOperator (
265
+ fragment ,
266
+ output_folder = out_path ,
262
267
copy_tags = test_copy_tags ,
263
268
model_info = None ,
264
269
equipment_info = EquipmentInfo (),
265
270
custom_tags = {"SeriesDescription" : "Report from AI algorithm. Not for clinical use." },
271
+ name = "writer_op" ,
266
272
)
267
273
268
274
# Testing with the main entry functions
@@ -284,8 +290,9 @@ def test():
284
290
with open (pdf_file , "rb" ) as f :
285
291
pdf_bytes = f .read ()
286
292
287
- sr_writer .write (pdf_bytes , dicom_series , Path ( out_path ). absolute () )
293
+ sr_writer .write (pdf_bytes , dicom_series , out_path )
288
294
289
295
290
296
if __name__ == "__main__" :
291
- test ()
297
+ test (test_copy_tags = True )
298
+ test (test_copy_tags = False )
0 commit comments