Mercurial > hg > orthanc
comparison Core/DicomParsing/Internals/DicomImageDecoder.cpp @ 2423:5a7c5c541a1d
Built-in decoding of palette images
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 09 Oct 2017 21:58:08 +0200 |
parents | 7e217a1cc63f |
children | a6fab385b89e |
comparison
equal
deleted
inserted
replaced
2422:b340f0a9022c | 2423:5a7c5c541a1d |
---|---|
94 # include "../../Images/JpegWriter.h" | 94 # include "../../Images/JpegWriter.h" |
95 #endif | 95 #endif |
96 | 96 |
97 #include <boost/lexical_cast.hpp> | 97 #include <boost/lexical_cast.hpp> |
98 | 98 |
99 #include <dcmtk/dcmdata/dcdeftag.h> | |
99 #include <dcmtk/dcmdata/dcfilefo.h> | 100 #include <dcmtk/dcmdata/dcfilefo.h> |
100 #include <dcmtk/dcmdata/dcrleccd.h> | 101 #include <dcmtk/dcmdata/dcrleccd.h> |
101 #include <dcmtk/dcmdata/dcrlecp.h> | 102 #include <dcmtk/dcmdata/dcrlecp.h> |
103 #include <dcmtk/dcmdata/dcrlerp.h> | |
102 | 104 |
103 #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 | 105 #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 |
106 # include <dcmtk/dcmjpeg/djrplol.h> | |
104 # include <dcmtk/dcmjpls/djcodecd.h> | 107 # include <dcmtk/dcmjpls/djcodecd.h> |
105 # include <dcmtk/dcmjpls/djcparam.h> | 108 # include <dcmtk/dcmjpls/djcparam.h> |
106 # include <dcmtk/dcmjpeg/djrplol.h> | 109 # include <dcmtk/dcmjpls/djrparam.h> |
107 #endif | 110 #endif |
108 | 111 |
109 #if ORTHANC_ENABLE_DCMTK_JPEG == 1 | 112 #if ORTHANC_ENABLE_DCMTK_JPEG == 1 |
110 # include <dcmtk/dcmjpeg/djcodecd.h> | 113 # include <dcmtk/dcmjpeg/djcodecd.h> |
111 # include <dcmtk/dcmjpeg/djcparam.h> | 114 # include <dcmtk/dcmjpeg/djcparam.h> |
113 # include <dcmtk/dcmjpeg/djdecext.h> | 116 # include <dcmtk/dcmjpeg/djdecext.h> |
114 # include <dcmtk/dcmjpeg/djdeclol.h> | 117 # include <dcmtk/dcmjpeg/djdeclol.h> |
115 # include <dcmtk/dcmjpeg/djdecpro.h> | 118 # include <dcmtk/dcmjpeg/djdecpro.h> |
116 # include <dcmtk/dcmjpeg/djdecsps.h> | 119 # include <dcmtk/dcmjpeg/djdecsps.h> |
117 # include <dcmtk/dcmjpeg/djdecsv1.h> | 120 # include <dcmtk/dcmjpeg/djdecsv1.h> |
121 # include <dcmtk/dcmjpeg/djrploss.h> | |
118 #endif | 122 #endif |
119 | 123 |
120 #if DCMTK_VERSION_NUMBER <= 360 | 124 #if DCMTK_VERSION_NUMBER <= 360 |
121 # define EXS_JPEGProcess1 EXS_JPEGProcess1TransferSyntax | 125 # define EXS_JPEGProcess1 EXS_JPEGProcess1TransferSyntax |
122 # define EXS_JPEGProcess2_4 EXS_JPEGProcess2_4TransferSyntax | 126 # define EXS_JPEGProcess2_4 EXS_JPEGProcess2_4TransferSyntax |
379 } | 383 } |
380 } | 384 } |
381 } | 385 } |
382 | 386 |
383 | 387 |
388 static ImageAccessor* DecodeLookupTable(std::auto_ptr<ImageAccessor>& target, | |
389 const DicomImageInformation& info, | |
390 DcmDataset& dataset, | |
391 const uint8_t* pixelData, | |
392 unsigned long pixelLength) | |
393 { | |
394 LOG(INFO) << "Decoding a lookup table"; | |
395 | |
396 OFString r, g, b; | |
397 PixelFormat format; | |
398 const uint16_t* lutRed = NULL; | |
399 const uint16_t* lutGreen = NULL; | |
400 const uint16_t* lutBlue = NULL; | |
401 unsigned long rc = 0; | |
402 unsigned long gc = 0; | |
403 unsigned long bc = 0; | |
404 | |
405 if (pixelData == NULL && | |
406 !dataset.findAndGetUint8Array(DCM_PixelData, pixelData, &pixelLength).good()) | |
407 { | |
408 throw OrthancException(ErrorCode_NotImplemented); | |
409 } | |
410 | |
411 if (info.IsPlanar() || | |
412 info.GetNumberOfFrames() != 1 || | |
413 !info.ExtractPixelFormat(format, false) || | |
414 !dataset.findAndGetOFStringArray(DCM_BluePaletteColorLookupTableDescriptor, b).good() || | |
415 !dataset.findAndGetOFStringArray(DCM_GreenPaletteColorLookupTableDescriptor, g).good() || | |
416 !dataset.findAndGetOFStringArray(DCM_RedPaletteColorLookupTableDescriptor, r).good() || | |
417 !dataset.findAndGetUint16Array(DCM_BluePaletteColorLookupTableData, lutBlue, &bc).good() || | |
418 !dataset.findAndGetUint16Array(DCM_GreenPaletteColorLookupTableData, lutGreen, &gc).good() || | |
419 !dataset.findAndGetUint16Array(DCM_RedPaletteColorLookupTableData, lutRed, &rc).good() || | |
420 r != g || | |
421 r != b || | |
422 g != b || | |
423 lutRed == NULL || | |
424 lutGreen == NULL || | |
425 lutBlue == NULL || | |
426 pixelData == NULL) | |
427 { | |
428 throw OrthancException(ErrorCode_NotImplemented); | |
429 } | |
430 | |
431 switch (format) | |
432 { | |
433 case PixelFormat_RGB24: | |
434 { | |
435 if (r != "256\\0\\16" || | |
436 rc != 256 || | |
437 gc != 256 || | |
438 bc != 256 || | |
439 pixelLength != target->GetWidth() * target->GetHeight()) | |
440 { | |
441 throw OrthancException(ErrorCode_NotImplemented); | |
442 } | |
443 | |
444 const uint8_t* source = reinterpret_cast<const uint8_t*>(pixelData); | |
445 | |
446 for (unsigned int y = 0; y < target->GetHeight(); y++) | |
447 { | |
448 uint8_t* p = reinterpret_cast<uint8_t*>(target->GetRow(y)); | |
449 | |
450 for (unsigned int x = 0; x < target->GetWidth(); x++) | |
451 { | |
452 p[0] = lutRed[*source] >> 8; | |
453 p[1] = lutGreen[*source] >> 8; | |
454 p[2] = lutBlue[*source] >> 8; | |
455 source++; | |
456 p += 3; | |
457 } | |
458 } | |
459 | |
460 return target.release(); | |
461 } | |
462 | |
463 case PixelFormat_RGB48: | |
464 { | |
465 if (r != "0\\0\\16" || | |
466 rc != 65536 || | |
467 gc != 65536 || | |
468 bc != 65536 || | |
469 pixelLength != 2 * target->GetWidth() * target->GetHeight()) | |
470 { | |
471 throw OrthancException(ErrorCode_NotImplemented); | |
472 } | |
473 | |
474 const uint16_t* source = reinterpret_cast<const uint16_t*>(pixelData); | |
475 | |
476 for (unsigned int y = 0; y < target->GetHeight(); y++) | |
477 { | |
478 uint16_t* p = reinterpret_cast<uint16_t*>(target->GetRow(y)); | |
479 | |
480 for (unsigned int x = 0; x < target->GetWidth(); x++) | |
481 { | |
482 p[0] = lutRed[*source]; | |
483 p[1] = lutGreen[*source]; | |
484 p[2] = lutBlue[*source]; | |
485 source++; | |
486 p += 3; | |
487 } | |
488 } | |
489 | |
490 return target.release(); | |
491 } | |
492 | |
493 default: | |
494 break; | |
495 } | |
496 | |
497 throw OrthancException(ErrorCode_InternalError); | |
498 } | |
499 | |
500 | |
384 ImageAccessor* DicomImageDecoder::DecodeUncompressedImage(DcmDataset& dataset, | 501 ImageAccessor* DicomImageDecoder::DecodeUncompressedImage(DcmDataset& dataset, |
385 unsigned int frame) | 502 unsigned int frame) |
386 { | 503 { |
504 /** | |
505 * Create the target image. | |
506 **/ | |
507 | |
508 std::auto_ptr<ImageAccessor> target(CreateImage(dataset, false)); | |
509 | |
387 ImageSource source; | 510 ImageSource source; |
388 source.Setup(dataset, frame); | 511 source.Setup(dataset, frame); |
389 | 512 |
390 | |
391 /** | |
392 * Resize the target image. | |
393 **/ | |
394 | |
395 std::auto_ptr<ImageAccessor> target(CreateImage(dataset, false)); | |
396 | |
397 if (source.GetWidth() != target->GetWidth() || | 513 if (source.GetWidth() != target->GetWidth() || |
398 source.GetHeight() != target->GetHeight()) | 514 source.GetHeight() != target->GetHeight()) |
399 { | 515 { |
400 throw OrthancException(ErrorCode_InternalError); | 516 throw OrthancException(ErrorCode_InternalError); |
401 } | 517 } |
518 | |
519 | |
520 /** | |
521 * Deal with lookup tables | |
522 **/ | |
523 | |
524 const DicomImageInformation& info = source.GetAccessor().GetInformation(); | |
525 | |
526 if (info.GetPhotometricInterpretation() == PhotometricInterpretation_Palette) | |
527 { | |
528 return DecodeLookupTable(target, info, dataset, NULL, 0); | |
529 } | |
402 | 530 |
403 | 531 |
404 /** | 532 /** |
405 * If the format of the DICOM buffer is natively supported, use a | 533 * If the format of the DICOM buffer is natively supported, use a |
406 * direct access to copy its values. | 534 * direct access to copy its values. |
407 **/ | 535 **/ |
408 | |
409 const DicomImageInformation& info = source.GetAccessor().GetInformation(); | |
410 | 536 |
411 bool fastVersionSuccess = false; | 537 bool fastVersionSuccess = false; |
412 PixelFormat sourceFormat; | 538 PixelFormat sourceFormat; |
413 if (!info.IsPlanar() && | 539 if (!info.IsPlanar() && |
414 info.ExtractPixelFormat(sourceFormat, false)) | 540 info.ExtractPixelFormat(sourceFormat, false)) |
468 | 594 |
469 return target.release(); | 595 return target.release(); |
470 } | 596 } |
471 | 597 |
472 | 598 |
473 ImageAccessor* DicomImageDecoder::ApplyCodec(const DcmCodec& codec, | 599 ImageAccessor* DicomImageDecoder::ApplyCodec |
474 const DcmCodecParameter& parameters, | 600 (const DcmCodec& codec, |
475 DcmDataset& dataset, | 601 const DcmCodecParameter& parameters, |
476 unsigned int frame) | 602 const DcmRepresentationParameter& representationParameter, |
603 DcmDataset& dataset, | |
604 unsigned int frame) | |
477 { | 605 { |
478 DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset); | 606 DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset); |
479 if (pixelSequence == NULL) | 607 if (pixelSequence == NULL) |
480 { | 608 { |
481 throw OrthancException(ErrorCode_BadFileFormat); | 609 throw OrthancException(ErrorCode_BadFileFormat); |
482 } | 610 } |
483 | 611 |
612 DicomMap m; | |
613 FromDcmtkBridge::ExtractDicomSummary(m, dataset); | |
614 DicomImageInformation info(m); | |
615 | |
484 std::auto_ptr<ImageAccessor> target(CreateImage(dataset, true)); | 616 std::auto_ptr<ImageAccessor> target(CreateImage(dataset, true)); |
485 | 617 |
486 Uint32 startFragment = 0; // Default | 618 Uint32 startFragment = 0; // Default |
487 OFString decompressedColorModel; // Out | 619 OFString decompressedColorModel; // Out |
488 DJ_RPLossless representationParameter; | 620 |
489 OFCondition c = codec.decodeFrame(&representationParameter, | 621 OFCondition c; |
490 pixelSequence, ¶meters, | 622 |
491 &dataset, frame, startFragment, target->GetBuffer(), | 623 if (info.GetPhotometricInterpretation() == PhotometricInterpretation_Palette && |
492 target->GetSize(), decompressedColorModel); | 624 info.GetChannelCount() == 1) |
493 | 625 { |
494 if (c.good()) | 626 std::string uncompressed; |
495 { | 627 uncompressed.resize(info.GetWidth() * info.GetHeight() * info.GetBytesPerValue()); |
496 return target.release(); | 628 |
629 if (uncompressed.size() == 0 || | |
630 !codec.decodeFrame(&representationParameter, | |
631 pixelSequence, ¶meters, | |
632 &dataset, frame, startFragment, &uncompressed[0], | |
633 uncompressed.size(), decompressedColorModel).good()) | |
634 { | |
635 LOG(ERROR) << "Cannot decode a palette image"; | |
636 throw OrthancException(ErrorCode_BadFileFormat); | |
637 } | |
638 | |
639 return DecodeLookupTable(target, info, dataset, | |
640 reinterpret_cast<const uint8_t*>(uncompressed.c_str()), | |
641 uncompressed.size()); | |
497 } | 642 } |
498 else | 643 else |
499 { | 644 { |
500 LOG(ERROR) << "Cannot decode an image"; | 645 if (!codec.decodeFrame(&representationParameter, |
501 throw OrthancException(ErrorCode_BadFileFormat); | 646 pixelSequence, ¶meters, |
647 &dataset, frame, startFragment, target->GetBuffer(), | |
648 target->GetSize(), decompressedColorModel).good()) | |
649 { | |
650 LOG(ERROR) << "Cannot decode a non-palette image"; | |
651 throw OrthancException(ErrorCode_BadFileFormat); | |
652 } | |
653 | |
654 return target.release(); | |
502 } | 655 } |
503 } | 656 } |
504 | 657 |
505 | 658 |
506 ImageAccessor* DicomImageDecoder::Decode(ParsedDicomFile& dicom, | 659 ImageAccessor* DicomImageDecoder::Decode(ParsedDicomFile& dicom, |
530 | 683 |
531 if (syntax == EXS_JPEGLSLossless || | 684 if (syntax == EXS_JPEGLSLossless || |
532 syntax == EXS_JPEGLSLossy) | 685 syntax == EXS_JPEGLSLossy) |
533 { | 686 { |
534 DJLSCodecParameter parameters; | 687 DJLSCodecParameter parameters; |
688 DJLSRepresentationParameter representationParameter; | |
535 std::auto_ptr<DJLSDecoderBase> decoder; | 689 std::auto_ptr<DJLSDecoderBase> decoder; |
536 | 690 |
537 switch (syntax) | 691 switch (syntax) |
538 { | 692 { |
539 case EXS_JPEGLSLossless: | 693 case EXS_JPEGLSLossless: |
548 | 702 |
549 default: | 703 default: |
550 throw OrthancException(ErrorCode_InternalError); | 704 throw OrthancException(ErrorCode_InternalError); |
551 } | 705 } |
552 | 706 |
553 return ApplyCodec(*decoder, parameters, dataset, frame); | 707 return ApplyCodec(*decoder, parameters, representationParameter, dataset, frame); |
554 } | 708 } |
555 #endif | 709 #endif |
556 | 710 |
557 | 711 |
558 #if ORTHANC_ENABLE_DCMTK_JPEG == 1 | 712 #if ORTHANC_ENABLE_DCMTK_JPEG == 1 |
571 DJCodecParameter parameters( | 725 DJCodecParameter parameters( |
572 ECC_lossyYCbCr, // Mode for color conversion for compression, Unused for decompression | 726 ECC_lossyYCbCr, // Mode for color conversion for compression, Unused for decompression |
573 EDC_photometricInterpretation, // Perform color space conversion from YCbCr to RGB if DICOM photometric interpretation indicates YCbCr | 727 EDC_photometricInterpretation, // Perform color space conversion from YCbCr to RGB if DICOM photometric interpretation indicates YCbCr |
574 EUC_default, // Mode for UID creation, unused for decompression | 728 EUC_default, // Mode for UID creation, unused for decompression |
575 EPC_default); // Automatically determine whether color-by-plane is required from the SOP Class UID and decompressed photometric interpretation | 729 EPC_default); // Automatically determine whether color-by-plane is required from the SOP Class UID and decompressed photometric interpretation |
730 DJ_RPLossy representationParameter; | |
576 std::auto_ptr<DJCodecDecoder> decoder; | 731 std::auto_ptr<DJCodecDecoder> decoder; |
577 | 732 |
578 switch (syntax) | 733 switch (syntax) |
579 { | 734 { |
580 case EXS_JPEGProcess1: | 735 case EXS_JPEGProcess1: |
609 | 764 |
610 default: | 765 default: |
611 throw OrthancException(ErrorCode_InternalError); | 766 throw OrthancException(ErrorCode_InternalError); |
612 } | 767 } |
613 | 768 |
614 return ApplyCodec(*decoder, parameters, dataset, frame); | 769 return ApplyCodec(*decoder, parameters, representationParameter, dataset, frame); |
615 } | 770 } |
616 #endif | 771 #endif |
617 | 772 |
618 | 773 |
619 if (syntax == EXS_RLELossless) | 774 if (syntax == EXS_RLELossless) |
620 { | 775 { |
621 LOG(INFO) << "Decoding a RLE lossless DICOM image"; | 776 LOG(INFO) << "Decoding a RLE lossless DICOM image"; |
622 DcmRLECodecParameter parameters; | 777 DcmRLECodecParameter parameters; |
623 DcmRLECodecDecoder decoder; | 778 DcmRLECodecDecoder decoder; |
624 return ApplyCodec(decoder, parameters, dataset, frame); | 779 DcmRLERepresentationParameter representationParameter; |
780 return ApplyCodec(decoder, parameters, representationParameter, dataset, frame); | |
625 } | 781 } |
626 | 782 |
627 | 783 |
628 /** | 784 /** |
629 * This DICOM image format is not natively supported by | 785 * This DICOM image format is not natively supported by |
676 } | 832 } |
677 | 833 |
678 if (image->GetFormat() != format) | 834 if (image->GetFormat() != format) |
679 { | 835 { |
680 // A conversion is required | 836 // A conversion is required |
681 std::auto_ptr<ImageAccessor> target(new Image(format, image->GetWidth(), image->GetHeight(), false)); | 837 std::auto_ptr<ImageAccessor> target |
838 (new Image(format, image->GetWidth(), image->GetHeight(), false)); | |
682 ImageProcessing::Convert(*target, *image); | 839 ImageProcessing::Convert(*target, *image); |
683 image = target; | 840 image = target; |
684 } | 841 } |
685 | 842 |
686 return true; | 843 return true; |
692 switch (image->GetFormat()) | 849 switch (image->GetFormat()) |
693 { | 850 { |
694 case PixelFormat_RGB24: | 851 case PixelFormat_RGB24: |
695 { | 852 { |
696 // Directly return color images without modification (RGB) | 853 // Directly return color images without modification (RGB) |
854 return true; | |
855 } | |
856 | |
857 case PixelFormat_RGB48: | |
858 { | |
859 std::auto_ptr<ImageAccessor> target | |
860 (new Image(PixelFormat_RGB24, image->GetWidth(), image->GetHeight(), false)); | |
861 ImageProcessing::Convert(*target, *image); | |
862 image = target; | |
697 return true; | 863 return true; |
698 } | 864 } |
699 | 865 |
700 case PixelFormat_Grayscale8: | 866 case PixelFormat_Grayscale8: |
701 case PixelFormat_Grayscale16: | 867 case PixelFormat_Grayscale16: |
709 { | 875 { |
710 ImageProcessing::Set(*image, 0); | 876 ImageProcessing::Set(*image, 0); |
711 } | 877 } |
712 else | 878 else |
713 { | 879 { |
714 ImageProcessing::ShiftScale(*image, static_cast<float>(-a), 255.0f / static_cast<float>(b - a)); | 880 ImageProcessing::ShiftScale(*image, static_cast<float>(-a), |
881 255.0f / static_cast<float>(b - a)); | |
715 } | 882 } |
716 | 883 |
717 // If the source image is not grayscale 8bpp, convert it | 884 // If the source image is not grayscale 8bpp, convert it |
718 if (image->GetFormat() != PixelFormat_Grayscale8) | 885 if (image->GetFormat() != PixelFormat_Grayscale8) |
719 { | 886 { |
720 std::auto_ptr<ImageAccessor> target(new Image(PixelFormat_Grayscale8, image->GetWidth(), image->GetHeight(), false)); | 887 std::auto_ptr<ImageAccessor> target |
888 (new Image(PixelFormat_Grayscale8, image->GetWidth(), image->GetHeight(), false)); | |
721 ImageProcessing::Convert(*target, *image); | 889 ImageProcessing::Convert(*target, *image); |
722 image = target; | 890 image = target; |
723 } | 891 } |
724 | 892 |
725 return true; | 893 return true; |