# HG changeset patch # User Sebastien Jodogne # Date 1402414084 -7200 # Node ID 54d2c5df676051e5897c04e4df5c57c7b0914417 # Parent a4d2be5154a9b9ee4df857fd3957fb2a33239e9c# Parent f2841a9e04cf825b323c83690fa46f182329a15e integration jpeg -> mainline diff -r a4d2be5154a9 -r 54d2c5df6760 CMakeLists.txt --- a/CMakeLists.txt Mon Jun 02 13:01:02 2014 +0200 +++ b/CMakeLists.txt Tue Jun 10 17:28:04 2014 +0200 @@ -18,6 +18,8 @@ SET(DCMTK_DICTIONARY_DIR "" CACHE PATH "Directory containing the DCMTK dictionaries \"dicom.dic\" and \"private.dic\" (only when using system version of DCMTK)") SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") SET(UNIT_TESTS_WITH_HTTP_CONNEXIONS ON CACHE BOOL "Allow unit tests to make HTTP requests") +SET(ENABLE_JPEG ON CACHE BOOL "Enable JPEG decompression") +SET(ENABLE_JPEG_LOSSLESS ON CACHE BOOL "Enable JPEG-LS (Lossless) decompression") # Advanced parameters to fine-tune linking against system libraries SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") @@ -66,6 +68,7 @@ Core/DicomFormat/DicomArray.cpp Core/DicomFormat/DicomMap.cpp Core/DicomFormat/DicomTag.cpp + Core/DicomFormat/DicomImageInformation.cpp Core/DicomFormat/DicomIntegerPixelAccessor.cpp Core/DicomFormat/DicomInstanceHasher.cpp Core/Enumerations.cpp @@ -92,6 +95,7 @@ Core/MultiThreading/ThreadedCommandProcessor.cpp Core/ImageFormats/ImageAccessor.cpp Core/ImageFormats/ImageBuffer.cpp + Core/ImageFormats/ImageProcessing.cpp Core/ImageFormats/PngReader.cpp Core/ImageFormats/PngWriter.cpp Core/SQLite/Connection.cpp @@ -126,6 +130,7 @@ OrthancServer/Internals/FindScp.cpp OrthancServer/Internals/MoveScp.cpp OrthancServer/Internals/StoreScp.cpp + OrthancServer/Internals/DicomImageDecoder.cpp OrthancServer/OrthancInitialization.cpp OrthancServer/OrthancPeerParameters.cpp OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp @@ -161,6 +166,8 @@ UnitTestsSources/Lua.cpp UnitTestsSources/MultiThreading.cpp UnitTestsSources/UnitTestsMain.cpp + UnitTestsSources/ImageProcessingTests.cpp + UnitTestsSources/JpegLossless.cpp ) @@ -201,6 +208,20 @@ endif() +if (ENABLE_JPEG) + add_definitions(-DORTHANC_JPEG_ENABLED=1) +else() + add_definitions(-DORTHANC_JPEG_ENABLED=0) +endif() + + +if (ENABLE_JPEG_LOSSLESS) + add_definitions(-DORTHANC_JPEG_LOSSLESS_ENABLED=1) +else() + add_definitions(-DORTHANC_JPEG_LOSSLESS_ENABLED=0) +endif() + + ##################################################################### ## Autogeneration of files diff -r a4d2be5154a9 -r 54d2c5df6760 Core/DicomFormat/DicomImageInformation.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomFormat/DicomImageInformation.cpp Tue Jun 10 17:28:04 2014 +0200 @@ -0,0 +1,188 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "../PrecompiledHeaders.h" + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include "DicomImageInformation.h" + +#include "../OrthancException.h" +#include +#include +#include +#include + +namespace Orthanc +{ + DicomImageInformation::DicomImageInformation(const DicomMap& values) + { + unsigned int pixelRepresentation; + unsigned int planarConfiguration = 0; + + try + { + width_ = boost::lexical_cast(values.GetValue(DICOM_TAG_COLUMNS).AsString()); + height_ = boost::lexical_cast(values.GetValue(DICOM_TAG_ROWS).AsString()); + bitsAllocated_ = boost::lexical_cast(values.GetValue(DICOM_TAG_BITS_ALLOCATED).AsString()); + + try + { + samplesPerPixel_ = boost::lexical_cast(values.GetValue(DICOM_TAG_SAMPLES_PER_PIXEL).AsString()); + } + catch (OrthancException&) + { + samplesPerPixel_ = 1; // Assume 1 color channel + } + + try + { + bitsStored_ = boost::lexical_cast(values.GetValue(DICOM_TAG_BITS_STORED).AsString()); + } + catch (OrthancException&) + { + bitsStored_ = bitsAllocated_; + } + + try + { + highBit_ = boost::lexical_cast(values.GetValue(DICOM_TAG_HIGH_BIT).AsString()); + } + catch (OrthancException&) + { + highBit_ = bitsStored_ - 1; + } + + try + { + pixelRepresentation = boost::lexical_cast(values.GetValue(DICOM_TAG_PIXEL_REPRESENTATION).AsString()); + } + catch (OrthancException&) + { + pixelRepresentation = 0; // Assume unsigned pixels + } + + if (samplesPerPixel_ > 1) + { + // The "Planar Configuration" is only set when "Samples per Pixels" is greater than 1 + // https://www.dabsoft.ch/dicom/3/C.7.6.3.1.3/ + try + { + planarConfiguration = boost::lexical_cast(values.GetValue(DICOM_TAG_PLANAR_CONFIGURATION).AsString()); + } + catch (OrthancException&) + { + planarConfiguration = 0; // Assume interleaved color channels + } + } + } + catch (boost::bad_lexical_cast&) + { + throw OrthancException(ErrorCode_NotImplemented); + } + catch (OrthancException&) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + try + { + numberOfFrames_ = boost::lexical_cast(values.GetValue(DICOM_TAG_NUMBER_OF_FRAMES).AsString()); + } + catch (OrthancException) + { + // If the tag "NumberOfFrames" is absent, assume there is a single frame + numberOfFrames_ = 1; + } + catch (boost::bad_lexical_cast&) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + if ((bitsAllocated_ != 8 && bitsAllocated_ != 16 && + bitsAllocated_ != 24 && bitsAllocated_ != 32) || + numberOfFrames_ == 0 || + (planarConfiguration != 0 && planarConfiguration != 1)) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + if (bitsAllocated_ > 32 || + bitsStored_ >= 32) + { + // Not available, as the accessor internally uses int32_t values + throw OrthancException(ErrorCode_NotImplemented); + } + + if (samplesPerPixel_ == 0) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + bytesPerValue_ = bitsAllocated_ / 8; + + isPlanar_ = (planarConfiguration != 0 ? true : false); + isSigned_ = (pixelRepresentation != 0 ? true : false); + } + + + bool DicomImageInformation::ExtractPixelFormat(PixelFormat& format) const + { + if (GetBitsStored() == 8 && GetChannelCount() == 1 && !IsSigned()) + { + format = PixelFormat_Grayscale8; + return true; + } + + if (GetBitsStored() == 8 && GetChannelCount() == 3 && !IsSigned()) + { + format = PixelFormat_RGB24; + return true; + } + + if (GetBitsAllocated() == 16 && GetChannelCount() == 1 && !IsSigned()) + { + format = PixelFormat_Grayscale16; + return true; + } + + if (GetBitsAllocated() == 16 && GetChannelCount() == 1 && IsSigned()) + { + format = PixelFormat_SignedGrayscale16; + return true; + } + + return false; + } +} diff -r a4d2be5154a9 -r 54d2c5df6760 Core/DicomFormat/DicomImageInformation.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomFormat/DicomImageInformation.h Tue Jun 10 17:28:04 2014 +0200 @@ -0,0 +1,117 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "DicomMap.h" + +#include + +namespace Orthanc +{ + class DicomImageInformation + { + private: + unsigned int width_; + unsigned int height_; + unsigned int samplesPerPixel_; + unsigned int numberOfFrames_; + + bool isPlanar_; + bool isSigned_; + size_t bytesPerValue_; + + unsigned int bitsAllocated_; + unsigned int bitsStored_; + unsigned int highBit_; + + public: + DicomImageInformation(const DicomMap& values); + + unsigned int GetWidth() const + { + return width_; + } + + unsigned int GetHeight() const + { + return height_; + } + + unsigned int GetNumberOfFrames() const + { + return numberOfFrames_; + } + + unsigned int GetChannelCount() const + { + return samplesPerPixel_; + } + + unsigned int GetBitsStored() const + { + return bitsStored_; + } + + size_t GetBytesPerValue() const + { + return bytesPerValue_; + } + + bool IsSigned() const + { + return isSigned_; + } + + unsigned int GetBitsAllocated() const + { + return bitsAllocated_; + } + + unsigned int GetHighBit() const + { + return highBit_; + } + + bool IsPlanar() const + { + return isPlanar_; + } + + unsigned int GetShift() const + { + return highBit_ + 1 - bitsStored_; + } + + bool ExtractPixelFormat(PixelFormat& format) const; + }; +} diff -r a4d2be5154a9 -r 54d2c5df6760 Core/DicomFormat/DicomIntegerPixelAccessor.cpp --- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Mon Jun 02 13:01:02 2014 +0200 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Tue Jun 10 17:28:04 2014 +0200 @@ -49,102 +49,42 @@ DicomIntegerPixelAccessor::DicomIntegerPixelAccessor(const DicomMap& values, const void* pixelData, size_t size) : + information_(values), pixelData_(pixelData), size_(size) { - unsigned int bitsAllocated; - unsigned int bitsStored; - unsigned int highBit; - unsigned int pixelRepresentation; - planarConfiguration_ = 0; - - try - { - width_ = boost::lexical_cast(values.GetValue(DICOM_TAG_COLUMNS).AsString()); - height_ = boost::lexical_cast(values.GetValue(DICOM_TAG_ROWS).AsString()); - samplesPerPixel_ = boost::lexical_cast(values.GetValue(DICOM_TAG_SAMPLES_PER_PIXEL).AsString()); - bitsAllocated = boost::lexical_cast(values.GetValue(DICOM_TAG_BITS_ALLOCATED).AsString()); - bitsStored = boost::lexical_cast(values.GetValue(DICOM_TAG_BITS_STORED).AsString()); - highBit = boost::lexical_cast(values.GetValue(DICOM_TAG_HIGH_BIT).AsString()); - pixelRepresentation = boost::lexical_cast(values.GetValue(DICOM_TAG_PIXEL_REPRESENTATION).AsString()); - - if (samplesPerPixel_ > 1) - { - // The "Planar Configuration" is only set when "Samples per Pixels" is greater than 1 - // https://www.dabsoft.ch/dicom/3/C.7.6.3.1.3/ - planarConfiguration_ = boost::lexical_cast(values.GetValue(DICOM_TAG_PLANAR_CONFIGURATION).AsString()); - } - } - catch (boost::bad_lexical_cast) - { - throw OrthancException(ErrorCode_NotImplemented); - } - catch (OrthancException) - { - throw OrthancException(ErrorCode_NotImplemented); - } + frame_ = 0; + frameOffset_ = (information_.GetHeight() * information_.GetWidth() * + information_.GetBytesPerValue() * information_.GetChannelCount()); - frame_ = 0; - try - { - numberOfFrames_ = boost::lexical_cast(values.GetValue(DICOM_TAG_NUMBER_OF_FRAMES).AsString()); - } - catch (OrthancException) - { - // If the tag "NumberOfFrames" is absent, assume there is a single frame - numberOfFrames_ = 1; - } - catch (boost::bad_lexical_cast) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - if ((bitsAllocated != 8 && bitsAllocated != 16 && - bitsAllocated != 24 && bitsAllocated != 32) || - numberOfFrames_ == 0 || - (planarConfiguration_ != 0 && planarConfiguration_ != 1)) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - if (bitsAllocated > 32 || - bitsStored >= 32) - { - // Not available, as the accessor internally uses int32_t values - throw OrthancException(ErrorCode_NotImplemented); - } - - if (samplesPerPixel_ == 0) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - bytesPerPixel_ = bitsAllocated / 8; - shift_ = highBit + 1 - bitsStored; - frameOffset_ = height_ * width_ * bytesPerPixel_ * samplesPerPixel_; - - if (numberOfFrames_ * frameOffset_ > size) + if (information_.GetNumberOfFrames() * frameOffset_ > size) { throw OrthancException(ErrorCode_BadFileFormat); } - /*printf("%d %d %d %d %d %d %d %d\n", width_, height_, samplesPerPixel_, bitsAllocated, - bitsStored, highBit, pixelRepresentation, numberOfFrames_);*/ - - if (pixelRepresentation) + if (information_.IsSigned()) { // Pixels are signed - mask_ = (1 << (bitsStored - 1)) - 1; - signMask_ = (1 << (bitsStored - 1)); + mask_ = (1 << (information_.GetBitsStored() - 1)) - 1; + signMask_ = (1 << (information_.GetBitsStored() - 1)); } else { // Pixels are unsigned - mask_ = (1 << bitsStored) - 1; + mask_ = (1 << information_.GetBitsStored()) - 1; signMask_ = 0; } - if (planarConfiguration_ == 0) + if (information_.IsPlanar()) + { + /** + * Each color plane shall be sent contiguously. For RGB images, + * this means the order of the pixel values sent is R1, R2, R3, + * ..., G1, G2, G3, ..., B1, B2, B3, etc. + **/ + rowOffset_ = information_.GetWidth() * information_.GetBytesPerValue(); + } + else { /** * The sample values for the first pixel are followed by the @@ -152,16 +92,7 @@ * means the order of the pixel values sent shall be R1, G1, B1, * R2, G2, B2, ..., etc. **/ - rowOffset_ = width_ * bytesPerPixel_ * samplesPerPixel_; - } - else - { - /** - * Each color plane shall be sent contiguously. For RGB images, - * this means the order of the pixel values sent is R1, R2, R3, - * ..., G1, G2, G3, ..., B1, B2, B3, etc. - **/ - rowOffset_ = width_ * bytesPerPixel_; + rowOffset_ = information_.GetWidth() * information_.GetBytesPerValue() * information_.GetChannelCount(); } } @@ -169,7 +100,7 @@ void DicomIntegerPixelAccessor::GetExtremeValues(int32_t& min, int32_t& max) const { - if (height_ == 0 || width_ == 0) + if (information_.GetHeight() == 0 || information_.GetWidth() == 0) { min = max = 0; return; @@ -178,11 +109,11 @@ min = std::numeric_limits::max(); max = std::numeric_limits::min(); - for (unsigned int y = 0; y < height_; y++) + for (unsigned int y = 0; y < information_.GetHeight(); y++) { - for (unsigned int x = 0; x < width_; x++) + for (unsigned int x = 0; x < information_.GetWidth(); x++) { - for (unsigned int c = 0; c < GetChannelCount(); c++) + for (unsigned int c = 0; c < information_.GetChannelCount(); c++) { int32_t v = GetValue(x, y); if (v < min) @@ -199,13 +130,25 @@ unsigned int y, unsigned int channel) const { - assert(x < width_ && y < height_ && channel < samplesPerPixel_); + assert(x < information_.GetWidth() && + y < information_.GetHeight() && + channel < information_.GetChannelCount()); const uint8_t* pixel = reinterpret_cast(pixelData_) + y * rowOffset_ + frame_ * frameOffset_; // https://www.dabsoft.ch/dicom/3/C.7.6.3.1.3/ - if (planarConfiguration_ == 0) + if (information_.IsPlanar()) + { + /** + * Each color plane shall be sent contiguously. For RGB images, + * this means the order of the pixel values sent is R1, R2, R3, + * ..., G1, G2, G3, ..., B1, B2, B3, etc. + **/ + assert(frameOffset_ % information_.GetChannelCount() == 0); + pixel += channel * frameOffset_ / information_.GetChannelCount() + x * information_.GetBytesPerValue(); + } + else { /** * The sample values for the first pixel are followed by the @@ -213,29 +156,19 @@ * means the order of the pixel values sent shall be R1, G1, B1, * R2, G2, B2, ..., etc. **/ - pixel += channel * bytesPerPixel_ + x * samplesPerPixel_ * bytesPerPixel_; - } - else - { - /** - * Each color plane shall be sent contiguously. For RGB images, - * this means the order of the pixel values sent is R1, R2, R3, - * ..., G1, G2, G3, ..., B1, B2, B3, etc. - **/ - assert(frameOffset_ % samplesPerPixel_ == 0); - pixel += channel * frameOffset_ / samplesPerPixel_ + x * bytesPerPixel_; + pixel += channel * information_.GetBytesPerValue() + x * information_.GetChannelCount() * information_.GetBytesPerValue(); } uint32_t v; v = pixel[0]; - if (bytesPerPixel_ >= 2) + if (information_.GetBytesPerValue() >= 2) v = v + (static_cast(pixel[1]) << 8); - if (bytesPerPixel_ >= 3) + if (information_.GetBytesPerValue() >= 3) v = v + (static_cast(pixel[2]) << 16); - if (bytesPerPixel_ >= 4) + if (information_.GetBytesPerValue() >= 4) v = v + (static_cast(pixel[3]) << 24); - v = v >> shift_; + v = v >> information_.GetShift(); if (v & signMask_) { @@ -253,7 +186,7 @@ void DicomIntegerPixelAccessor::SetCurrentFrame(unsigned int frame) { - if (frame >= numberOfFrames_) + if (frame >= information_.GetNumberOfFrames()) { throw OrthancException(ErrorCode_ParameterOutOfRange); } diff -r a4d2be5154a9 -r 54d2c5df6760 Core/DicomFormat/DicomIntegerPixelAccessor.h --- a/Core/DicomFormat/DicomIntegerPixelAccessor.h Mon Jun 02 13:01:02 2014 +0200 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.h Tue Jun 10 17:28:04 2014 +0200 @@ -34,6 +34,8 @@ #include "DicomMap.h" +#include "DicomImageInformation.h" + #include namespace Orthanc @@ -41,20 +43,14 @@ class DicomIntegerPixelAccessor { private: - unsigned int width_; - unsigned int height_; - unsigned int samplesPerPixel_; - unsigned int numberOfFrames_; - unsigned int planarConfiguration_; + DicomImageInformation information_; + + uint32_t signMask_; + uint32_t mask_; + const void* pixelData_; size_t size_; - - uint8_t shift_; - uint32_t signMask_; - uint32_t mask_; - size_t bytesPerPixel_; unsigned int frame_; - size_t frameOffset_; size_t rowOffset_; @@ -63,19 +59,9 @@ const void* pixelData, size_t size); - unsigned int GetWidth() const - { - return width_; - } - - unsigned int GetHeight() const + const DicomImageInformation GetInformation() const { - return height_; - } - - unsigned int GetNumberOfFrames() const - { - return numberOfFrames_; + return information_; } unsigned int GetCurrentFrame() const @@ -88,11 +74,11 @@ void GetExtremeValues(int32_t& min, int32_t& max) const; - unsigned int GetChannelCount() const + int32_t GetValue(unsigned int x, unsigned int y, unsigned int channel = 0) const; + + const void* GetPixelData() const { - return samplesPerPixel_; + return pixelData_; } - - int32_t GetValue(unsigned int x, unsigned int y, unsigned int channel = 0) const; }; } diff -r a4d2be5154a9 -r 54d2c5df6760 Core/Enumerations.h --- a/Core/Enumerations.h Mon Jun 02 13:01:02 2014 +0200 +++ b/Core/Enumerations.h Tue Jun 10 17:28:04 2014 +0200 @@ -69,7 +69,9 @@ ErrorCode_FullStorage, ErrorCode_CorruptedFile, ErrorCode_InexistentTag, - ErrorCode_ReadOnly + ErrorCode_ReadOnly, + ErrorCode_IncompatibleImageFormat, + ErrorCode_IncompatibleImageSize }; /** @@ -80,34 +82,34 @@ /** * {summary}{Color image in RGB24 format.} * {description}{This format describes a color image. The pixels are stored in 3 - * consecutive bytes. The memory layout is RGB. + * consecutive bytes. The memory layout is RGB.} **/ - PixelFormat_RGB24, + PixelFormat_RGB24 = 1, /** * {summary}{Color image in RGBA32 format.} * {description}{This format describes a color image. The pixels are stored in 4 - * consecutive bytes. The memory layout is RGBA. + * consecutive bytes. The memory layout is RGBA.} **/ - PixelFormat_RGBA32, + PixelFormat_RGBA32 = 2, /** * {summary}{Graylevel 8bpp image.} * {description}{The image is graylevel. Each pixel is unsigned and stored in one byte.} **/ - PixelFormat_Grayscale8, + PixelFormat_Grayscale8 = 3, /** * {summary}{Graylevel, unsigned 16bpp image.} * {description}{The image is graylevel. Each pixel is unsigned and stored in two bytes.} **/ - PixelFormat_Grayscale16, + PixelFormat_Grayscale16 = 4, /** * {summary}{Graylevel, signed 16bpp image.} * {description}{The image is graylevel. Each pixel is signed and stored in two bytes.} **/ - PixelFormat_SignedGrayscale16 + PixelFormat_SignedGrayscale16 = 5 }; @@ -120,22 +122,22 @@ * {summary}{Rescaled to 8bpp.} * {description}{The minimum value of the image is set to 0, and its maximum value is set to 255.} **/ - ImageExtractionMode_Preview, + ImageExtractionMode_Preview = 1, /** * {summary}{Truncation to the [0, 255] range.} **/ - ImageExtractionMode_UInt8, + ImageExtractionMode_UInt8 = 2, /** * {summary}{Truncation to the [0, 65535] range.} **/ - ImageExtractionMode_UInt16, + ImageExtractionMode_UInt16 = 3, /** * {summary}{Truncation to the [-32768, 32767] range.} **/ - ImageExtractionMode_Int16 + ImageExtractionMode_Int16 = 4 }; diff -r a4d2be5154a9 -r 54d2c5df6760 Core/ImageFormats/ImageAccessor.cpp --- a/Core/ImageFormats/ImageAccessor.cpp Mon Jun 02 13:01:02 2014 +0200 +++ b/Core/ImageFormats/ImageAccessor.cpp Tue Jun 10 17:28:04 2014 +0200 @@ -36,6 +36,8 @@ #include "../OrthancException.h" #include +#include +#include namespace Orthanc { @@ -43,6 +45,7 @@ { if (readOnly_) { + LOG(ERROR) << "Trying to write on a read-only image"; throw OrthancException(ErrorCode_ReadOnly); } @@ -67,6 +70,7 @@ { if (readOnly_) { + LOG(ERROR) << "Trying to write on a read-only image"; throw OrthancException(ErrorCode_ReadOnly); } @@ -104,6 +108,8 @@ height_ = height; pitch_ = pitch; buffer_ = const_cast(buffer); + + assert(GetBytesPerPixel(format_) * width_ <= pitch_); } @@ -119,5 +125,7 @@ height_ = height; pitch_ = pitch; buffer_ = buffer; + + assert(GetBytesPerPixel(format_) * width_ <= pitch_); } } diff -r a4d2be5154a9 -r 54d2c5df6760 Core/ImageFormats/ImageAccessor.h --- a/Core/ImageFormats/ImageAccessor.h Mon Jun 02 13:01:02 2014 +0200 +++ b/Core/ImageFormats/ImageAccessor.h Tue Jun 10 17:28:04 2014 +0200 @@ -77,6 +77,11 @@ return pitch_; } + unsigned int GetSize() const + { + return GetHeight() * GetPitch(); + } + const void* GetConstBuffer() const { return buffer_; diff -r a4d2be5154a9 -r 54d2c5df6760 Core/ImageFormats/ImageBuffer.cpp --- a/Core/ImageFormats/ImageBuffer.cpp Mon Jun 02 13:01:02 2014 +0200 +++ b/Core/ImageFormats/ImageBuffer.cpp Tue Jun 10 17:28:04 2014 +0200 @@ -33,22 +33,40 @@ #include "../PrecompiledHeaders.h" #include "ImageBuffer.h" +#include "../OrthancException.h" + +#include +#include + namespace Orthanc { void ImageBuffer::Allocate() { if (changed_) { - pitch_ = GetBytesPerPixel() * width_; + Deallocate(); - data_.resize(pitch_ * height_); - if (data_.size() > 0) + /* + if (forceMinimalPitch_) + { + TODO: Align pitch and memory buffer to optimal size for SIMD. + } + */ + + pitch_ = GetBytesPerPixel() * width_; + size_t size = pitch_ * height_; + + if (size == 0) { - buffer_ = &data_[0]; + buffer_ = NULL; } else { - buffer_ = 0; + buffer_ = malloc(size); + if (buffer_ == NULL) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } } changed_ = false; @@ -56,9 +74,32 @@ } - ImageBuffer::ImageBuffer() : changed_(false) + void ImageBuffer::Deallocate() + { + if (buffer_ != NULL) + { + free(buffer_); + buffer_ = NULL; + changed_ = true; + } + } + + + ImageBuffer::ImageBuffer(unsigned int width, + unsigned int height, + PixelFormat format) + { + Initialize(); + SetWidth(width); + SetHeight(height); + SetFormat(format); + } + + + void ImageBuffer::Initialize() { changed_ = false; + forceMinimalPitch_ = true; format_ = PixelFormat_Grayscale8; width_ = 0; height_ = 0; @@ -69,22 +110,31 @@ void ImageBuffer::SetFormat(PixelFormat format) { - changed_ = true; - format_ = format; + if (format != format_) + { + changed_ = true; + format_ = format; + } } void ImageBuffer::SetWidth(unsigned int width) { - changed_ = true; - width_ = width; + if (width != width_) + { + changed_ = true; + width_ = width; + } } void ImageBuffer::SetHeight(unsigned int height) { - changed_ = true; - height_ = height; + if (height != height_) + { + changed_ = true; + height_ = height; + } } @@ -106,4 +156,37 @@ accessor.AssignReadOnly(format_, width_, height_, pitch_, buffer_); return accessor; } + + + void ImageBuffer::SetMinimalPitchForced(bool force) + { + if (force != forceMinimalPitch_) + { + changed_ = true; + forceMinimalPitch_ = force; + } + } + + + void ImageBuffer::AcquireOwnership(ImageBuffer& other) + { + // Remove the content of the current image + Deallocate(); + + // Force the allocation of the other image (if not already + // allocated) + other.Allocate(); + + // Transfer the content of the other image + changed_ = false; + forceMinimalPitch_ = other.forceMinimalPitch_; + format_ = other.format_; + width_ = other.width_; + height_ = other.height_; + pitch_ = other.pitch_; + buffer_ = other.buffer_; + + // Force the reinitialization of the other image + other.Initialize(); + } } diff -r a4d2be5154a9 -r 54d2c5df6760 Core/ImageFormats/ImageBuffer.h --- a/Core/ImageFormats/ImageBuffer.h Mon Jun 02 13:01:02 2014 +0200 +++ b/Core/ImageFormats/ImageBuffer.h Tue Jun 10 17:28:04 2014 +0200 @@ -36,25 +36,42 @@ #include #include +#include namespace Orthanc { - class ImageBuffer + class ImageBuffer : public boost::noncopyable { private: bool changed_; - std::vector data_; + bool forceMinimalPitch_; // Currently unused PixelFormat format_; unsigned int width_; unsigned int height_; unsigned int pitch_; - uint8_t *buffer_; + void *buffer_; + + void Initialize(); void Allocate(); + void Deallocate(); + public: - ImageBuffer(); + ImageBuffer(unsigned int width, + unsigned int height, + PixelFormat format); + + ImageBuffer() + { + Initialize(); + } + + ~ImageBuffer() + { + Deallocate(); + } PixelFormat GetFormat() const { @@ -85,5 +102,14 @@ ImageAccessor GetAccessor(); ImageAccessor GetConstAccessor(); + + bool IsMinimalPitchForced() const + { + return forceMinimalPitch_; + } + + void SetMinimalPitchForced(bool force); + + void AcquireOwnership(ImageBuffer& other); }; } diff -r a4d2be5154a9 -r 54d2c5df6760 Core/ImageFormats/ImageProcessing.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/ImageFormats/ImageProcessing.cpp Tue Jun 10 17:28:04 2014 +0200 @@ -0,0 +1,473 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "../PrecompiledHeaders.h" +#include "ImageProcessing.h" + +#include "../OrthancException.h" + +#include + +#include +#include +#include +#include + +namespace Orthanc +{ + template + static void ConvertInternal(ImageAccessor& target, + const ImageAccessor& source) + { + const TargetType minValue = std::numeric_limits::min(); + const TargetType maxValue = std::numeric_limits::max(); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + TargetType* t = reinterpret_cast(target.GetRow(y)); + const SourceType* s = reinterpret_cast(source.GetConstRow(y)); + + for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s++) + { + if (static_cast(*s) < static_cast(minValue)) + { + *t = minValue; + } + else if (static_cast(*s) > static_cast(maxValue)) + { + *t = maxValue; + } + else + { + *t = static_cast(*s); + } + } + } + } + + + template + static void SetInternal(ImageAccessor& image, + int64_t constant) + { + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + PixelType* p = reinterpret_cast(image.GetRow(y)); + + for (unsigned int x = 0; x < image.GetWidth(); x++, p++) + { + *p = static_cast(constant); + } + } + } + + + template + static void GetMinMaxValueInternal(PixelType& minValue, + PixelType& maxValue, + const ImageAccessor& source) + { + // Deal with the special case of empty image + if (source.GetWidth() == 0 || + source.GetHeight() == 0) + { + minValue = 0; + maxValue = 0; + return; + } + + minValue = std::numeric_limits::max(); + maxValue = std::numeric_limits::min(); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + const PixelType* p = reinterpret_cast(source.GetConstRow(y)); + + for (unsigned int x = 0; x < source.GetWidth(); x++, p++) + { + if (*p < minValue) + { + minValue = *p; + } + + if (*p > maxValue) + { + maxValue = *p; + } + } + } + } + + + + template + static void AddConstantInternal(ImageAccessor& image, + int64_t constant) + { + if (constant == 0) + { + return; + } + + const int64_t minValue = std::numeric_limits::min(); + const int64_t maxValue = std::numeric_limits::max(); + + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + PixelType* p = reinterpret_cast(image.GetRow(y)); + + for (unsigned int x = 0; x < image.GetWidth(); x++, p++) + { + int64_t v = static_cast(*p) + constant; + + if (v > maxValue) + { + *p = maxValue; + } + else if (v < minValue) + { + *p = minValue; + } + else + { + *p = static_cast(v); + } + } + } + } + + + + template + void MultiplyConstantInternal(ImageAccessor& image, + float factor) + { + if (abs(factor - 1.0f) <= std::numeric_limits::epsilon()) + { + return; + } + + const int64_t minValue = std::numeric_limits::min(); + const int64_t maxValue = std::numeric_limits::max(); + + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + PixelType* p = reinterpret_cast(image.GetRow(y)); + + for (unsigned int x = 0; x < image.GetWidth(); x++, p++) + { + int64_t v = boost::math::llround(static_cast(*p) * factor); + + if (v > maxValue) + { + *p = maxValue; + } + else if (v < minValue) + { + *p = minValue; + } + else + { + *p = static_cast(v); + } + } + } + } + + + template + void ShiftScaleInternal(ImageAccessor& image, + float offset, + float scaling) + { + const float minValue = static_cast(std::numeric_limits::min()); + const float maxValue = static_cast(std::numeric_limits::max()); + + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + PixelType* p = reinterpret_cast(image.GetRow(y)); + + for (unsigned int x = 0; x < image.GetWidth(); x++, p++) + { + float v = (static_cast(*p) + offset) * scaling; + + if (v > maxValue) + { + *p = std::numeric_limits::max(); + } + else if (v < minValue) + { + *p = std::numeric_limits::min(); + } + else + { + *p = static_cast(boost::math::iround(v)); + } + } + } + } + + + void ImageProcessing::Copy(ImageAccessor& target, + const ImageAccessor& source) + { + if (target.GetWidth() != source.GetWidth() || + target.GetHeight() != source.GetHeight()) + { + throw OrthancException(ErrorCode_IncompatibleImageSize); + } + + if (target.GetFormat() != source.GetFormat()) + { + throw OrthancException(ErrorCode_IncompatibleImageFormat); + } + + unsigned int lineSize = GetBytesPerPixel(source.GetFormat()) * source.GetWidth(); + + assert(source.GetPitch() >= lineSize && target.GetPitch() >= lineSize); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + memcpy(target.GetRow(y), source.GetConstRow(y), lineSize); + } + } + + + void ImageProcessing::Convert(ImageAccessor& target, + const ImageAccessor& source) + { + if (target.GetWidth() != source.GetWidth() || + target.GetHeight() != source.GetHeight()) + { + throw OrthancException(ErrorCode_IncompatibleImageSize); + } + + if (source.GetFormat() == target.GetFormat()) + { + Copy(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale16 && + source.GetFormat() == PixelFormat_Grayscale8) + { + ConvertInternal(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_SignedGrayscale16 && + source.GetFormat() == PixelFormat_Grayscale8) + { + ConvertInternal(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale8 && + source.GetFormat() == PixelFormat_Grayscale16) + { + ConvertInternal(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_SignedGrayscale16 && + source.GetFormat() == PixelFormat_Grayscale16) + { + ConvertInternal(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale8 && + source.GetFormat() == PixelFormat_SignedGrayscale16) + { + ConvertInternal(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale16 && + source.GetFormat() == PixelFormat_SignedGrayscale16) + { + ConvertInternal(target, source); + return; + } + + throw OrthancException(ErrorCode_NotImplemented); + } + + + + void ImageProcessing::Set(ImageAccessor& image, + int64_t value) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + SetInternal(image, value); + return; + + case PixelFormat_Grayscale16: + SetInternal(image, value); + return; + + case PixelFormat_SignedGrayscale16: + SetInternal(image, value); + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void ImageProcessing::ShiftRight(ImageAccessor& image, + unsigned int shift) + { + if (image.GetWidth() == 0 || + image.GetHeight() == 0 || + shift == 0) + { + // Nothing to do + return; + } + + throw OrthancException(ErrorCode_NotImplemented); + } + + + void ImageProcessing::GetMinMaxValue(int64_t& minValue, + int64_t& maxValue, + const ImageAccessor& image) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + { + uint8_t a, b; + GetMinMaxValueInternal(a, b, image); + minValue = a; + maxValue = b; + break; + } + + case PixelFormat_Grayscale16: + { + uint16_t a, b; + GetMinMaxValueInternal(a, b, image); + minValue = a; + maxValue = b; + break; + } + + case PixelFormat_SignedGrayscale16: + { + int16_t a, b; + GetMinMaxValueInternal(a, b, image); + minValue = a; + maxValue = b; + break; + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + + void ImageProcessing::AddConstant(ImageAccessor& image, + int64_t value) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + AddConstantInternal(image, value); + return; + + case PixelFormat_Grayscale16: + AddConstantInternal(image, value); + return; + + case PixelFormat_SignedGrayscale16: + AddConstantInternal(image, value); + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void ImageProcessing::MultiplyConstant(ImageAccessor& image, + float factor) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + MultiplyConstantInternal(image, factor); + return; + + case PixelFormat_Grayscale16: + MultiplyConstantInternal(image, factor); + return; + + case PixelFormat_SignedGrayscale16: + MultiplyConstantInternal(image, factor); + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void ImageProcessing::ShiftScale(ImageAccessor& image, + float offset, + float scaling) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + ShiftScaleInternal(image, offset, scaling); + return; + + case PixelFormat_Grayscale16: + ShiftScaleInternal(image, offset, scaling); + return; + + case PixelFormat_SignedGrayscale16: + ShiftScaleInternal(image, offset, scaling); + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } +} diff -r a4d2be5154a9 -r 54d2c5df6760 Core/ImageFormats/ImageProcessing.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/ImageFormats/ImageProcessing.h Tue Jun 10 17:28:04 2014 +0200 @@ -0,0 +1,70 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "ImageAccessor.h" + +#include + +namespace Orthanc +{ + class ImageProcessing + { + public: + static void Copy(ImageAccessor& target, + const ImageAccessor& source); + + static void Convert(ImageAccessor& target, + const ImageAccessor& source); + + static void Set(ImageAccessor& image, + int64_t value); + + static void ShiftRight(ImageAccessor& target, + unsigned int shift); + + static void GetMinMaxValue(int64_t& minValue, + int64_t& maxValue, + const ImageAccessor& image); + + static void AddConstant(ImageAccessor& image, + int64_t value); + + static void MultiplyConstant(ImageAccessor& image, + float factor); + + static void ShiftScale(ImageAccessor& image, + float offset, + float scaling); + }; +} diff -r a4d2be5154a9 -r 54d2c5df6760 Core/ImageFormats/PngWriter.h --- a/Core/ImageFormats/PngWriter.h Mon Jun 02 13:01:02 2014 +0200 +++ b/Core/ImageFormats/PngWriter.h Tue Jun 10 17:28:04 2014 +0200 @@ -32,7 +32,7 @@ #pragma once -#include "../Enumerations.h" +#include "ImageAccessor.h" #include #include @@ -74,5 +74,19 @@ unsigned int pitch, PixelFormat format, const void* buffer); + + void WriteToFile(const char* filename, + const ImageAccessor& accessor) + { + WriteToFile(filename, accessor.GetWidth(), accessor.GetHeight(), + accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); + } + + void WriteToMemory(std::string& png, + const ImageAccessor& accessor) + { + WriteToMemory(png, accessor.GetWidth(), accessor.GetHeight(), + accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); + } }; } diff -r a4d2be5154a9 -r 54d2c5df6760 Core/OrthancException.cpp --- a/Core/OrthancException.cpp Mon Jun 02 13:01:02 2014 +0200 +++ b/Core/OrthancException.cpp Tue Jun 10 17:28:04 2014 +0200 @@ -115,6 +115,12 @@ case ErrorCode_ReadOnly: return "Cannot modify a read-only data structure"; + case ErrorCode_IncompatibleImageSize: + return "Incompatible size of the images"; + + case ErrorCode_IncompatibleImageFormat: + return "Incompatible format of the images"; + case ErrorCode_Custom: default: return "???"; diff -r a4d2be5154a9 -r 54d2c5df6760 LinuxCompilation.txt --- a/LinuxCompilation.txt Mon Jun 02 13:01:02 2014 +0200 +++ b/LinuxCompilation.txt Tue Jun 10 17:28:04 2014 +0200 @@ -134,13 +134,27 @@ uuid-dev libcurl4-openssl-dev liblua5.1-0-dev \ libgoogle-glog-dev libgtest-dev libpng-dev \ libsqlite3-dev libssl-dev zlib1g-dev \ - libdcmtk2-dev libboost-all-dev libwrap0-dev + libdcmtk2-dev libboost-all-dev libwrap0-dev libcharls-dev + +With JPEG: + +# cmake "-DDCMTK_LIBRARIES=CharLS;dcmjpls;wrap;oflog" \ + -DALLOW_DOWNLOADS=ON \ + -DUSE_SYSTEM_MONGOOSE=OFF \ + -DUSE_SYSTEM_JSONCPP=OFF \ + -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + ~/Orthanc + + +Without JPEG: # cmake "-DDCMTK_LIBRARIES=wrap;oflog" \ -DALLOW_DOWNLOADS=ON \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_SYSTEM_JSONCPP=OFF \ -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DENABLE_JPEG=OFF \ + -DENABLE_JPEG_LOSSLESS=OFF \ ~/Orthanc diff -r a4d2be5154a9 -r 54d2c5df6760 OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h --- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h Mon Jun 02 13:01:02 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h Tue Jun 10 17:28:04 2014 +0200 @@ -507,35 +507,35 @@ * The image is graylevel. Each pixel is signed and stored in two bytes. * **/ - PixelFormat_SignedGrayscale16 = 4, + PixelFormat_SignedGrayscale16 = 5, /** * @brief Color image in RGB24 format. * - * Color image in RGB24 format. + * This format describes a color image. The pixels are stored in 3 consecutive bytes. The memory layout is RGB. * **/ - PixelFormat_RGB24 = 0, + PixelFormat_RGB24 = 1, /** * @brief Color image in RGBA32 format. * - * Color image in RGBA32 format. + * This format describes a color image. The pixels are stored in 4 consecutive bytes. The memory layout is RGBA. * **/ - PixelFormat_RGBA32 = 1, + PixelFormat_RGBA32 = 2, /** * @brief Graylevel 8bpp image. * * The image is graylevel. Each pixel is unsigned and stored in one byte. * **/ - PixelFormat_Grayscale8 = 2, + PixelFormat_Grayscale8 = 3, /** * @brief Graylevel, unsigned 16bpp image. * * The image is graylevel. Each pixel is unsigned and stored in two bytes. * **/ - PixelFormat_Grayscale16 = 3 + PixelFormat_Grayscale16 = 4 }; } @@ -556,28 +556,28 @@ * Truncation to the [-32768, 32767] range. * **/ - ImageExtractionMode_Int16 = 3, + ImageExtractionMode_Int16 = 4, /** * @brief Rescaled to 8bpp. * * The minimum value of the image is set to 0, and its maximum value is set to 255. * **/ - ImageExtractionMode_Preview = 0, + ImageExtractionMode_Preview = 1, /** * @brief Truncation to the [0, 255] range. * * Truncation to the [0, 255] range. * **/ - ImageExtractionMode_UInt8 = 1, + ImageExtractionMode_UInt8 = 2, /** * @brief Truncation to the [0, 65535] range. * * Truncation to the [0, 65535] range. * **/ - ImageExtractionMode_UInt16 = 2 + ImageExtractionMode_UInt16 = 3 }; } diff -r a4d2be5154a9 -r 54d2c5df6760 OrthancServer/FromDcmtkBridge.cpp --- a/OrthancServer/FromDcmtkBridge.cpp Mon Jun 02 13:01:02 2014 +0200 +++ b/OrthancServer/FromDcmtkBridge.cpp Tue Jun 10 17:28:04 2014 +0200 @@ -31,55 +31,15 @@ -/*========================================================================= - - This file is based on portions of the following project: - - Program: GDCM (Grassroots DICOM). A DICOM library - Module: http://gdcm.sourceforge.net/Copyright.html - -Copyright (c) 2006-2011 Mathieu Malaterre -Copyright (c) 1993-2005 CREATIS -(CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any - contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to - endorse or promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - #include "PrecompiledHeadersServer.h" #ifndef NOMINMAX #define NOMINMAX #endif +#include "Internals/DicomImageDecoder.h" + #include "FromDcmtkBridge.h" - #include "ToDcmtkBridge.h" #include "../Core/Toolbox.h" #include "../Core/OrthancException.h" @@ -468,318 +428,44 @@ } - static void ExtractPngImageColorPreview(std::string& result, - DicomIntegerPixelAccessor& accessor) - { - assert(accessor.GetChannelCount() == 3); - PngWriter w; - - std::vector image(accessor.GetWidth() * accessor.GetHeight() * 3, 0); - uint8_t* pixel = &image[0]; - - for (unsigned int y = 0; y < accessor.GetHeight(); y++) - { - for (unsigned int x = 0; x < accessor.GetWidth(); x++) - { - for (unsigned int c = 0; c < 3; c++, pixel++) - { - int32_t v = accessor.GetValue(x, y, c); - if (v < 0) - *pixel = 0; - else if (v > 255) - *pixel = 255; - else - *pixel = v; - } - } - } - - w.WriteToMemory(result, accessor.GetWidth(), accessor.GetHeight(), - accessor.GetWidth() * 3, PixelFormat_RGB24, &image[0]); - } - - - static void ExtractPngImageGrayscalePreview(std::string& result, - DicomIntegerPixelAccessor& accessor) - { - assert(accessor.GetChannelCount() == 1); - PngWriter w; - - int32_t min, max; - accessor.GetExtremeValues(min, max); - - std::vector image(accessor.GetWidth() * accessor.GetHeight(), 0); - if (min != max) - { - uint8_t* pixel = &image[0]; - for (unsigned int y = 0; y < accessor.GetHeight(); y++) - { - for (unsigned int x = 0; x < accessor.GetWidth(); x++, pixel++) - { - int32_t v = accessor.GetValue(x, y); - *pixel = static_cast( - boost::math::lround(static_cast(v - min) / - static_cast(max - min) * 255.0f)); - } - } - } - - w.WriteToMemory(result, accessor.GetWidth(), accessor.GetHeight(), - accessor.GetWidth(), PixelFormat_Grayscale8, &image[0]); - } - - - template - static void ExtractPngImageTruncate(std::string& result, - DicomIntegerPixelAccessor& accessor, - PixelFormat format) - { - assert(accessor.GetChannelCount() == 1); - - PngWriter w; - - std::vector image(accessor.GetWidth() * accessor.GetHeight(), 0); - T* pixel = &image[0]; - for (unsigned int y = 0; y < accessor.GetHeight(); y++) - { - for (unsigned int x = 0; x < accessor.GetWidth(); x++, pixel++) - { - int32_t v = accessor.GetValue(x, y); - if (v < static_cast(std::numeric_limits::min())) - *pixel = std::numeric_limits::min(); - else if (v > static_cast(std::numeric_limits::max())) - *pixel = std::numeric_limits::max(); - else - *pixel = static_cast(v); - } - } - - w.WriteToMemory(result, accessor.GetWidth(), accessor.GetHeight(), - accessor.GetWidth() * sizeof(T), format, &image[0]); - } - - - static bool DecodePsmctRle1(std::string& output, - DcmDataset& dataset) - { - static const DicomTag tagContent(0x07a1, 0x100a); - static const DicomTag tagCompressionType(0x07a1, 0x1011); - - DcmElement* e; - char* c; - - // Check whether the DICOM instance contains an image encoded with - // the PMSCT_RLE1 scheme. - if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(tagCompressionType), e).good() || - e == NULL || - !e->isaString() || - !e->getString(c).good() || - c == NULL || - strcmp("PMSCT_RLE1", c)) - { - return false; - } - - // OK, this is a custom RLE encoding from Philips. Get the pixel - // data from the appropriate private DICOM tag. - Uint8* pixData = NULL; - if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(tagContent), e).good() || - e == NULL || - e->getUint8Array(pixData) != EC_Normal) - { - return false; - } - - // The "unsigned" below IS VERY IMPORTANT - const uint8_t* inbuffer = reinterpret_cast(pixData); - const size_t length = e->getLength(); - - /** - * The code below is an adaptation of a sample code for GDCM by - * Mathieu Malaterre (under a BSD license). - * http://gdcm.sourceforge.net/html/rle2img_8cxx-example.html - **/ - - // RLE pass - std::vector temp; - temp.reserve(length); - for (size_t i = 0; i < length; i++) - { - if (inbuffer[i] == 0xa5) - { - temp.push_back(inbuffer[i+2]); - for (uint8_t repeat = inbuffer[i + 1]; repeat != 0; repeat--) - { - temp.push_back(inbuffer[i+2]); - } - i += 2; - } - else - { - temp.push_back(inbuffer[i]); - } - } - - // Delta encoding pass - uint16_t delta = 0; - output.clear(); - output.reserve(temp.size()); - for (size_t i = 0; i < temp.size(); i++) - { - uint16_t value; - - if (temp[i] == 0x5a) - { - uint16_t v1 = temp[i + 1]; - uint16_t v2 = temp[i + 2]; - value = (v2 << 8) + v1; - i += 2; - } - else - { - value = delta + (int8_t) temp[i]; - } - - output.push_back(value & 0xff); - output.push_back(value >> 8); - delta = value; - } - - if (output.size() % 2) - { - output.resize(output.size() - 1); - } - - return true; - } - - void FromDcmtkBridge::ExtractPngImage(std::string& result, DcmDataset& dataset, unsigned int frame, ImageExtractionMode mode) { - // See also: http://support.dcmtk.org/wiki/dcmtk/howto/accessing-compressed-data - - std::auto_ptr accessor; + ImageBuffer tmp; + bool ok = false; - DicomMap m; - FromDcmtkBridge::Convert(m, dataset); - - std::string privateContent; - - DcmElement* e; - if (dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), e).good() && - e != NULL) + switch (mode) { - Uint8* pixData = NULL; - if (e->getUint8Array(pixData) == EC_Normal) - { - accessor.reset(new DicomIntegerPixelAccessor(m, pixData, e->getLength())); - accessor->SetCurrentFrame(frame); - } + case ImageExtractionMode_UInt8: + ok = DicomImageDecoder::DecodeAndTruncate(tmp, dataset, frame, PixelFormat_Grayscale8); + break; + + case ImageExtractionMode_UInt16: + ok = DicomImageDecoder::DecodeAndTruncate(tmp, dataset, frame, PixelFormat_Grayscale16); + break; + + case ImageExtractionMode_Int16: + ok = DicomImageDecoder::DecodeAndTruncate(tmp, dataset, frame, PixelFormat_SignedGrayscale16); + break; + + case ImageExtractionMode_Preview: + ok = DicomImageDecoder::DecodePreview(tmp, dataset, frame); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); } - else if (DecodePsmctRle1(privateContent, dataset)) - { - LOG(INFO) << "The PMSCT_RLE1 decoding has succeeded"; - Uint8* pixData = NULL; - if (privateContent.size() > 0) - pixData = reinterpret_cast(&privateContent[0]); - accessor.reset(new DicomIntegerPixelAccessor(m, pixData, privateContent.size())); - accessor->SetCurrentFrame(frame); - } - - if (accessor.get() == NULL) + + if (!ok) { throw OrthancException(ErrorCode_BadFileFormat); } - PixelFormat format; - bool supported = false; - - if (accessor->GetChannelCount() == 1) - { - switch (mode) - { - case ImageExtractionMode_Preview: - supported = true; - format = PixelFormat_Grayscale8; - break; - - case ImageExtractionMode_UInt8: - supported = true; - format = PixelFormat_Grayscale8; - break; - - case ImageExtractionMode_UInt16: - supported = true; - format = PixelFormat_Grayscale16; - break; - - case ImageExtractionMode_Int16: - supported = true; - format = PixelFormat_SignedGrayscale16; - break; - - default: - supported = false; - break; - } - } - else if (accessor->GetChannelCount() == 3) - { - switch (mode) - { - case ImageExtractionMode_Preview: - supported = true; - format = PixelFormat_RGB24; - break; - - default: - supported = false; - break; - } - } - - if (!supported) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - if (accessor.get() == NULL || - accessor->GetWidth() == 0 || - accessor->GetHeight() == 0) - { - PngWriter w; - w.WriteToMemory(result, 0, 0, 0, format, NULL); - } - else - { - switch (mode) - { - case ImageExtractionMode_Preview: - if (format == PixelFormat_Grayscale8) - ExtractPngImageGrayscalePreview(result, *accessor); - else - ExtractPngImageColorPreview(result, *accessor); - break; - - case ImageExtractionMode_UInt8: - ExtractPngImageTruncate(result, *accessor, format); - break; - - case ImageExtractionMode_UInt16: - ExtractPngImageTruncate(result, *accessor, format); - break; - - case ImageExtractionMode_Int16: - ExtractPngImageTruncate(result, *accessor, format); - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } + ImageAccessor accessor(tmp.GetAccessor()); + PngWriter writer; + writer.WriteToMemory(result, accessor); } diff -r a4d2be5154a9 -r 54d2c5df6760 OrthancServer/Internals/DicomImageDecoder.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Internals/DicomImageDecoder.cpp Tue Jun 10 17:28:04 2014 +0200 @@ -0,0 +1,681 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "DicomImageDecoder.h" + + +/*========================================================================= + + This file is based on portions of the following project + (cf. function "DecodePsmctRle1()"): + + Program: GDCM (Grassroots DICOM). A DICOM library + Module: http://gdcm.sourceforge.net/Copyright.html + + Copyright (c) 2006-2011 Mathieu Malaterre + Copyright (c) 1993-2005 CREATIS + (CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any + contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + =========================================================================*/ + + + +#include "../../Core/OrthancException.h" +#include "../../Core/ImageFormats/ImageProcessing.h" +#include "../../Core/ImageFormats/PngWriter.h" // TODO REMOVE THIS +#include "../../Core/DicomFormat/DicomIntegerPixelAccessor.h" +#include "../ToDcmtkBridge.h" +#include "../FromDcmtkBridge.h" + +#include + +#include + +#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 +#include +#include +#include +#endif + + +namespace Orthanc +{ + class DicomImageDecoder::ImageSource + { + private: + std::string psmct_; + std::auto_ptr slowAccessor_; + std::auto_ptr fastAccessor_; + + public: + void Setup(DcmDataset& dataset, + unsigned int frame) + { + psmct_.clear(); + slowAccessor_.reset(NULL); + fastAccessor_.reset(NULL); + + // See also: http://support.dcmtk.org/wiki/dcmtk/howto/accessing-compressed-data + + DicomMap m; + FromDcmtkBridge::Convert(m, dataset); + + /** + * Create an accessor to the raw values of the DICOM image. + **/ + + DcmElement* e; + if (dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), e).good() && + e != NULL) + { + Uint8* pixData = NULL; + if (e->getUint8Array(pixData) == EC_Normal) + { + slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, e->getLength())); + } + } + else if (DicomImageDecoder::DecodePsmctRle1(psmct_, dataset)) + { + LOG(INFO) << "The PMSCT_RLE1 decoding has succeeded"; + Uint8* pixData = NULL; + if (psmct_.size() > 0) + { + pixData = reinterpret_cast(&psmct_[0]); + } + + slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, psmct_.size())); + } + + if (slowAccessor_.get() == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + slowAccessor_->SetCurrentFrame(frame); + + + /** + * If possible, create a fast ImageAccessor to the image buffer. + **/ + + + } + + unsigned int GetWidth() const + { + assert(slowAccessor_.get() != NULL); + return slowAccessor_->GetInformation().GetWidth(); + } + + unsigned int GetHeight() const + { + assert(slowAccessor_.get() != NULL); + return slowAccessor_->GetInformation().GetHeight(); + } + + unsigned int GetChannelCount() const + { + assert(slowAccessor_.get() != NULL); + return slowAccessor_->GetInformation().GetChannelCount(); + } + + const DicomIntegerPixelAccessor& GetAccessor() const + { + assert(slowAccessor_.get() != NULL); + return *slowAccessor_; + } + + bool HasFastAccessor() const + { + return fastAccessor_.get() != NULL; + } + + const ImageAccessor& GetFastAccessor() const + { + assert(HasFastAccessor()); + return *fastAccessor_; + } + }; + + + static const DicomTag DICOM_TAG_CONTENT(0x07a1, 0x100a); + static const DicomTag DICOM_TAG_COMPRESSION_TYPE(0x07a1, 0x1011); + + bool DicomImageDecoder::IsPsmctRle1(DcmDataset& dataset) + { + DcmElement* e; + char* c; + + // Check whether the DICOM instance contains an image encoded with + // the PMSCT_RLE1 scheme. + if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_COMPRESSION_TYPE), e).good() || + e == NULL || + !e->isaString() || + !e->getString(c).good() || + c == NULL || + strcmp("PMSCT_RLE1", c)) + { + return false; + } + else + { + return true; + } + } + + + bool DicomImageDecoder::DecodePsmctRle1(std::string& output, + DcmDataset& dataset) + { + // Check whether the DICOM instance contains an image encoded with + // the PMSCT_RLE1 scheme. + if (!IsPsmctRle1(dataset)) + { + return false; + } + + // OK, this is a custom RLE encoding from Philips. Get the pixel + // data from the appropriate private DICOM tag. + Uint8* pixData = NULL; + DcmElement* e; + if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_CONTENT), e).good() || + e == NULL || + e->getUint8Array(pixData) != EC_Normal) + { + return false; + } + + // The "unsigned" below IS VERY IMPORTANT + const uint8_t* inbuffer = reinterpret_cast(pixData); + const size_t length = e->getLength(); + + /** + * The code below is an adaptation of a sample code for GDCM by + * Mathieu Malaterre (under a BSD license). + * http://gdcm.sourceforge.net/html/rle2img_8cxx-example.html + **/ + + // RLE pass + std::vector temp; + temp.reserve(length); + for (size_t i = 0; i < length; i++) + { + if (inbuffer[i] == 0xa5) + { + temp.push_back(inbuffer[i+2]); + for (uint8_t repeat = inbuffer[i + 1]; repeat != 0; repeat--) + { + temp.push_back(inbuffer[i+2]); + } + i += 2; + } + else + { + temp.push_back(inbuffer[i]); + } + } + + // Delta encoding pass + uint16_t delta = 0; + output.clear(); + output.reserve(temp.size()); + for (size_t i = 0; i < temp.size(); i++) + { + uint16_t value; + + if (temp[i] == 0x5a) + { + uint16_t v1 = temp[i + 1]; + uint16_t v2 = temp[i + 2]; + value = (v2 << 8) + v1; + i += 2; + } + else + { + value = delta + (int8_t) temp[i]; + } + + output.push_back(value & 0xff); + output.push_back(value >> 8); + delta = value; + } + + if (output.size() % 2) + { + output.resize(output.size() - 1); + } + + return true; + } + + + void DicomImageDecoder::SetupImageBuffer(ImageBuffer& target, + DcmDataset& dataset) + { + DicomMap m; + FromDcmtkBridge::Convert(m, dataset); + + DicomImageInformation info(m); + PixelFormat format; + + if (!info.ExtractPixelFormat(format)) + { + LOG(WARNING) << "Unsupported DICOM image: " << info.GetBitsStored() + << "bpp, " << info.GetChannelCount() << " channels, " + << (info.IsSigned() ? "signed" : "unsigned") + << (info.IsPlanar() ? ", planar" : ", non-planar"); + throw OrthancException(ErrorCode_NotImplemented); + } + + target.SetHeight(info.GetHeight()); + target.SetWidth(info.GetWidth()); + target.SetFormat(format); + } + + + bool DicomImageDecoder::IsJpegLossless(const DcmDataset& dataset) + { + // http://support.dcmtk.org/docs/dcxfer_8h-source.html + return (dataset.getOriginalXfer() == EXS_JPEGLSLossless || + dataset.getOriginalXfer() == EXS_JPEGLSLossy); + } + + + bool DicomImageDecoder::IsUncompressedImage(const DcmDataset& dataset) + { + // http://support.dcmtk.org/docs/dcxfer_8h-source.html + return (dataset.getOriginalXfer() == EXS_Unknown || + dataset.getOriginalXfer() == EXS_LittleEndianImplicit || + dataset.getOriginalXfer() == EXS_BigEndianImplicit || + dataset.getOriginalXfer() == EXS_LittleEndianExplicit || + dataset.getOriginalXfer() == EXS_BigEndianExplicit); + } + + + template + static void CopyPixels(ImageAccessor& target, + const DicomIntegerPixelAccessor& source) + { + const PixelType minValue = std::numeric_limits::min(); + const PixelType maxValue = std::numeric_limits::max(); + + for (unsigned int y = 0; y < source.GetInformation().GetHeight(); y++) + { + PixelType* pixel = reinterpret_cast(target.GetRow(y)); + for (unsigned int x = 0; x < source.GetInformation().GetWidth(); x++) + { + for (unsigned int c = 0; c < source.GetInformation().GetChannelCount(); c++, pixel++) + { + int32_t v = source.GetValue(x, y, c); + if (v < static_cast(minValue)) + { + *pixel = minValue; + } + else if (v > static_cast(maxValue)) + { + *pixel = maxValue; + } + else + { + *pixel = static_cast(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) + { + ImageSource source; + source.Setup(dataset, frame); + + + /** + * Resize the target image. + **/ + + SetupImageBuffer(target, dataset); + + if (source.GetWidth() != target.GetWidth() || + source.GetHeight() != target.GetHeight()) + { + throw OrthancException(ErrorCode_InternalError); + } + + + /** + * If the format of the DICOM buffer is natively supported, use a + * direct access to copy its values. + **/ + + ImageAccessor targetAccessor(target.GetAccessor()); + const DicomImageInformation& info = source.GetAccessor().GetInformation(); + + bool fastVersionSuccess = false; + PixelFormat sourceFormat; + if (!info.IsPlanar() && + info.ExtractPixelFormat(sourceFormat)) + { + try + { + ImageAccessor sourceImage; + sourceImage.AssignReadOnly(sourceFormat, + info.GetWidth(), + info.GetHeight(), + info.GetWidth() * GetBytesPerPixel(sourceFormat), + source.GetAccessor().GetPixelData()); + + ImageProcessing::Convert(targetAccessor, sourceImage); + ImageProcessing::ShiftRight(targetAccessor, info.GetShift()); + fastVersionSuccess = true; + } + catch (OrthancException&) + { + // Unsupported conversion, use the slow version + } + } + + + /** + * Slow version : loop over the DICOM buffer, storing its value + * into the target image. + **/ + + if (!fastVersionSuccess) + { + switch (target.GetFormat()) + { + case PixelFormat_RGB24: + case PixelFormat_RGBA32: + case PixelFormat_Grayscale8: + CopyPixels(targetAccessor, source.GetAccessor()); + break; + + case PixelFormat_Grayscale16: + CopyPixels(targetAccessor, source.GetAccessor()); + break; + + case PixelFormat_SignedGrayscale16: + CopyPixels(targetAccessor, source.GetAccessor()); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + } + + +#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 + void DicomImageDecoder::DecodeJpegLossless(ImageBuffer& target, + DcmDataset& dataset, + unsigned int frame) + { + if (!IsJpegLossless(dataset)) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + DcmElement *element = NULL; + if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), element).good()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + DcmPixelData& pixelData = dynamic_cast(*element); + DcmPixelSequence* pixelSequence = NULL; + if (!pixelData.getEncapsulatedRepresentation + (dataset.getOriginalXfer(), NULL, pixelSequence).good()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + SetupImageBuffer(target, dataset); + + ImageAccessor targetAccessor(target.GetAccessor()); + + /** + * The "DJLSLosslessDecoder" and "DJLSNearLosslessDecoder" in DCMTK + * are exactly the same, except for the "supportedTransferSyntax()" + * virtual function. + * http://support.dcmtk.org/docs/classDJLSDecoderBase.html + **/ + + DJLSLosslessDecoder decoder; DJLSCodecParameter parameters; + //DJLSNearLosslessDecoder decoder; DJLSCodecParameter parameters; + + Uint32 startFragment = 0; // Default + OFString decompressedColorModel; // Out + DJ_RPLossless representationParameter; + OFCondition c = decoder.decodeFrame(&representationParameter, pixelSequence, ¶meters, + &dataset, frame, startFragment, targetAccessor.GetBuffer(), + targetAccessor.GetSize(), decompressedColorModel); + + if (!c.good()) + { + throw OrthancException(ErrorCode_InternalError); + } + } +#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 + + +#if ORTHANC_JPEG_ENABLED == 1 + // TODO Implement this part to speed up JPEG decompression +#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 converted(dynamic_cast(dataset.clone())); + converted->chooseRepresentation(EXS_LittleEndianExplicit, NULL); + + if (converted->canWriteXfer(EXS_LittleEndianExplicit)) + { + DecodeUncompressedImageInternal(target, *converted, frame); + return true; + } + } + + return false; + } + + + bool DicomImageDecoder::DecodeAndTruncate(ImageBuffer& target, + DcmDataset& dataset, + unsigned int frame, + PixelFormat format) + { + // TODO Special case for uncompressed images + + ImageBuffer source; + if (!Decode(source, dataset, frame)) + { + return false; + } + + if (source.GetFormat() == format) + { + // No conversion is required, return the temporary image + target.AcquireOwnership(source); + return true; + } + + target.SetFormat(format); + target.SetWidth(source.GetWidth()); + target.SetHeight(source.GetHeight()); + + ImageAccessor targetAccessor(target.GetAccessor()); + ImageAccessor sourceAccessor(source.GetAccessor()); + ImageProcessing::Convert(targetAccessor, sourceAccessor); + + return true; + } + + + bool DicomImageDecoder::DecodePreview(ImageBuffer& target, + DcmDataset& dataset, + unsigned int frame) + { + // TODO Special case for uncompressed images + + ImageBuffer source; + if (!Decode(source, dataset, frame)) + { + return false; + } + + switch (source.GetFormat()) + { + case PixelFormat_RGB24: + { + // Directly return color images (RGB) + target.AcquireOwnership(source); + return true; + } + + case PixelFormat_Grayscale8: + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + { + // Grayscale image: Stretch its dynamics to the [0,255] range + target.SetFormat(PixelFormat_Grayscale8); + target.SetWidth(source.GetWidth()); + target.SetHeight(source.GetHeight()); + + ImageAccessor targetAccessor(target.GetAccessor()); + ImageAccessor sourceAccessor(source.GetAccessor()); + + int64_t a, b; + ImageProcessing::GetMinMaxValue(a, b, sourceAccessor); + + if (a == b) + { + ImageProcessing::Set(targetAccessor, 0); + } + else + { + ImageProcessing::ShiftScale(sourceAccessor, -a, 255.0f / static_cast(b - a)); + + if (source.GetFormat() == PixelFormat_Grayscale8) + { + target.AcquireOwnership(source); + } + else + { + ImageProcessing::Convert(targetAccessor, sourceAccessor); + } + } + + return true; + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } +} diff -r a4d2be5154a9 -r 54d2c5df6760 OrthancServer/Internals/DicomImageDecoder.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Internals/DicomImageDecoder.h Tue Jun 10 17:28:04 2014 +0200 @@ -0,0 +1,87 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include + +#include "../../Core/ImageFormats/ImageBuffer.h" + +namespace Orthanc +{ + class DicomImageDecoder + { + private: + class ImageSource; + + static void DecodeUncompressedImageInternal(ImageBuffer& target, + DcmDataset& dataset, + unsigned int frame); + + static bool IsPsmctRle1(DcmDataset& dataset); + + static void SetupImageBuffer(ImageBuffer& target, + DcmDataset& dataset); + + public: // TODO SWITCH TO PRIVATE + static bool DecodePsmctRle1(std::string& output, + 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, + unsigned int frame); +#endif + + static bool Decode(ImageBuffer& target, + DcmDataset& dataset, + unsigned int frame); + + static bool DecodeAndTruncate(ImageBuffer& target, + DcmDataset& dataset, + unsigned int frame, + PixelFormat format); + + static bool DecodePreview(ImageBuffer& target, + DcmDataset& dataset, + unsigned int frame); + }; +} diff -r a4d2be5154a9 -r 54d2c5df6760 OrthancServer/OrthancInitialization.cpp --- a/OrthancServer/OrthancInitialization.cpp Mon Jun 02 13:01:02 2014 +0200 +++ b/OrthancServer/OrthancInitialization.cpp Tue Jun 10 17:28:04 2014 +0200 @@ -45,6 +45,17 @@ #include #include + +#if ORTHANC_JPEG_ENABLED == 1 +#include +#endif + + +#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 +#include +#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 } diff -r a4d2be5154a9 -r 54d2c5df6760 OrthancServer/ParsedDicomFile.cpp --- a/OrthancServer/ParsedDicomFile.cpp Mon Jun 02 13:01:02 2014 +0200 +++ b/OrthancServer/ParsedDicomFile.cpp Tue Jun 10 17:28:04 2014 +0200 @@ -999,15 +999,15 @@ DicomIntegerPixelAccessor& accessor, PixelFormat format) { - assert(accessor.GetChannelCount() == 1); + assert(accessor.GetInformation().GetChannelCount() == 1); PngWriter w; - std::vector image(accessor.GetWidth() * accessor.GetHeight(), 0); + std::vector image(accessor.GetInformation().GetWidth() * accessor.GetInformation().GetHeight(), 0); T* pixel = &image[0]; - for (unsigned int y = 0; y < accessor.GetHeight(); y++) + for (unsigned int y = 0; y < accessor.GetInformation().GetHeight(); y++) { - for (unsigned int x = 0; x < accessor.GetWidth(); x++, pixel++) + for (unsigned int x = 0; x < accessor.GetInformation().GetWidth(); x++, pixel++) { int32_t v = accessor.GetValue(x, y); if (v < static_cast(std::numeric_limits::min())) @@ -1019,8 +1019,8 @@ } } - w.WriteToMemory(result, accessor.GetWidth(), accessor.GetHeight(), - accessor.GetWidth() * sizeof(T), format, &image[0]); + w.WriteToMemory(result, accessor.GetInformation().GetWidth(), accessor.GetInformation().GetHeight(), + accessor.GetInformation().GetWidth() * sizeof(T), format, &image[0]); } diff -r a4d2be5154a9 -r 54d2c5df6760 Resources/CMake/DcmtkConfiguration.cmake --- a/Resources/CMake/DcmtkConfiguration.cmake Mon Jun 02 13:01:02 2014 +0200 +++ b/Resources/CMake/DcmtkConfiguration.cmake Tue Jun 10 17:28:04 2014 +0200 @@ -1,151 +1,187 @@ -# Lookup for DICOM dictionaries, if none is specified by the user -if (DCMTK_DICTIONARY_DIR STREQUAL "") - find_path(DCMTK_DICTIONARY_DIR_AUTO dicom.dic - /usr/share/dcmtk - /usr/share/libdcmtk2 - ) - - message("Autodetected path to the DICOM dictionaries: ${DCMTK_DICTIONARY_DIR_AUTO}") - add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR_AUTO}") -else() - add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR}") -endif() - - - -if (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK) - SET(DCMTK_VERSION_NUMBER 360) - SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0) - DownloadPackage( - "219ad631b82031806147e4abbfba4fa4" - "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/dcmtk-3.6.0.zip" - "${DCMTK_SOURCES_DIR}") - - IF(CMAKE_CROSSCOMPILING) - SET(C_CHAR_UNSIGNED 1 CACHE INTERNAL "Whether char is unsigned.") - ENDIF() - SET(DCMTK_SOURCE_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0) - include(${DCMTK_SOURCES_DIR}/CMake/CheckFunctionWithHeaderExists.cmake) - include(${DCMTK_SOURCES_DIR}/CMake/GenerateDCMTKConfigure.cmake) - - if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") - set(HAVE_SSTREAM 1) - set(HAVE_PROTOTYPE_BZERO 1) - set(HAVE_PROTOTYPE_GETHOSTNAME 1) - set(HAVE_PROTOTYPE_GETSOCKOPT 1) - set(HAVE_PROTOTYPE_SETSOCKOPT 1) - set(HAVE_PROTOTYPE_CONNECT 1) - set(HAVE_PROTOTYPE_BIND 1) - set(HAVE_PROTOTYPE_ACCEPT 1) - set(HAVE_PROTOTYPE_SETSOCKNAME 1) - set(HAVE_PROTOTYPE_GETSOCKNAME 1) - endif() - - CONFIGURE_FILE( - ${DCMTK_SOURCES_DIR}/CMake/osconfig.h.in - ${DCMTK_SOURCES_DIR}/config/include/dcmtk/config/osconfig.h) - - AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmnet/libsrc DCMTK_SOURCES) - AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmdata/libsrc DCMTK_SOURCES) - AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/ofstd/libsrc DCMTK_SOURCES) - - # Source for the logging facility of DCMTK - AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/oflog/libsrc DCMTK_SOURCES) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - list(REMOVE_ITEM DCMTK_SOURCES - ${DCMTK_SOURCES_DIR}/oflog/libsrc/windebap.cc - ${DCMTK_SOURCES_DIR}/oflog/libsrc/winsock.cc - ) - elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - list(REMOVE_ITEM DCMTK_SOURCES - ${DCMTK_SOURCES_DIR}/oflog/libsrc/unixsock.cc - ) - - if (${CMAKE_COMPILER_IS_GNUCXX}) - # This is a patch for MinGW64 - execute_process( - COMMAND patch -p0 -i ${CMAKE_SOURCE_DIR}/Resources/Patches/dcmtk-mingw64.patch - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - ) - endif() - - endif() - - list(REMOVE_ITEM DCMTK_SOURCES - ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdictbi.cc - ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdeftag.cc - ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdictbi.cc - ) - - # This fixes crashes related to the destruction of the DCMTK OFLogger - # http://support.dcmtk.org/docs-snapshot/file_macros.html - add_definitions( - -DLOG4CPLUS_DISABLE_FATAL=1 - -DDCMTK_VERSION_NUMBER=360 - ) - - include_directories( - #${DCMTK_SOURCES_DIR} - ${DCMTK_SOURCES_DIR}/config/include - ${DCMTK_SOURCES_DIR}/dcmnet/include - ${DCMTK_SOURCES_DIR}/ofstd/include - ${DCMTK_SOURCES_DIR}/oflog/include - ${DCMTK_SOURCES_DIR}/dcmdata/include - ) - - source_group(ThirdParty\\Dcmtk REGULAR_EXPRESSION ${DCMTK_SOURCES_DIR}/.*) - - set(DCMTK_BUNDLES_LOG4CPLUS 1) - - if (STANDALONE_BUILD) - add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=1) - else() - add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=0) - endif() - - set(DCMTK_DICTIONARIES - DICTIONARY_DICOM ${DCMTK_SOURCES_DIR}/dcmdata/data/dicom.dic - DICTIONARY_PRIVATE ${DCMTK_SOURCES_DIR}/dcmdata/data/private.dic - DICTIONARY_DICONDE ${DCMTK_SOURCES_DIR}/dcmdata/data/diconde.dic - ) - -else() - # The following line allows to manually add libraries at the - # command-line, which is necessary for Ubuntu/Debian packages - set(tmp "${DCMTK_LIBRARIES}") - include(FindDCMTK) - list(APPEND DCMTK_LIBRARIES "${tmp}") - - include_directories(${DCMTK_INCLUDE_DIR}) - link_libraries(${DCMTK_LIBRARIES}) - - add_definitions( - -DHAVE_CONFIG_H=1 - ) - - if (EXISTS "${DCMTK_DIR}/config/cfunix.h") - set(DCMTK_CONFIGURATION_FILE "${DCMTK_DIR}/config/cfunix.h") - elseif (EXISTS "${DCMTK_DIR}/config/osconfig.h") # This is for Arch Linux - set(DCMTK_CONFIGURATION_FILE "${DCMTK_DIR}/config/osconfig.h") - else() - message(FATAL_ERROR "Please install libdcmtk1-dev") - endif() - - # Autodetection of the version of DCMTK - file(STRINGS - "${DCMTK_CONFIGURATION_FILE}" - DCMTK_VERSION_NUMBER1 REGEX - ".*PACKAGE_VERSION .*") - - string(REGEX REPLACE - ".*PACKAGE_VERSION.*\"([0-9]*)\\.([0-9]*)\\.([0-9]*)\"$" - "\\1\\2\\3" - DCMTK_VERSION_NUMBER - ${DCMTK_VERSION_NUMBER1}) - - add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=0) - -endif() - -add_definitions(-DDCMTK_VERSION_NUMBER=${DCMTK_VERSION_NUMBER}) -message("DCMTK version: ${DCMTK_VERSION_NUMBER}") +# Lookup for DICOM dictionaries, if none is specified by the user +if (DCMTK_DICTIONARY_DIR STREQUAL "") + find_path(DCMTK_DICTIONARY_DIR_AUTO dicom.dic + /usr/share/dcmtk + /usr/share/libdcmtk2 + ) + + message("Autodetected path to the DICOM dictionaries: ${DCMTK_DICTIONARY_DIR_AUTO}") + add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR_AUTO}") +else() + add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR}") +endif() + + + +if (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK) + SET(DCMTK_VERSION_NUMBER 360) + SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0) + DownloadPackage( + "219ad631b82031806147e4abbfba4fa4" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/dcmtk-3.6.0.zip" + "${DCMTK_SOURCES_DIR}") + + IF(CMAKE_CROSSCOMPILING) + SET(C_CHAR_UNSIGNED 1 CACHE INTERNAL "Whether char is unsigned.") + ENDIF() + SET(DCMTK_SOURCE_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0) + include(${DCMTK_SOURCES_DIR}/CMake/CheckFunctionWithHeaderExists.cmake) + include(${DCMTK_SOURCES_DIR}/CMake/GenerateDCMTKConfigure.cmake) + + if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") + set(HAVE_SSTREAM 1) + set(HAVE_PROTOTYPE_BZERO 1) + set(HAVE_PROTOTYPE_GETHOSTNAME 1) + set(HAVE_PROTOTYPE_GETSOCKOPT 1) + set(HAVE_PROTOTYPE_SETSOCKOPT 1) + set(HAVE_PROTOTYPE_CONNECT 1) + set(HAVE_PROTOTYPE_BIND 1) + set(HAVE_PROTOTYPE_ACCEPT 1) + set(HAVE_PROTOTYPE_SETSOCKNAME 1) + set(HAVE_PROTOTYPE_GETSOCKNAME 1) + endif() + + CONFIGURE_FILE( + ${DCMTK_SOURCES_DIR}/CMake/osconfig.h.in + ${DCMTK_SOURCES_DIR}/config/include/dcmtk/config/osconfig.h) + + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmnet/libsrc DCMTK_SOURCES) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmdata/libsrc DCMTK_SOURCES) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/ofstd/libsrc DCMTK_SOURCES) + + + if (ENABLE_JPEG) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc DCMTK_SOURCES) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libijg8 DCMTK_SOURCES) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libijg12 DCMTK_SOURCES) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libijg16 DCMTK_SOURCES) + include_directories( + ${DCMTK_SOURCES_DIR}/dcmjpeg/include + ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg8 + ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg12 + ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg16 + ${DCMTK_SOURCES_DIR}/dcmimgle/include + ) + list(REMOVE_ITEM DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/ddpiimpl.cc + ) + endif() + + + if (ENABLE_JPEG_LOSSLESS) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpls/libsrc DCMTK_SOURCES) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpls/libcharls DCMTK_SOURCES) + include_directories( + ${DCMTK_SOURCES_DIR}/dcmjpeg/include + ${DCMTK_SOURCES_DIR}/dcmjpls/include + ${DCMTK_SOURCES_DIR}/dcmjpls/libcharls + ) + list(REMOVE_ITEM DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djcodece.cc + ) + list(APPEND DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djrplol.cc + ) + endif() + + + # Source for the logging facility of DCMTK + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/oflog/libsrc DCMTK_SOURCES) + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + list(REMOVE_ITEM DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/oflog/libsrc/windebap.cc + ${DCMTK_SOURCES_DIR}/oflog/libsrc/winsock.cc + ) + elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + list(REMOVE_ITEM DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/oflog/libsrc/unixsock.cc + ) + + if (${CMAKE_COMPILER_IS_GNUCXX}) + # This is a patch for MinGW64 + execute_process( + COMMAND patch -p0 -i ${CMAKE_SOURCE_DIR}/Resources/Patches/dcmtk-mingw64.patch + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + endif() + + endif() + + list(REMOVE_ITEM DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdictbi.cc + ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdeftag.cc + ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdictbi.cc + ) + + # This fixes crashes related to the destruction of the DCMTK OFLogger + # http://support.dcmtk.org/docs-snapshot/file_macros.html + add_definitions( + -DLOG4CPLUS_DISABLE_FATAL=1 + -DDCMTK_VERSION_NUMBER=360 + ) + + include_directories( + #${DCMTK_SOURCES_DIR} + ${DCMTK_SOURCES_DIR}/config/include + ${DCMTK_SOURCES_DIR}/dcmnet/include + ${DCMTK_SOURCES_DIR}/ofstd/include + ${DCMTK_SOURCES_DIR}/oflog/include + ${DCMTK_SOURCES_DIR}/dcmdata/include + ) + + source_group(ThirdParty\\Dcmtk REGULAR_EXPRESSION ${DCMTK_SOURCES_DIR}/.*) + + set(DCMTK_BUNDLES_LOG4CPLUS 1) + + if (STANDALONE_BUILD) + add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=1) + else() + add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=0) + endif() + + set(DCMTK_DICTIONARIES + DICTIONARY_DICOM ${DCMTK_SOURCES_DIR}/dcmdata/data/dicom.dic + DICTIONARY_PRIVATE ${DCMTK_SOURCES_DIR}/dcmdata/data/private.dic + DICTIONARY_DICONDE ${DCMTK_SOURCES_DIR}/dcmdata/data/diconde.dic + ) + +else() + # The following line allows to manually add libraries at the + # command-line, which is necessary for Ubuntu/Debian packages + set(tmp "${DCMTK_LIBRARIES}") + include(FindDCMTK) + list(APPEND DCMTK_LIBRARIES "${tmp}") + + include_directories(${DCMTK_INCLUDE_DIR}) + link_libraries(${DCMTK_LIBRARIES}) + + add_definitions( + -DHAVE_CONFIG_H=1 + ) + + if (EXISTS "${DCMTK_DIR}/config/cfunix.h") + set(DCMTK_CONFIGURATION_FILE "${DCMTK_DIR}/config/cfunix.h") + elseif (EXISTS "${DCMTK_DIR}/config/osconfig.h") # This is for Arch Linux + set(DCMTK_CONFIGURATION_FILE "${DCMTK_DIR}/config/osconfig.h") + else() + message(FATAL_ERROR "Please install libdcmtk1-dev") + endif() + + # Autodetection of the version of DCMTK + file(STRINGS + "${DCMTK_CONFIGURATION_FILE}" + DCMTK_VERSION_NUMBER1 REGEX + ".*PACKAGE_VERSION .*") + + string(REGEX REPLACE + ".*PACKAGE_VERSION.*\"([0-9]*)\\.([0-9]*)\\.([0-9]*)\"$" + "\\1\\2\\3" + DCMTK_VERSION_NUMBER + ${DCMTK_VERSION_NUMBER1}) + + add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=0) + +endif() + +add_definitions(-DDCMTK_VERSION_NUMBER=${DCMTK_VERSION_NUMBER}) +message("DCMTK version: ${DCMTK_VERSION_NUMBER}") diff -r a4d2be5154a9 -r 54d2c5df6760 UnitTestsSources/ImageProcessingTests.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/ImageProcessingTests.cpp Tue Jun 10 17:28:04 2014 +0200 @@ -0,0 +1,78 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../Core/DicomFormat/DicomImageInformation.h" +#include "../Core/ImageFormats/ImageBuffer.h" +#include "../Core/ImageFormats/ImageProcessing.h" + +using namespace Orthanc; + + +TEST(DicomImageInformation, ExtractPixelFormat1) +{ + // Cardiac/MR* + DicomMap m; + m.SetValue(DICOM_TAG_ROWS, "24"); + m.SetValue(DICOM_TAG_COLUMNS, "16"); + m.SetValue(DICOM_TAG_BITS_ALLOCATED, "16"); + m.SetValue(DICOM_TAG_SAMPLES_PER_PIXEL, "1"); + m.SetValue(DICOM_TAG_BITS_STORED, "12"); + m.SetValue(DICOM_TAG_HIGH_BIT, "11"); + m.SetValue(DICOM_TAG_PIXEL_REPRESENTATION, "0"); + + DicomImageInformation info(m); + PixelFormat format; + ASSERT_TRUE(info.ExtractPixelFormat(format)); + ASSERT_EQ(PixelFormat_Grayscale16, format); +} + + +TEST(DicomImageInformation, ExtractPixelFormat2) +{ + // Delphine CT + DicomMap m; + m.SetValue(DICOM_TAG_ROWS, "24"); + m.SetValue(DICOM_TAG_COLUMNS, "16"); + m.SetValue(DICOM_TAG_BITS_ALLOCATED, "16"); + m.SetValue(DICOM_TAG_SAMPLES_PER_PIXEL, "1"); + m.SetValue(DICOM_TAG_BITS_STORED, "16"); + m.SetValue(DICOM_TAG_HIGH_BIT, "15"); + m.SetValue(DICOM_TAG_PIXEL_REPRESENTATION, "1"); + + DicomImageInformation info(m); + PixelFormat format; + ASSERT_TRUE(info.ExtractPixelFormat(format)); + ASSERT_EQ(PixelFormat_SignedGrayscale16, format); +} diff -r a4d2be5154a9 -r 54d2c5df6760 UnitTestsSources/JpegLossless.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/JpegLossless.cpp Tue Jun 10 17:28:04 2014 +0200 @@ -0,0 +1,112 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../OrthancServer/Internals/DicomImageDecoder.h" + +#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 + +#include + +#include "../OrthancServer/ParsedDicomFile.h" +#include "../Core/OrthancException.h" +#include "../Core/ImageFormats/ImageBuffer.h" +#include "../Core/ImageFormats/PngWriter.h" + +using namespace Orthanc; + + +TEST(JpegLossless, Basic) +{ +#if 0 + // Fallback + + std::string s; + Toolbox::ReadFile(s, "IM-0001-1001-0001.dcm"); + + ParsedDicomFile parsed(s); + DcmFileFormat& dicom = *reinterpret_cast(parsed.GetDcmtkObject()); + + DcmDataset* dataset = dicom.getDataset(); + + dataset->chooseRepresentation(EXS_LittleEndianExplicit, NULL); + + if (dataset->canWriteXfer(EXS_LittleEndianExplicit)) + { + printf("ICI\n"); + + parsed.SaveToFile("tutu.dcm"); + + // decompress data set if compressed + dataset->chooseRepresentation(EXS_LittleEndianExplicit, NULL); + + DcmXfer original_xfer(dataset->getOriginalXfer()); + std::cout << original_xfer.getXferName() << std::endl; + + FromDcmtkBridge::ExtractPngImage(s, *dataset, 1, ImageExtractionMode_Preview); + //fileformat.saveFile("test_decompressed.dcm", EXS_LittleEndianExplicit); + } +#else + DcmFileFormat fileformat; + //ASSERT_TRUE(fileformat.loadFile("IM-0001-1001-0001.dcm").good()); + //ASSERT_TRUE(fileformat.loadFile("tata.dcm").good()); + ASSERT_TRUE(fileformat.loadFile("RG2_JPLY").good()); + + DcmDataset& dataset = *fileformat.getDataset(); + + //ASSERT_TRUE(DicomImageDecoder::IsJpegLossless(dataset)); + + ImageBuffer image; + //DicomImageDecoder::DecodeJpegLossless(image, dataset, 0); + DicomImageDecoder::Decode(image, dataset, 0); + + ImageAccessor accessor(image.GetAccessor()); + + for (unsigned int y = 0; y < accessor.GetHeight(); y++) + { + int16_t *p = reinterpret_cast(accessor.GetRow(y)); + for (unsigned int x = 0; x < accessor.GetWidth(); x++, p ++) + { + if (*p < 0) + *p = 0; + } + } + + PngWriter w; + w.WriteToFile("tata.png", accessor); +#endif +} + + +#endif