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