Mercurial > hg > orthanc
changeset 847:03ea55da7429 jpeg
fully functional JPEG-LS conversion
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 05 Jun 2014 18:14:16 +0200 |
parents | 715ab7674993 |
children | 703e8d5b03fd |
files | OrthancServer/Internals/DicomImageDecoder.cpp OrthancServer/Internals/DicomImageDecoder.h OrthancServer/OrthancInitialization.cpp UnitTestsSources/JpegLossless.cpp |
diffstat | 4 files changed, 274 insertions(+), 12 deletions(-) [+] |
line wrap: on
line diff
--- a/OrthancServer/Internals/DicomImageDecoder.cpp Thu Jun 05 16:23:03 2014 +0200 +++ b/OrthancServer/Internals/DicomImageDecoder.cpp Thu Jun 05 18:14:16 2014 +0200 @@ -78,14 +78,18 @@ #include "../../Core/OrthancException.h" +#include "../../Core/DicomFormat/DicomIntegerPixelAccessor.h" #include "../ToDcmtkBridge.h" +#include "../FromDcmtkBridge.h" +#include <glog/logging.h> + +#include <boost/lexical_cast.hpp> + +#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 #include <dcmtk/dcmjpls/djcodecd.h> #include <dcmtk/dcmjpls/djcparam.h> #include <dcmtk/dcmjpeg/djrplol.h> -#include <boost/lexical_cast.hpp> - -#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 #endif @@ -274,9 +278,183 @@ } + bool DicomImageDecoder::IsUncompressedImage(const DcmDataset& dataset) + { + return (dataset.getOriginalXfer() == EXS_Unknown || + dataset.getOriginalXfer() == EXS_LittleEndianImplicit || + dataset.getOriginalXfer() == EXS_BigEndianImplicit || + dataset.getOriginalXfer() == EXS_LittleEndianExplicit || + dataset.getOriginalXfer() == EXS_BigEndianExplicit); + } + + + template <typename PixelType> + static void CopyPixels(ImageAccessor& target, + const DicomIntegerPixelAccessor& source) + { + const PixelType minValue = std::numeric_limits<PixelType>::min(); + const PixelType maxValue = std::numeric_limits<PixelType>::max(); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + PixelType* pixel = reinterpret_cast<PixelType*>(target.GetRow(y)); + for (unsigned int x = 0; x < source.GetWidth(); x++) + { + for (unsigned int c = 0; c < source.GetChannelCount(); c++, pixel++) + { + int32_t v = source.GetValue(x, y, c); + if (v < static_cast<int32_t>(minValue)) + { + *pixel = minValue; + } + else if (v > static_cast<int32_t>(maxValue)) + { + *pixel = maxValue; + } + else + { + *pixel = static_cast<PixelType>(v); + } + } + } + } + } + + + void DicomImageDecoder::DecodeUncompressedImage(ImageBuffer& target, + DcmDataset& dataset, + unsigned int frame) + { + if (!IsUncompressedImage(dataset)) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + DecodeUncompressedImageInternal(target, dataset, frame); + } + + + void DicomImageDecoder::DecodeUncompressedImageInternal(ImageBuffer& target, + DcmDataset& dataset, + unsigned int frame) + { + // See also: http://support.dcmtk.org/wiki/dcmtk/howto/accessing-compressed-data + + std::auto_ptr<DicomIntegerPixelAccessor> source; + + DicomMap m; + FromDcmtkBridge::Convert(m, dataset); + + + /** + * Create an accessor to the raw values of the DICOM image. + **/ + + std::string privateContent; + + DcmElement* e; + if (dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), e).good() && + e != NULL) + { + Uint8* pixData = NULL; + if (e->getUint8Array(pixData) == EC_Normal) + { + source.reset(new DicomIntegerPixelAccessor(m, pixData, e->getLength())); + } + } + else if (DicomImageDecoder::DecodePsmctRle1(privateContent, dataset)) + { + LOG(INFO) << "The PMSCT_RLE1 decoding has succeeded"; + Uint8* pixData = NULL; + if (privateContent.size() > 0) + { + pixData = reinterpret_cast<Uint8*>(&privateContent[0]); + } + + source.reset(new DicomIntegerPixelAccessor(m, pixData, privateContent.size())); + } + + if (source.get() == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + source->SetCurrentFrame(frame); + + + /** + * Resize the target image, with some sanity checks. + **/ + + SetupImageBuffer(target, dataset); + + if (target.GetWidth() != target.GetWidth() || + target.GetHeight() != target.GetHeight()) + { + throw OrthancException(ErrorCode_InternalError); + } + + bool ok; + switch (target.GetFormat()) + { + case PixelFormat_RGB24: + ok = source->GetChannelCount() == 3; + break; + + case PixelFormat_RGBA32: + ok = source->GetChannelCount() == 4; + break; + + case PixelFormat_Grayscale8: + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + ok = source->GetChannelCount() == 1; + break; + + default: + ok = false; // (*) + break; + } + + if (!ok) + { + throw OrthancException(ErrorCode_InternalError); + } + + + /** + * Loop over the DICOM buffer, storing its value into the target + * image. + **/ + + ImageAccessor accessor(target.GetAccessor()); + + switch (target.GetFormat()) + { + case PixelFormat_RGB24: + case PixelFormat_RGBA32: + case PixelFormat_Grayscale8: + CopyPixels<uint8_t>(accessor, *source); + break; + + case PixelFormat_Grayscale16: + CopyPixels<uint16_t>(accessor, *source); + break; + + case PixelFormat_SignedGrayscale16: + CopyPixels<int16_t>(accessor, *source); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + #if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 void DicomImageDecoder::DecodeJpegLossless(ImageBuffer& target, - DcmDataset& dataset) + DcmDataset& dataset, + unsigned int frame) { if (!IsJpegLossless(dataset)) { @@ -315,7 +493,7 @@ OFString decompressedColorModel; // Out DJ_RPLossless representationParameter; OFCondition c = decoder.decodeFrame(&representationParameter, pixelSequence, ¶meters, - &dataset, 0, startFragment, accessor.GetBuffer(), + &dataset, frame, startFragment, accessor.GetBuffer(), accessor.GetSize(), decompressedColorModel); if (!c.good()) @@ -326,4 +504,47 @@ #endif + + bool DicomImageDecoder::Decode(ImageBuffer& target, + DcmDataset& dataset, + unsigned int frame) + { + if (IsUncompressedImage(dataset)) + { + DecodeUncompressedImage(target, dataset, frame); + return true; + } + +#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 + if (IsJpegLossless(dataset)) + { + LOG(INFO) << "Decoding a JPEG-LS image"; + DecodeJpegLossless(target, dataset, frame); + return true; + } +#endif + + /** + * This DICOM image format is not natively supported by + * Orthanc. As a last resort, try and decode it through + * DCMTK. This will result in higher memory consumption. This is + * actually the second example of the following page: + * http://support.dcmtk.org/docs/mod_dcmjpeg.html#Examples + **/ + + { + LOG(INFO) << "Using DCMTK to decode a compressed image"; + + std::auto_ptr<DcmDataset> converted(dynamic_cast<DcmDataset*>(dataset.clone())); + converted->chooseRepresentation(EXS_LittleEndianExplicit, NULL); + + if (converted->canWriteXfer(EXS_LittleEndianExplicit)) + { + DecodeUncompressedImageInternal(target, *converted, frame); + return true; + } + } + + return false; + } }
--- a/OrthancServer/Internals/DicomImageDecoder.h Thu Jun 05 16:23:03 2014 +0200 +++ b/OrthancServer/Internals/DicomImageDecoder.h Thu Jun 05 18:14:16 2014 +0200 @@ -42,6 +42,10 @@ { public: // TODO SWITCH TO PRIVATE //private: + static void DecodeUncompressedImageInternal(ImageBuffer& target, + DcmDataset& dataset, + unsigned int frame); + static bool IsPsmctRle1(DcmDataset& dataset); static void SetupImageBuffer(ImageBuffer& target, @@ -51,12 +55,22 @@ DcmDataset& dataset); public: + static bool IsUncompressedImage(const DcmDataset& dataset); + static bool IsJpegLossless(const DcmDataset& dataset); + static void DecodeUncompressedImage(ImageBuffer& target, + DcmDataset& dataset, + unsigned int frame); + #if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 static void DecodeJpegLossless(ImageBuffer& target, - DcmDataset& dataset); + DcmDataset& dataset, + unsigned int frame); #endif + static bool Decode(ImageBuffer& target, + DcmDataset& dataset, + unsigned int frame); }; }
--- a/OrthancServer/OrthancInitialization.cpp Thu Jun 05 16:23:03 2014 +0200 +++ b/OrthancServer/OrthancInitialization.cpp Thu Jun 05 18:14:16 2014 +0200 @@ -45,6 +45,17 @@ #include <boost/thread.hpp> #include <glog/logging.h> + +#if ORTHANC_JPEG_ENABLED == 1 +#include <dcmtk/dcmjpeg/djdecode.h> +#endif + + +#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 +#include <dcmtk/dcmjpls/djdecode.h> +#endif + + namespace Orthanc { static boost::mutex globalMutex_; @@ -182,6 +193,16 @@ RegisterUserContentType(); DicomServer::InitializeDictionary(); + +#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 + LOG(WARNING) << "Registering JPEG Lossless codecs"; + DJLSDecoderRegistration::registerCodecs(); +#endif + +#if ORTHANC_JPEG_ENABLED == 1 + LOG(WARNING) << "Registering JPEG codecs"; + DJDecoderRegistration::registerCodecs(); +#endif } @@ -191,6 +212,16 @@ boost::mutex::scoped_lock lock(globalMutex_); HttpClient::GlobalFinalize(); configuration_.reset(NULL); + +#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 + // Unregister JPEG-LS codecs + DJLSDecoderRegistration::cleanup(); +#endif + +#if ORTHANC_JPEG_ENABLED == 1 + // Unregister JPEG codecs + DJDecoderRegistration::cleanup(); +#endif }
--- a/UnitTestsSources/JpegLossless.cpp Thu Jun 05 16:23:03 2014 +0200 +++ b/UnitTestsSources/JpegLossless.cpp Thu Jun 05 18:14:16 2014 +0200 @@ -49,8 +49,6 @@ TEST(JpegLossless, Basic) { - //DJLSDecoderRegistration::registerCodecs( EJLSUC_default, EJLSPC_restore,OFFalse ); - #if 0 // Fallback @@ -89,7 +87,8 @@ ASSERT_TRUE(DicomImageDecoder::IsJpegLossless(dataset)); ImageBuffer image; - DicomImageDecoder::DecodeJpegLossless(image, dataset); + //DicomImageDecoder::DecodeJpegLossless(image, dataset, 0); + DicomImageDecoder::Decode(image, dataset, 0); ImageAccessor accessor(image.GetAccessor()); @@ -108,9 +107,6 @@ } #endif - - - //DJLSDecoderRegistration::cleanup(); }