1 /*=========================================================================
2
3 Program: GDCM (Grassroots DICOM). A DICOM library
4
5 Copyright (c) 2006-2011 Mathieu Malaterre
6 All rights reserved.
7 See Copyright.txt or http://gdcm.sourceforge.net/Copyright.html for details.
8
9 This software is distributed WITHOUT ANY WARRANTY; without even
10 the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
11 PURPOSE. See the above copyright notice for more information.
12
13 =========================================================================*/
14 #include "gdcmImageChangeTransferSyntax.h"
15 #include "gdcmSequenceOfFragments.h"
16 #include "gdcmSequenceOfItems.h"
17 #include "gdcmFragment.h"
18 #include "gdcmPixmap.h"
19 #include "gdcmBitmap.h"
20 #include "gdcmRAWCodec.h"
21 #include "gdcmJPEGCodec.h"
22 #include "gdcmJPEGLSCodec.h"
23 #include "gdcmJPEG2000Codec.h"
24 #include "gdcmRLECodec.h"
25
26 namespace gdcm
27 {
28
29 /*
30 bool ImageChangeTransferSyntax::TryRAWCodecIcon(const DataElement &pixelde)
31 {
32 unsigned long len = Input->GetIconImage().GetBufferLength();
33 //assert( len == pixelde.GetByteValue()->GetLength() );
34 const TransferSyntax &ts = GetTransferSyntax();
35
36 RAWCodec codec;
37 if( codec.CanCode( ts ) )
38 {
39 codec.SetDimensions( Input->GetIconImage().GetDimensions() );
40 codec.SetPlanarConfiguration( Input->GetIconImage().GetPlanarConfiguration() );
41 codec.SetPhotometricInterpretation( Input->GetIconImage().GetPhotometricInterpretation() );
42 codec.SetPixelFormat( Input->GetIconImage().GetPixelFormat() );
43 codec.SetNeedOverlayCleanup( Input->GetIconImage().AreOverlaysInPixelData() );
44 DataElement out;
45 //bool r = codec.Code(Input->GetDataElement(), out);
46 bool r = codec.Code(pixelde, out);
47
48 DataElement &de = Output->GetIconImage().GetDataElement();
49 de.SetValue( out.GetValue() );
50 if( !r )
51 {
52 return false;
53 }
54 return true;
55 }
56 return false;
57 }
58 */
59
UpdatePhotometricInterpretation(Bitmap const & input,Bitmap & output)60 void UpdatePhotometricInterpretation( Bitmap const &input, Bitmap &output )
61 {
62 // when decompressing J2K, need to revert to proper photo inter in uncompressed TS:
63 if( input.GetPhotometricInterpretation() == PhotometricInterpretation::YBR_RCT
64 || input.GetPhotometricInterpretation() == PhotometricInterpretation::YBR_ICT )
65 {
66 output.SetPhotometricInterpretation( PhotometricInterpretation::RGB );
67 }
68 // when decompressing loss jpeg, need to revert to proper photo inter in uncompressed TS:
69 if( input.GetPhotometricInterpretation() == PhotometricInterpretation::YBR_FULL_422 )
70 {
71 output.SetPhotometricInterpretation( PhotometricInterpretation::YBR_FULL );
72 }
73 assert( output.GetPhotometricInterpretation() == PhotometricInterpretation::RGB
74 || output.GetPhotometricInterpretation() == PhotometricInterpretation::YBR_FULL
75 || output.GetPhotometricInterpretation() == PhotometricInterpretation::MONOCHROME1
76 || output.GetPhotometricInterpretation() == PhotometricInterpretation::MONOCHROME2
77 || output.GetPhotometricInterpretation() == PhotometricInterpretation::ARGB
78 || output.GetPhotometricInterpretation() == PhotometricInterpretation::PALETTE_COLOR ); // programmer error
79 }
80
TryRAWCodec(const DataElement & pixelde,Bitmap const & input,Bitmap & output)81 bool ImageChangeTransferSyntax::TryRAWCodec(const DataElement &pixelde, Bitmap const &input, Bitmap &output)
82 {
83 unsigned long len = input.GetBufferLength(); (void)len;
84 //assert( len == pixelde.GetByteValue()->GetLength() );
85 const TransferSyntax &ts = GetTransferSyntax();
86
87 RAWCodec codec;
88 if( codec.CanCode( ts ) )
89 {
90 codec.SetDimensions( input.GetDimensions() );
91 codec.SetPlanarConfiguration( input.GetPlanarConfiguration() );
92 codec.SetPhotometricInterpretation( input.GetPhotometricInterpretation() );
93 codec.SetPixelFormat( input.GetPixelFormat() );
94 codec.SetNeedOverlayCleanup( input.AreOverlaysInPixelData() || input.UnusedBitsPresentInPixelData() );
95 DataElement out;
96 //bool r = codec.Code(input.GetDataElement(), out);
97 bool r = codec.Code(pixelde, out);
98
99 if( !r )
100 {
101 return false;
102 }
103 DataElement &de = output.GetDataElement();
104 de.SetValue( out.GetValue() );
105 UpdatePhotometricInterpretation( input, output );
106 return true;
107 }
108 return false;
109 }
110
TryRLECodec(const DataElement & pixelde,Bitmap const & input,Bitmap & output)111 bool ImageChangeTransferSyntax::TryRLECodec(const DataElement &pixelde, Bitmap const &input, Bitmap &output)
112 {
113 unsigned long len = input.GetBufferLength(); (void)len;
114 //assert( len == pixelde.GetByteValue()->GetLength() );
115 const TransferSyntax &ts = GetTransferSyntax();
116
117 RLECodec codec;
118 if( codec.CanCode( ts ) )
119 {
120 codec.SetDimensions( input.GetDimensions() );
121 codec.SetPlanarConfiguration( input.GetPlanarConfiguration() );
122 codec.SetPhotometricInterpretation( input.GetPhotometricInterpretation() );
123 codec.SetPixelFormat( input.GetPixelFormat() );
124 codec.SetNeedOverlayCleanup( input.AreOverlaysInPixelData() || input.UnusedBitsPresentInPixelData() );
125 DataElement out;
126 //bool r = codec.Code(input.GetDataElement(), out);
127 bool r = codec.Code(pixelde, out);
128
129 if( !r )
130 {
131 return false;
132 }
133 DataElement &de = output.GetDataElement();
134 de.SetValue( out.GetValue() );
135 UpdatePhotometricInterpretation( input, output );
136 if( input.GetPixelFormat().GetSamplesPerPixel() == 3 )
137 {
138 if( input.GetPlanarConfiguration() == 0 )
139 {
140 // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_G.2.html
141 // The use of separate segments implies that the Planar Configuration (0028,0006) will always be 1 for RLE compressed images.
142 output.SetPlanarConfiguration(1);
143 }
144 }
145 return true;
146 }
147 return false;
148 }
149
TryJPEGCodec(const DataElement & pixelde,Bitmap const & input,Bitmap & output)150 bool ImageChangeTransferSyntax::TryJPEGCodec(const DataElement &pixelde, Bitmap const &input, Bitmap &output)
151 {
152 unsigned long len = input.GetBufferLength(); (void)len;
153 //assert( len == pixelde.GetByteValue()->GetLength() );
154 const TransferSyntax &ts = GetTransferSyntax();
155
156 JPEGCodec jpgcodec;
157 // pass lossy/lossless flag:
158 // JPEGCodec are easier to deal with since there is no dual transfer syntax
159 // that can be both lossy and lossless:
160 if( ts.IsLossy() )
161 {
162 //assert( !ts.IsLossless() ); // I cannot do since since Try* functions are called with all TS, I could be receiving a JPEGLS TS...
163 jpgcodec.SetLossless( false );
164 }
165
166 ImageCodec *codec = &jpgcodec;
167 JPEGCodec *usercodec = dynamic_cast<JPEGCodec*>(UserCodec);
168 if( usercodec && usercodec->CanCode( ts ) )
169 {
170 codec = usercodec;
171 }
172
173 if( codec->CanCode( ts ) )
174 {
175 codec->SetDimensions( input.GetDimensions() );
176 // FIXME: GDCM always apply the planar configuration to 0...
177 //if( input.GetPlanarConfiguration() )
178 // {
179 // output.SetPlanarConfiguration( 0 );
180 // }
181 codec->SetPlanarConfiguration( input.GetPlanarConfiguration() );
182 codec->SetPhotometricInterpretation( input.GetPhotometricInterpretation() );
183 codec->SetPixelFormat( input.GetPixelFormat() );
184 codec->SetNeedOverlayCleanup( input.AreOverlaysInPixelData() || input.UnusedBitsPresentInPixelData() );
185 // let's check we are not trying to compress 16bits with JPEG/Lossy/8bits
186 if( !input.GetPixelFormat().IsCompatible( ts ) )
187 {
188 gdcmErrorMacro("Pixel Format incompatible with TS" );
189 return false;
190 }
191 DataElement out;
192 //bool r = codec.Code(input.GetDataElement(), out);
193 bool r = codec->Code(pixelde, out);
194 // FIXME: this is not the best place to change the Output image internal type,
195 // but since I know IJG is always applying the Planar Configuration, it does make
196 // any sense to EVER produce a JPEG image where the Planar Configuration would be one
197 // so let's be nice and actually sync JPEG configuration with DICOM Planar Conf.
198 output.SetPlanarConfiguration( 0 );
199 //output.SetPhotometricInterpretation( PhotometricInterpretation::RGB );
200
201 // Indeed one cannot produce a true lossless RGB image according to DICOM standard
202 // when doing lossless jpeg:
203 if( output.GetPhotometricInterpretation() == PhotometricInterpretation::RGB )
204 {
205 gdcmWarningMacro( "Technically this is not defined in the standard. \n"
206 "Some validator may complains this image is invalid, but would be wrong.");
207 }
208
209 // PHILIPS_Gyroscan-12-MONO2-Jpeg_Lossless.dcm
210 if( !r )
211 {
212 return false;
213 }
214 DataElement &de = output.GetDataElement();
215 de.SetValue( out.GetValue() );
216 UpdatePhotometricInterpretation( input, output );
217 // When compressing with JPEG I think planar should always be:
218 //output.SetPlanarConfiguration(0);
219 // FIXME ! This should be done all the time for all codec:
220 // Did PI change or not ?
221 if ( !output.GetPhotometricInterpretation().IsSameColorSpace( codec->GetPhotometricInterpretation() ) )
222 {
223 // HACK
224 //Image *i = (Image*)this;
225 //i->SetPhotometricInterpretation( codec.GetPhotometricInterpretation() );
226 assert(0);
227 }
228 return true;
229 }
230 return false;
231 }
232
TryJPEGLSCodec(const DataElement & pixelde,Bitmap const & input,Bitmap & output)233 bool ImageChangeTransferSyntax::TryJPEGLSCodec(const DataElement &pixelde, Bitmap const &input, Bitmap &output)
234 {
235 unsigned long len = input.GetBufferLength(); (void)len;
236 //assert( len == pixelde.GetByteValue()->GetLength() );
237 const TransferSyntax &ts = GetTransferSyntax();
238
239 JPEGLSCodec jlscodec;
240 ImageCodec *codec = &jlscodec;
241 JPEGLSCodec *usercodec = dynamic_cast<JPEGLSCodec*>(UserCodec);
242 if( usercodec && usercodec->CanCode( ts ) )
243 {
244 codec = usercodec;
245 }
246
247 if( codec->CanCode( ts ) )
248 {
249 codec->SetDimensions( input.GetDimensions() );
250 codec->SetPixelFormat( input.GetPixelFormat() );
251 //codec.SetNumberOfDimensions( input.GetNumberOfDimensions() );
252 codec->SetPlanarConfiguration( input.GetPlanarConfiguration() );
253 codec->SetPhotometricInterpretation( input.GetPhotometricInterpretation() );
254 codec->SetNeedOverlayCleanup( input.AreOverlaysInPixelData() || input.UnusedBitsPresentInPixelData() );
255 DataElement out;
256 //bool r = codec.Code(input.GetDataElement(), out);
257 bool r;
258 if( input.AreOverlaysInPixelData() || input.UnusedBitsPresentInPixelData() )
259 {
260 ByteValue *bv = const_cast<ByteValue*>(pixelde.GetByteValue());
261 assert( bv );
262 gdcm::DataElement tmp;
263 tmp.SetByteValue( bv->GetPointer(), bv->GetLength());
264 bv = const_cast<ByteValue*>(tmp.GetByteValue());
265 r = codec->CleanupUnusedBits((char*)bv->GetVoidPointer(), bv->GetLength());
266 if(!r) return false;
267 r = codec->Code(tmp, out);
268 }
269 else
270 {
271 r = codec->Code(pixelde, out);
272 }
273 if(!r) return false;
274
275 DataElement &de = output.GetDataElement();
276 de.SetValue( out.GetValue() );
277 UpdatePhotometricInterpretation( input, output );
278 if( input.GetPixelFormat().GetSamplesPerPixel() == 3 )
279 {
280 if( input.GetPlanarConfiguration() == 0 )
281 {
282 // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_8.2.3.html#table_8.2.3-1
283 output.SetPlanarConfiguration(1);
284 }
285 }
286
287 return r;
288 }
289 return false;
290 }
291
TryJPEG2000Codec(const DataElement & pixelde,Bitmap const & input,Bitmap & output)292 bool ImageChangeTransferSyntax::TryJPEG2000Codec(const DataElement &pixelde, Bitmap const &input, Bitmap &output)
293 {
294 unsigned long len = input.GetBufferLength(); (void)len;
295 //assert( len == pixelde.GetByteValue()->GetLength() );
296 const TransferSyntax &ts = GetTransferSyntax();
297
298 JPEG2000Codec j2kcodec;
299 ImageCodec *codec = &j2kcodec;
300 JPEG2000Codec *usercodec = dynamic_cast<JPEG2000Codec*>(UserCodec);
301 if( usercodec && usercodec->CanCode( ts ) )
302 {
303 codec = usercodec;
304 }
305
306 if( codec->CanCode( ts ) )
307 {
308 codec->SetDimensions( input.GetDimensions() );
309 codec->SetPixelFormat( input.GetPixelFormat() );
310 codec->SetNumberOfDimensions( input.GetNumberOfDimensions() );
311 codec->SetPlanarConfiguration( input.GetPlanarConfiguration() );
312 codec->SetPhotometricInterpretation( input.GetPhotometricInterpretation() );
313 codec->SetNeedOverlayCleanup( input.AreOverlaysInPixelData() || input.UnusedBitsPresentInPixelData() );
314 DataElement out;
315 //bool r = codec.Code(input.GetDataElement(), out);
316 bool r = codec->Code(pixelde, out);
317
318 // The value of Planar Configuration (0028,0006) is irrelevant since the
319 // manner of encoding components is specified in the JPEG 2000 standard,
320 // hence it shall be set to 0.
321 output.SetPlanarConfiguration( 0 );
322
323 if( input.GetPixelFormat().GetSamplesPerPixel() == 3 )
324 {
325 if( input.GetPhotometricInterpretation().IsSameColorSpace( PhotometricInterpretation::RGB ) )
326 {
327 if( ts == TransferSyntax::JPEG2000Lossless )
328 {
329 output.SetPhotometricInterpretation( PhotometricInterpretation::YBR_RCT );
330 }
331 else
332 {
333 assert( ts == TransferSyntax::JPEG2000 );
334 output.SetPhotometricInterpretation( PhotometricInterpretation::YBR_ICT );
335 }
336 }
337 else
338 {
339 assert( input.GetPhotometricInterpretation().IsSameColorSpace( PhotometricInterpretation::YBR_FULL ) );
340 if( ts == TransferSyntax::JPEG2000Lossless )
341 {
342 output.SetPhotometricInterpretation( PhotometricInterpretation::YBR_FULL );
343 // Indeed one cannot produce a true lossless RGB image according to DICOM standard
344 gdcmWarningMacro( "Technically this is not defined in the standard. \n"
345 "Some validator may complains this image is invalid, but would be wrong.");
346 }
347 else
348 {
349 assert( ts == TransferSyntax::JPEG2000 );
350 //output.SetPhotometricInterpretation( PhotometricInterpretation::YBR_ICT );
351 // FIXME: technically when doing lossy we could be standard compliant and first convert to
352 // RGB THEN compress to YBR_ICT. For now produce improper j2k image
353 output.SetPhotometricInterpretation( PhotometricInterpretation::YBR_FULL );
354 }
355 }
356 }
357 else
358 {
359 assert( input.GetPixelFormat().GetSamplesPerPixel() == 1 );
360 }
361
362 if( !r ) return false;
363 DataElement &de = output.GetDataElement();
364 de.SetValue( out.GetValue() );
365 UpdatePhotometricInterpretation( input, output );
366 return r;
367 }
368 return false;
369 }
370
Change()371 bool ImageChangeTransferSyntax::Change()
372 {
373 if( TS == TransferSyntax::TS_END )
374 {
375 if( !Force ) return false;
376 // When force option is set but no specific TransferSyntax has been set, only inspect the
377 // encapsulated stream...
378 // See ImageReader::Read
379 if( Input->GetTransferSyntax().IsEncapsulated() && Input->GetTransferSyntax() != TransferSyntax::RLELossless )
380 {
381 Output = Input;
382 return true;
383 }
384 return false;
385 }
386 // let's get rid of some easy case:
387 if( Input->GetPhotometricInterpretation() == PhotometricInterpretation::PALETTE_COLOR &&
388 TS.IsLossy() )
389 {
390 gdcmErrorMacro( "PALETTE_COLOR and Lossy compression are impossible. Convert to RGB first." );
391 return false;
392 }
393
394 Output = Input;
395 // if( TS.IsLossy() && !TS.IsLossless() )
396 // Output->SetLossyFlag( true );
397
398 // Fast path
399 if( Input->GetTransferSyntax() == TS && !Force ) return true;
400
401 // FIXME
402 // For now only support raw input, otherwise we would need to first decompress them
403 if( (Input->GetTransferSyntax() != TransferSyntax::ImplicitVRLittleEndian
404 && Input->GetTransferSyntax() != TransferSyntax::ExplicitVRLittleEndian
405 && Input->GetTransferSyntax() != TransferSyntax::ExplicitVRBigEndian)
406 // YBR_FULL_422 / raw needs to be decompressed:
407 || ( (Input->GetTransferSyntax() == TransferSyntax::ImplicitVRLittleEndian
408 || Input->GetTransferSyntax() == TransferSyntax::ExplicitVRLittleEndian
409 || Input->GetTransferSyntax() == TransferSyntax::ExplicitVRBigEndian)
410 && Input->GetPhotometricInterpretation() == PhotometricInterpretation::YBR_FULL_422 )
411 || Force )
412 {
413 // In memory decompression:
414 DataElement pixeldata( Tag(0x7fe0,0x0010) );
415 ByteValue *bv0 = new ByteValue();
416 uint32_t len0 = (uint32_t)Input->GetBufferLength();
417 bv0->SetLength( len0 );
418 bool b = Input->GetBuffer( (char*)bv0->GetVoidPointer() );
419 if( !b )
420 {
421 gdcmErrorMacro( "Error in getting buffer from input image." );
422 return false;
423 }
424 pixeldata.SetValue( *bv0 );
425
426 bool success = false;
427 if( !success ) success = TryRAWCodec(pixeldata, *Input, *Output);
428 if( !success ) success = TryJPEGCodec(pixeldata, *Input, *Output);
429 if( !success ) success = TryJPEGLSCodec(pixeldata, *Input, *Output);
430 if( !success ) success = TryJPEG2000Codec(pixeldata, *Input, *Output);
431 if( !success ) success = TryRLECodec(pixeldata, *Input, *Output);
432 Output->SetTransferSyntax( TS );
433 if( !success )
434 {
435 //assert(0);
436 return false;
437 }
438
439 // same goes for icon
440 DataElement iconpixeldata( Tag(0x7fe0,0x0010) );
441 Bitmap &bitmap = *Input;
442 if( Pixmap *pixmap = dynamic_cast<Pixmap*>( &bitmap ) )
443 {
444 Bitmap &outbitmap = *Output;
445 Pixmap *outpixmap = dynamic_cast<Pixmap*>( &outbitmap );
446 assert( outpixmap != nullptr );
447 if( !pixmap->GetIconImage().IsEmpty() )
448 {
449 // same goes for icon
450 ByteValue *bv = new ByteValue();
451 uint32_t len = (uint32_t)pixmap->GetIconImage().GetBufferLength();
452 bv->SetLength( len );
453 bool bb = pixmap->GetIconImage().GetBuffer( (char*)bv->GetVoidPointer() );
454 if( !bb )
455 {
456 return false;
457 }
458 iconpixeldata.SetValue( *bv );
459
460 success = false;
461 if( !success ) success = TryRAWCodec(iconpixeldata, pixmap->GetIconImage(), outpixmap->GetIconImage());
462 if( !success ) success = TryJPEGCodec(iconpixeldata, pixmap->GetIconImage(), outpixmap->GetIconImage());
463 if( !success ) success = TryJPEGLSCodec(iconpixeldata, pixmap->GetIconImage(), outpixmap->GetIconImage());
464 if( !success ) success = TryJPEG2000Codec(iconpixeldata, pixmap->GetIconImage(), outpixmap->GetIconImage());
465 if( !success ) success = TryRLECodec(iconpixeldata, pixmap->GetIconImage(), outpixmap->GetIconImage());
466 outpixmap->GetIconImage().SetTransferSyntax( TS );
467 if( !success )
468 {
469 //assert(0);
470 return false;
471 }
472 assert( outpixmap->GetIconImage().GetTransferSyntax() == TS );
473 }
474 }
475
476 //Output->ComputeLossyFlag();
477 assert( Output->GetTransferSyntax() == TS );
478 //if( TS.IsLossy() ) assert( Output->IsLossy() );
479 return success;
480 }
481
482 // too bad we actually have to do some work...
483 bool success = false;
484 if( !success ) success = TryRAWCodec(Input->GetDataElement(), *Input, *Output);
485 if( !success ) success = TryJPEGCodec(Input->GetDataElement(), *Input, *Output);
486 if( !success ) success = TryJPEG2000Codec(Input->GetDataElement(), *Input, *Output);
487 if( !success ) success = TryJPEGLSCodec(Input->GetDataElement(), *Input, *Output);
488 if( !success ) success = TryRLECodec(Input->GetDataElement(), *Input, *Output);
489 Output->SetTransferSyntax( TS );
490 if( !success )
491 {
492 //assert(0);
493 return false;
494 }
495
496 Bitmap &bitmap = *Input;
497 if( Pixmap *pixmap = dynamic_cast<Pixmap*>( &bitmap ) )
498 {
499 if( !pixmap->GetIconImage().IsEmpty() && CompressIconImage )
500 {
501 Bitmap &outbitmap = *Output;
502 Pixmap *outpixmap = dynamic_cast<Pixmap*>( &outbitmap );
503
504 // same goes for icon
505 success = false;
506 if( !success ) success = TryRAWCodec(pixmap->GetIconImage().GetDataElement(), pixmap->GetIconImage(), outpixmap->GetIconImage());
507 if( !success ) success = TryJPEGCodec(pixmap->GetIconImage().GetDataElement(), pixmap->GetIconImage(), outpixmap->GetIconImage());
508 if( !success ) success = TryJPEGLSCodec(pixmap->GetIconImage().GetDataElement(), pixmap->GetIconImage(), outpixmap->GetIconImage());
509 if( !success ) success = TryJPEG2000Codec(pixmap->GetIconImage().GetDataElement(), pixmap->GetIconImage(), outpixmap->GetIconImage());
510 if( !success ) success = TryRLECodec(pixmap->GetIconImage().GetDataElement(), pixmap->GetIconImage(), outpixmap->GetIconImage());
511 outpixmap->GetIconImage().SetTransferSyntax( TS );
512 if( !success )
513 {
514 //assert(0);
515 return false;
516 }
517 assert( outpixmap->GetIconImage().GetTransferSyntax() == TS );
518 }
519 }
520
521 //Output->ComputeLossyFlag();
522
523 assert( Output->GetTransferSyntax() == TS );
524 return success;
525 }
526
527
528 } // end namespace gdcm
529