16
16
use std:: path:: PathBuf ;
17
17
18
18
use clap:: Parser ;
19
- use dicom_core:: { value:: PrimitiveValue , DataElement , VR } ;
19
+ use dicom_core:: {
20
+ value:: { PixelFragmentSequence , PrimitiveValue } ,
21
+ DataElement , DicomValue , VR ,
22
+ } ;
20
23
use dicom_dictionary_std:: tags;
21
- use dicom_object:: { open_file, FileMetaTableBuilder } ;
24
+ use dicom_object:: { open_file, DefaultDicomObject , FileMetaTableBuilder } ;
25
+ use image:: DynamicImage ;
26
+
27
+ type Result < T , E = snafu:: Whatever > = std:: result:: Result < T , E > ;
22
28
23
29
/// Convert and replace a DICOM file's image with another image
24
30
#[ derive( Debug , Parser ) ]
@@ -32,6 +38,13 @@ struct App {
32
38
/// (default is to replace input extension with `.new.dcm`)
33
39
#[ arg( short = 'o' , long = "out" ) ]
34
40
output : Option < PathBuf > ,
41
+ /// Override the transfer syntax UID (pixel data is not converted)
42
+ #[ arg( long = "transfer-syntax" , alias = "ts" ) ]
43
+ transfer_syntax : Option < String > ,
44
+ /// Encapsulate the image file raw data in a fragment sequence
45
+ /// instead of writing native pixel data
46
+ #[ arg( long) ]
47
+ encapsulate : bool ,
35
48
/// Retain the implementation class UID and version name from base DICOM
36
49
#[ arg( long) ]
37
50
retain_implementation : bool ,
@@ -50,6 +63,8 @@ fn main() {
50
63
dcm_file,
51
64
img_file,
52
65
output,
66
+ encapsulate,
67
+ transfer_syntax,
53
68
retain_implementation,
54
69
verbose,
55
70
} = App :: parse ( ) ;
@@ -65,11 +80,147 @@ fn main() {
65
80
std:: process:: exit ( -1 ) ;
66
81
} ) ;
67
82
68
- let img = image:: open ( img_file) . unwrap_or_else ( |e| {
83
+ if encapsulate {
84
+ inject_encapsulated ( & mut obj, img_file, verbose)
85
+ } else {
86
+ inject_image ( & mut obj, img_file, verbose)
87
+ }
88
+ . unwrap_or_else ( |e| {
89
+ tracing:: error!( "{}" , snafu:: Report :: from_error( e) ) ;
90
+ std:: process:: exit ( -2 ) ;
91
+ } ) ;
92
+
93
+ let class_uid = obj. meta ( ) . media_storage_sop_class_uid . clone ( ) ;
94
+
95
+ let mut meta_builder = FileMetaTableBuilder :: new ( )
96
+ // currently the tool will always decode the image's pixel data,
97
+ // so encode it as Explicit VR Little Endian
98
+ . transfer_syntax ( "1.2.840.10008.1.2.1" )
99
+ . media_storage_sop_class_uid ( class_uid) ;
100
+
101
+ if let Some ( ts) = transfer_syntax {
102
+ meta_builder = meta_builder. transfer_syntax ( ts) ;
103
+ }
104
+
105
+ // recover implementation class UID and version name from base object
106
+ if retain_implementation {
107
+ let implementation_class_uid = & obj. meta ( ) . implementation_class_uid ;
108
+ meta_builder = meta_builder. implementation_class_uid ( implementation_class_uid) ;
109
+
110
+ if let Some ( implementation_version_name) = obj. meta ( ) . implementation_version_name . as_ref ( ) {
111
+ meta_builder = meta_builder. implementation_version_name ( implementation_version_name) ;
112
+ }
113
+ }
114
+
115
+ let obj = obj
116
+ . into_inner ( )
117
+ . with_meta ( meta_builder)
118
+ . unwrap_or_else ( |e| {
119
+ tracing:: error!( "{}" , snafu:: Report :: from_error( e) ) ;
120
+ std:: process:: exit ( -3 ) ;
121
+ } ) ;
122
+
123
+ obj. write_to_file ( & output) . unwrap_or_else ( |e| {
124
+ tracing:: error!( "{}" , snafu:: Report :: from_error( e) ) ;
125
+ std:: process:: exit ( -4 ) ;
126
+ } ) ;
127
+
128
+ if verbose {
129
+ println ! ( "DICOM file saved to {}" , output. display( ) ) ;
130
+ }
131
+ }
132
+
133
+ fn inject_image ( obj : & mut DefaultDicomObject , img_file : PathBuf , verbose : bool ) -> Result < ( ) > {
134
+ let image_reader = image:: ImageReader :: open ( img_file) . unwrap_or_else ( |e| {
135
+ tracing:: error!( "{}" , snafu:: Report :: from_error( e) ) ;
136
+ std:: process:: exit ( -1 ) ;
137
+ } ) ;
138
+
139
+ let img = image_reader. decode ( ) . unwrap_or_else ( |e| {
140
+ tracing:: error!( "{}" , snafu:: Report :: from_error( e) ) ;
141
+ std:: process:: exit ( -1 ) ;
142
+ } ) ;
143
+
144
+ let color = img. color ( ) ;
145
+
146
+ let bits_stored: u16 = match color {
147
+ image:: ColorType :: L8 => 8 ,
148
+ image:: ColorType :: L16 => 16 ,
149
+ image:: ColorType :: Rgb8 => 8 ,
150
+ image:: ColorType :: Rgb16 => 16 ,
151
+ _ => {
152
+ eprintln ! ( "Unsupported image format {:?}" , color) ;
153
+ std:: process:: exit ( -2 ) ;
154
+ }
155
+ } ;
156
+
157
+ update_from_img ( obj, & img, verbose) ;
158
+
159
+ for tag in [
160
+ tags:: NUMBER_OF_FRAMES ,
161
+ tags:: PIXEL_ASPECT_RATIO ,
162
+ tags:: SMALLEST_IMAGE_PIXEL_VALUE ,
163
+ tags:: LARGEST_IMAGE_PIXEL_VALUE ,
164
+ tags:: PIXEL_PADDING_RANGE_LIMIT ,
165
+ tags:: RED_PALETTE_COLOR_LOOKUP_TABLE_DATA ,
166
+ tags:: RED_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR ,
167
+ tags:: GREEN_PALETTE_COLOR_LOOKUP_TABLE_DATA ,
168
+ tags:: GREEN_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR ,
169
+ tags:: BLUE_PALETTE_COLOR_LOOKUP_TABLE_DATA ,
170
+ tags:: BLUE_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR ,
171
+ tags:: ICC_PROFILE ,
172
+ tags:: COLOR_SPACE ,
173
+ tags:: PIXEL_DATA_PROVIDER_URL ,
174
+ tags:: EXTENDED_OFFSET_TABLE ,
175
+ tags:: EXTENDED_OFFSET_TABLE_LENGTHS ,
176
+ ] {
177
+ obj. remove_element ( tag) ;
178
+ }
179
+
180
+ let pixeldata = img. into_bytes ( ) ;
181
+
182
+ obj. put ( DataElement :: new (
183
+ tags:: PIXEL_DATA ,
184
+ if bits_stored == 8 { VR :: OB } else { VR :: OW } ,
185
+ PrimitiveValue :: from ( pixeldata) ,
186
+ ) ) ;
187
+
188
+ Ok ( ( ) )
189
+ }
190
+
191
+ fn inject_encapsulated (
192
+ dcm : & mut DefaultDicomObject ,
193
+ img_file : PathBuf ,
194
+ verbose : bool ,
195
+ ) -> Result < ( ) > {
196
+ let image_reader = image:: ImageReader :: open ( & img_file) . unwrap_or_else ( |e| {
69
197
tracing:: error!( "{}" , snafu:: Report :: from_error( e) ) ;
70
198
std:: process:: exit ( -1 ) ;
71
199
} ) ;
72
200
201
+ // collect img file data
202
+ let all_data = std:: fs:: read ( img_file) . unwrap_or_else ( |e| {
203
+ tracing:: error!( "{}" , snafu:: Report :: from_error( e) ) ;
204
+ std:: process:: exit ( -2 ) ;
205
+ } ) ;
206
+
207
+ if let Ok ( img) = image_reader. decode ( ) {
208
+ // insert attributes but not pixel data
209
+
210
+ update_from_img ( & mut * dcm, & img, verbose) ;
211
+ }
212
+
213
+ // insert pixel data in a sequence
214
+ dcm. put ( DataElement :: new (
215
+ tags:: PIXEL_DATA ,
216
+ VR :: OB ,
217
+ DicomValue :: PixelSequence ( PixelFragmentSequence :: new_fragments ( vec ! [ all_data] ) ) ,
218
+ ) ) ;
219
+
220
+ Ok ( ( ) )
221
+ }
222
+
223
+ fn update_from_img ( obj : & mut DefaultDicomObject , img : & DynamicImage , verbose : bool ) {
73
224
let width = img. width ( ) ;
74
225
let height = img. height ( ) ;
75
226
let color = img. color ( ) ;
@@ -85,8 +236,6 @@ fn main() {
85
236
}
86
237
} ;
87
238
88
- let pixeldata = img. into_bytes ( ) ;
89
-
90
239
if verbose {
91
240
println ! ( "{}x{} {:?} image" , width, height, color) ;
92
241
}
@@ -151,68 +300,6 @@ fn main() {
151
300
VR :: US ,
152
301
PrimitiveValue :: from ( 0_u16 ) ,
153
302
) ) ;
154
-
155
- for tag in [
156
- tags:: NUMBER_OF_FRAMES ,
157
- tags:: PIXEL_ASPECT_RATIO ,
158
- tags:: SMALLEST_IMAGE_PIXEL_VALUE ,
159
- tags:: LARGEST_IMAGE_PIXEL_VALUE ,
160
- tags:: PIXEL_PADDING_RANGE_LIMIT ,
161
- tags:: RED_PALETTE_COLOR_LOOKUP_TABLE_DATA ,
162
- tags:: RED_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR ,
163
- tags:: GREEN_PALETTE_COLOR_LOOKUP_TABLE_DATA ,
164
- tags:: GREEN_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR ,
165
- tags:: BLUE_PALETTE_COLOR_LOOKUP_TABLE_DATA ,
166
- tags:: BLUE_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR ,
167
- tags:: ICC_PROFILE ,
168
- tags:: COLOR_SPACE ,
169
- tags:: PIXEL_DATA_PROVIDER_URL ,
170
- tags:: EXTENDED_OFFSET_TABLE ,
171
- tags:: EXTENDED_OFFSET_TABLE_LENGTHS ,
172
- ] {
173
- obj. remove_element ( tag) ;
174
- }
175
-
176
- obj. put ( DataElement :: new (
177
- tags:: PIXEL_DATA ,
178
- if bits_stored == 8 { VR :: OB } else { VR :: OW } ,
179
- PrimitiveValue :: from ( pixeldata) ,
180
- ) ) ;
181
-
182
- let class_uid = obj. meta ( ) . media_storage_sop_class_uid . clone ( ) ;
183
-
184
- let mut meta_builder = FileMetaTableBuilder :: new ( )
185
- // currently the tool will always decode the image's pixel data,
186
- // so encode it as Explicit VR Little Endian
187
- . transfer_syntax ( "1.2.840.10008.1.2.1" )
188
- . media_storage_sop_class_uid ( class_uid) ;
189
-
190
- // recover implementation class UID and version name from base object
191
- if retain_implementation {
192
- let implementation_class_uid = & obj. meta ( ) . implementation_class_uid ;
193
- meta_builder = meta_builder. implementation_class_uid ( implementation_class_uid) ;
194
-
195
- if let Some ( implementation_version_name) = obj. meta ( ) . implementation_version_name . as_ref ( ) {
196
- meta_builder = meta_builder. implementation_version_name ( implementation_version_name) ;
197
- }
198
- }
199
-
200
- let obj = obj
201
- . into_inner ( )
202
- . with_meta ( meta_builder)
203
- . unwrap_or_else ( |e| {
204
- tracing:: error!( "{}" , snafu:: Report :: from_error( e) ) ;
205
- std:: process:: exit ( -3 ) ;
206
- } ) ;
207
-
208
- obj. write_to_file ( & output) . unwrap_or_else ( |e| {
209
- tracing:: error!( "{}" , snafu:: Report :: from_error( e) ) ;
210
- std:: process:: exit ( -4 ) ;
211
- } ) ;
212
-
213
- if verbose {
214
- println ! ( "DICOM file saved to {}" , output. display( ) ) ;
215
- }
216
303
}
217
304
218
305
#[ cfg( test) ]
0 commit comments