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       const ByteValue *bv = pixelde.GetByteValue();
261       assert( bv );
262       gdcm::DataElement tmp;
263       tmp.SetByteValue( bv->GetPointer(), bv->GetLength());
264       bv = tmp.GetByteValue();
265       r = codec->CleanupUnusedBits((char*)bv->GetPointer(), 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     || Force )
407     {
408     // In memory decompression:
409     DataElement pixeldata( Tag(0x7fe0,0x0010) );
410     ByteValue *bv0 = new ByteValue();
411     uint32_t len0 = (uint32_t)Input->GetBufferLength();
412     bv0->SetLength( len0 );
413     bool b = Input->GetBuffer( (char*)bv0->GetPointer() );
414     if( !b )
415       {
416       gdcmErrorMacro( "Error in getting buffer from input image." );
417       return false;
418       }
419     pixeldata.SetValue( *bv0 );
420 
421     bool success = false;
422     if( !success ) success = TryRAWCodec(pixeldata, *Input, *Output);
423     if( !success ) success = TryJPEGCodec(pixeldata, *Input, *Output);
424     if( !success ) success = TryJPEGLSCodec(pixeldata, *Input, *Output);
425     if( !success ) success = TryJPEG2000Codec(pixeldata, *Input, *Output);
426     if( !success ) success = TryRLECodec(pixeldata, *Input, *Output);
427     Output->SetTransferSyntax( TS );
428     if( !success )
429       {
430       //assert(0);
431       return false;
432       }
433 
434     // same goes for icon
435     DataElement iconpixeldata( Tag(0x7fe0,0x0010) );
436     Bitmap &bitmap = *Input;
437     if( Pixmap *pixmap = dynamic_cast<Pixmap*>( &bitmap ) )
438       {
439       Bitmap &outbitmap = *Output;
440       Pixmap *outpixmap = dynamic_cast<Pixmap*>( &outbitmap );
441       assert( outpixmap != NULL );
442       if( !pixmap->GetIconImage().IsEmpty() )
443         {
444         // same goes for icon
445         ByteValue *bv = new ByteValue();
446         uint32_t len = (uint32_t)pixmap->GetIconImage().GetBufferLength();
447         bv->SetLength( len );
448         bool bb = pixmap->GetIconImage().GetBuffer( (char*)bv->GetPointer() );
449         if( !bb )
450           {
451           return false;
452           }
453         iconpixeldata.SetValue( *bv );
454 
455         success = false;
456         if( !success ) success = TryRAWCodec(iconpixeldata, pixmap->GetIconImage(), outpixmap->GetIconImage());
457         if( !success ) success = TryJPEGCodec(iconpixeldata, pixmap->GetIconImage(), outpixmap->GetIconImage());
458         if( !success ) success = TryJPEGLSCodec(iconpixeldata, pixmap->GetIconImage(), outpixmap->GetIconImage());
459         if( !success ) success = TryJPEG2000Codec(iconpixeldata, pixmap->GetIconImage(), outpixmap->GetIconImage());
460         if( !success ) success = TryRLECodec(iconpixeldata, pixmap->GetIconImage(), outpixmap->GetIconImage());
461         outpixmap->GetIconImage().SetTransferSyntax( TS );
462         if( !success )
463           {
464           //assert(0);
465           return false;
466           }
467         assert( outpixmap->GetIconImage().GetTransferSyntax() == TS );
468         }
469       }
470 
471     //Output->ComputeLossyFlag();
472     assert( Output->GetTransferSyntax() == TS );
473     //if( TS.IsLossy() ) assert( Output->IsLossy() );
474     return success;
475     }
476 
477   // too bad we actually have to do some work...
478   bool success = false;
479   if( !success ) success = TryRAWCodec(Input->GetDataElement(), *Input, *Output);
480   if( !success ) success = TryJPEGCodec(Input->GetDataElement(), *Input, *Output);
481   if( !success ) success = TryJPEG2000Codec(Input->GetDataElement(), *Input, *Output);
482   if( !success ) success = TryJPEGLSCodec(Input->GetDataElement(), *Input, *Output);
483   if( !success ) success = TryRLECodec(Input->GetDataElement(), *Input, *Output);
484   Output->SetTransferSyntax( TS );
485   if( !success )
486     {
487     //assert(0);
488     return false;
489     }
490 
491   Bitmap &bitmap = *Input;
492   if( Pixmap *pixmap = dynamic_cast<Pixmap*>( &bitmap ) )
493     {
494     if( !pixmap->GetIconImage().IsEmpty() && CompressIconImage )
495       {
496       Bitmap &outbitmap = *Output;
497       Pixmap *outpixmap = dynamic_cast<Pixmap*>( &outbitmap );
498 
499       // same goes for icon
500       success = false;
501       if( !success ) success = TryRAWCodec(pixmap->GetIconImage().GetDataElement(), pixmap->GetIconImage(), outpixmap->GetIconImage());
502       if( !success ) success = TryJPEGCodec(pixmap->GetIconImage().GetDataElement(), pixmap->GetIconImage(), outpixmap->GetIconImage());
503       if( !success ) success = TryJPEGLSCodec(pixmap->GetIconImage().GetDataElement(), pixmap->GetIconImage(), outpixmap->GetIconImage());
504       if( !success ) success = TryJPEG2000Codec(pixmap->GetIconImage().GetDataElement(), pixmap->GetIconImage(), outpixmap->GetIconImage());
505       if( !success ) success = TryRLECodec(pixmap->GetIconImage().GetDataElement(), pixmap->GetIconImage(), outpixmap->GetIconImage());
506       outpixmap->GetIconImage().SetTransferSyntax( TS );
507       if( !success )
508         {
509         //assert(0);
510         return false;
511         }
512       assert( outpixmap->GetIconImage().GetTransferSyntax() == TS );
513       }
514     }
515 
516   //Output->ComputeLossyFlag();
517 
518   assert( Output->GetTransferSyntax() == TS );
519   return success;
520 }
521 
522 
523 } // end namespace gdcm
524