# HG changeset patch # User Sebastien Jodogne # Date 1403690208 -7200 # Node ID 427a1f996b7b8af7ce417e5d29b07c2fc449f998 # Parent aabc3b4308900aa7f05cfc29fe21c1e0c36c64ff# Parent b3f6fb1130cd60680e954fe055ce74214b18bf57 integration mainline -> templating diff -r aabc3b430890 -r 427a1f996b7b CMakeLists.txt --- a/CMakeLists.txt Mon Jun 02 13:24:44 2014 +0200 +++ b/CMakeLists.txt Wed Jun 25 11:56:48 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,9 @@ UnitTestsSources/Lua.cpp UnitTestsSources/MultiThreading.cpp UnitTestsSources/UnitTestsMain.cpp + UnitTestsSources/ImageProcessingTests.cpp + UnitTestsSources/JpegLossless.cpp + UnitTestsSources/Plustache.cpp ) @@ -203,6 +211,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 @@ -290,7 +312,7 @@ OrthancServer/main.cpp ) -target_link_libraries(Orthanc ServerLibrary CoreLibrary) +target_link_libraries(Orthanc ServerLibrary CoreLibrary ${STATIC_LUA} ${STATIC_GOOGLE_LOG}) if (${OPENSSL_SOURCES_LENGTH} GREATER 0) target_link_libraries(Orthanc OpenSSL) @@ -319,7 +341,7 @@ ${GTEST_SOURCES} ${ORTHANC_UNIT_TESTS_SOURCES} ) -target_link_libraries(UnitTests ServerLibrary CoreLibrary) +target_link_libraries(UnitTests ServerLibrary CoreLibrary ${STATIC_LUA} ${STATIC_GOOGLE_LOG}) if (${OPENSSL_SOURCES_LENGTH} GREATER 0) target_link_libraries(UnitTests OpenSSL) @@ -366,14 +388,17 @@ add_library(OrthancClient SHARED ${ORTHANC_ROOT}/OrthancCppClient/OrthancCppClient.cpp + ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/SharedLibrary.cpp ${ORTHANC_ROOT}/Resources/md5/md5.c ${ORTHANC_ROOT}/Resources/base64/base64.cpp ${ORTHANC_CPP_CLIENT_AUX} ${THIRD_PARTY_SOURCES} ${CURL_SOURCES} + ${GOOGLE_LOG_SOURCES} ) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") set_target_properties(OrthancClient PROPERTIES LINK_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined -Wl,--as-needed -Wl,--version-script=${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/Laaw/VersionScript.map" ) @@ -388,6 +413,10 @@ ) endif() + elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + # TODO + target_link_libraries(OrthancClient pthread) + else() message(FATAL_ERROR "Support your platform here") endif() diff -r aabc3b430890 -r 427a1f996b7b Core/ChunkedBuffer.cpp --- a/Core/ChunkedBuffer.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/Core/ChunkedBuffer.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -65,6 +65,15 @@ } + void ChunkedBuffer::AddChunk(const std::string& chunk) + { + if (chunk.size() > 0) + { + AddChunk(&chunk[0], chunk.size()); + } + } + + void ChunkedBuffer::Flatten(std::string& result) { result.resize(numBytes_); diff -r aabc3b430890 -r 427a1f996b7b Core/ChunkedBuffer.h --- a/Core/ChunkedBuffer.h Mon Jun 02 13:24:44 2014 +0200 +++ b/Core/ChunkedBuffer.h Wed Jun 25 11:56:48 2014 +0200 @@ -64,6 +64,8 @@ void AddChunk(const char* chunkData, size_t chunkSize); + void AddChunk(const std::string& chunk); + void Flatten(std::string& result); }; } diff -r aabc3b430890 -r 427a1f996b7b Core/DicomFormat/DicomImageInformation.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomFormat/DicomImageInformation.cpp Wed Jun 25 11:56:48 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 aabc3b430890 -r 427a1f996b7b Core/DicomFormat/DicomImageInformation.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomFormat/DicomImageInformation.h Wed Jun 25 11:56:48 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 aabc3b430890 -r 427a1f996b7b Core/DicomFormat/DicomIntegerPixelAccessor.cpp --- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Wed Jun 25 11:56:48 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 aabc3b430890 -r 427a1f996b7b Core/DicomFormat/DicomIntegerPixelAccessor.h --- a/Core/DicomFormat/DicomIntegerPixelAccessor.h Mon Jun 02 13:24:44 2014 +0200 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.h Wed Jun 25 11:56:48 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 aabc3b430890 -r 427a1f996b7b Core/Enumerations.h --- a/Core/Enumerations.h Mon Jun 02 13:24:44 2014 +0200 +++ b/Core/Enumerations.h Wed Jun 25 11:56:48 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 aabc3b430890 -r 427a1f996b7b Core/ImageFormats/ImageAccessor.cpp --- a/Core/ImageFormats/ImageAccessor.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/Core/ImageFormats/ImageAccessor.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -34,15 +34,77 @@ #include "ImageAccessor.h" #include "../OrthancException.h" +#include "../ChunkedBuffer.h" #include +#include +#include +#include namespace Orthanc { + template + static void ToMatlabStringInternal(ChunkedBuffer& target, + const ImageAccessor& source) + { + target.AddChunk("double([ "); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + const PixelType* p = reinterpret_cast(source.GetConstRow(y)); + + std::string s; + if (y > 0) + { + s = "; "; + } + + s.reserve(source.GetWidth() * 8); + + for (unsigned int x = 0; x < source.GetWidth(); x++, p++) + { + s += boost::lexical_cast(static_cast(*p)) + " "; + } + + target.AddChunk(s); + } + + target.AddChunk("])"); + } + + + static void RGB24ToMatlabString(ChunkedBuffer& target, + const ImageAccessor& source) + { + assert(source.GetFormat() == PixelFormat_RGB24); + + target.AddChunk("double(permute(reshape([ "); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + const uint8_t* p = reinterpret_cast(source.GetConstRow(y)); + + std::string s; + s.reserve(source.GetWidth() * 3 * 8); + + for (unsigned int x = 0; x < 3 * source.GetWidth(); x++, p++) + { + s += boost::lexical_cast(static_cast(*p)) + " "; + } + + target.AddChunk(s); + } + + target.AddChunk("], [ 3 " + boost::lexical_cast(source.GetHeight()) + + " " + boost::lexical_cast(source.GetWidth()) + " ]), [ 3 2 1 ]))"); + } + + void* ImageAccessor::GetBuffer() const { if (readOnly_) { + LOG(ERROR) << "Trying to write on a read-only image"; throw OrthancException(ErrorCode_ReadOnly); } @@ -67,6 +129,7 @@ { if (readOnly_) { + LOG(ERROR) << "Trying to write on a read-only image"; throw OrthancException(ErrorCode_ReadOnly); } @@ -104,6 +167,8 @@ height_ = height; pitch_ = pitch; buffer_ = const_cast(buffer); + + assert(GetBytesPerPixel(format_) * width_ <= pitch_); } @@ -119,5 +184,38 @@ height_ = height; pitch_ = pitch; buffer_ = buffer; + + assert(GetBytesPerPixel(format_) * width_ <= pitch_); } + + + void ImageAccessor::ToMatlabString(std::string& target) const + { + ChunkedBuffer buffer; + + switch (GetFormat()) + { + case PixelFormat_Grayscale8: + ToMatlabStringInternal(buffer, *this); + break; + + case PixelFormat_Grayscale16: + ToMatlabStringInternal(buffer, *this); + break; + + case PixelFormat_SignedGrayscale16: + ToMatlabStringInternal(buffer, *this); + break; + + case PixelFormat_RGB24: + RGB24ToMatlabString(buffer, *this); + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + + buffer.Flatten(target); + } + } diff -r aabc3b430890 -r 427a1f996b7b Core/ImageFormats/ImageAccessor.h --- a/Core/ImageFormats/ImageAccessor.h Mon Jun 02 13:24:44 2014 +0200 +++ b/Core/ImageFormats/ImageAccessor.h Wed Jun 25 11:56:48 2014 +0200 @@ -77,6 +77,11 @@ return pitch_; } + unsigned int GetSize() const + { + return GetHeight() * GetPitch(); + } + const void* GetConstBuffer() const { return buffer_; @@ -101,5 +106,7 @@ unsigned int height, unsigned int pitch, void *buffer); + + void ToMatlabString(std::string& target) const; }; } diff -r aabc3b430890 -r 427a1f996b7b Core/ImageFormats/ImageBuffer.cpp --- a/Core/ImageFormats/ImageBuffer.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/Core/ImageFormats/ImageBuffer.cpp Wed Jun 25 11:56:48 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 aabc3b430890 -r 427a1f996b7b Core/ImageFormats/ImageBuffer.h --- a/Core/ImageFormats/ImageBuffer.h Mon Jun 02 13:24:44 2014 +0200 +++ b/Core/ImageFormats/ImageBuffer.h Wed Jun 25 11:56:48 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 aabc3b430890 -r 427a1f996b7b Core/ImageFormats/ImageProcessing.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/ImageFormats/ImageProcessing.cpp Wed Jun 25 11:56:48 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 = std::numeric_limits::max(); + } + else if (v < minValue) + { + *p = std::numeric_limits::min(); + } + 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 = std::numeric_limits::max(); + } + else if (v < minValue) + { + *p = std::numeric_limits::min(); + } + 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 aabc3b430890 -r 427a1f996b7b Core/ImageFormats/ImageProcessing.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/ImageFormats/ImageProcessing.h Wed Jun 25 11:56:48 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 aabc3b430890 -r 427a1f996b7b Core/ImageFormats/PngWriter.h --- a/Core/ImageFormats/PngWriter.h Mon Jun 02 13:24:44 2014 +0200 +++ b/Core/ImageFormats/PngWriter.h Wed Jun 25 11:56:48 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 aabc3b430890 -r 427a1f996b7b Core/MultiThreading/Mutex.cpp --- a/Core/MultiThreading/Mutex.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/Core/MultiThreading/Mutex.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -37,7 +37,7 @@ #if defined(_WIN32) #include -#elif defined(__linux) +#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__APPLE__) #include #else #error Support your platform here @@ -75,7 +75,7 @@ } -#elif defined(__linux) +#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__APPLE__) struct Mutex::PImpl { diff -r aabc3b430890 -r 427a1f996b7b Core/MultiThreading/ReaderWriterLock.h --- a/Core/MultiThreading/ReaderWriterLock.h Mon Jun 02 13:24:44 2014 +0200 +++ b/Core/MultiThreading/ReaderWriterLock.h Wed Jun 25 11:56:48 2014 +0200 @@ -34,9 +34,11 @@ #include "ILockable.h" +#include + namespace Orthanc { - class ReaderWriterLock + class ReaderWriterLock : public boost::noncopyable { private: struct PImpl; diff -r aabc3b430890 -r 427a1f996b7b Core/OrthancException.cpp --- a/Core/OrthancException.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/Core/OrthancException.cpp Wed Jun 25 11:56:48 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 aabc3b430890 -r 427a1f996b7b Core/Toolbox.cpp --- a/Core/Toolbox.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/Core/Toolbox.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -54,17 +54,17 @@ #include /* PATH_MAX */ #endif -#if defined(__linux) +#if defined(__linux) || defined(__FreeBSD_kernel__) #include /* PATH_MAX */ #include #include #endif -#if BOOST_HAS_LOCALE == 1 +#if BOOST_HAS_LOCALE != 1 +#error Since version 0.7.6, Orthanc entirely relies on boost::locale +#endif + #include -#else -#include -#endif #include "../Resources/md5/md5.h" #include "../Resources/base64/base64.h" @@ -74,68 +74,11 @@ // Patch for the missing "_strtoll" symbol when compiling with Visual Studio extern "C" { -int64_t _strtoi64(const char *nptr, char **endptr, int base); -int64_t strtoll(const char *nptr, char **endptr, int base) -{ - return _strtoi64(nptr, endptr, base); -} -} -#endif - - -#if BOOST_HAS_LOCALE == 0 -namespace -{ - class IconvRabi + int64_t _strtoi64(const char *nptr, char **endptr, int base); + int64_t strtoll(const char *nptr, char **endptr, int base) { - private: - iconv_t context_; - - public: - IconvRabi(const char* tocode, const char* fromcode) - { - context_ = iconv_open(tocode, fromcode); - if (!context_) - { - throw Orthanc::OrthancException("Unknown code page"); - } - } - - ~IconvRabi() - { - iconv_close(context_); - } - - std::string Convert(const std::string& source) - { - if (source.size() == 0) - { - return ""; - } - - std::string result; - char* sourcePos = const_cast(&source[0]); - size_t sourceLeft = source.size(); - - std::vector storage(source.size() + 10); - - while (sourceLeft > 0) - { - char* tmp = &storage[0]; - size_t outputLeft = storage.size(); - size_t err = iconv(context_, &sourcePos, &sourceLeft, &tmp, &outputLeft); - if (err < 0) - { - throw Orthanc::OrthancException("Bad character in sequence"); - } - - size_t count = storage.size() - outputLeft; - result += std::string(&storage[0], count); - } - - return result; - } - }; + return _strtoi64(nptr, endptr, base); + } } #endif @@ -162,7 +105,7 @@ { #if defined(_WIN32) ::Sleep(static_cast(microSeconds / static_cast(1000))); -#elif defined(__linux) +#elif defined(__linux) || defined(__APPLE__) || defined(__FreeBSD_kernel__) usleep(microSeconds); #else #error Support your platform here @@ -515,7 +458,7 @@ return std::string(&buffer[0]); } -#elif defined(__linux) +#elif defined(__linux) || defined(__FreeBSD_kernel__) std::string Toolbox::GetPathToExecutable() { std::vector buffer(PATH_MAX + 1); @@ -554,7 +497,6 @@ std::string Toolbox::ConvertToUtf8(const std::string& source, const char* fromEncoding) { -#if BOOST_HAS_LOCALE == 1 try { return boost::locale::conv::to_utf(source, fromEncoding); @@ -564,17 +506,6 @@ // Bad input string or bad encoding return ConvertToAscii(source); } -#else - IconvRabi iconv("UTF-8", fromEncoding); - try - { - return iconv.Convert(source); - } - catch (OrthancException) - { - return ConvertToAscii(source); - } -#endif } @@ -582,7 +513,7 @@ { std::string result; - result.reserve(source.size()); + result.reserve(source.size() + 1); for (size_t i = 0; i < source.size(); i++) { if (source[i] < 128 && source[i] >= 0 && !iscntrl(source[i])) diff -r aabc3b430890 -r 427a1f996b7b DarwinCompilation.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DarwinCompilation.txt Wed Jun 25 11:56:48 2014 +0200 @@ -0,0 +1,52 @@ +This file is a complement to "INSTALL", which contains instructions +that are specific to Mac OS X (Darwin). + + +Static linking for OS X using XCode +=================================== + +The most simple way of building Orthanc under OS X consists in +statically linking against all the third-party dependencies. In this +case, no package manager such as Homebrew or MacPorts is required. +The build tool (CMake) will download the sources of all the required +packages and automatically compile them. + + +Prerequisites +------------- + +1) XCode must be installed. + +2) CMake must be installed (http://www.cmake.org/). + +3) It is assumed that Orthanc source code is placed in the folder + "~/Orthanc" and that the binaries will be compiled to + "~/OrthancBuild". + + +Prepare the build with CMake +---------------------------- + +# cd ~/OrthancBuild +# cmake -GXcode -DCMAKE_OSX_DEPLOYMENT_TARGET=10.8 -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON .. + +NB: Adapt the value of "CMAKE_OSX_DEPLOYMENT_TARGET" with respect to +your version of XCode. + + +Build the Debug version of Orthanc +---------------------------------- + +# xcodebuild +# ./Debug/UnitTests + +The binaries of Orthanc are located at "~/OrthancBuild/Debug/Orthanc". + + +Build the Release version of Orthanc +------------------------------------ + +# xcodebuild -configuration Release +# ./Debug/UnitTests + +The binaries of Orthanc are located at "~/OrthancBuild/Release/Orthanc". diff -r aabc3b430890 -r 427a1f996b7b INSTALL --- a/INSTALL Mon Jun 02 13:24:44 2014 +0200 +++ b/INSTALL Wed Jun 25 11:56:48 2014 +0200 @@ -47,6 +47,13 @@ +Native OS X Compilation +----------------------- + +See the file "DarwinCompilation.txt". + + + Native Windows build with Microsoft Visual Studio 2005 ------------------------------------------------------ @@ -71,7 +78,7 @@ the following command: # cd ~/OrthancBuild -# cmake -DCMAKE_TOOLCHAIN_FILE=~/Orthanc/Resources/MinGWToolchain.cmake -DSTANDALONE_BUILD=ON -DCMAKE_BUILD_TYPE=Debug ~/Orthanc +# cmake -DCMAKE_TOOLCHAIN_FILE=~/Orthanc/Resources/MinGWToolchain.cmake -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DCMAKE_BUILD_TYPE=Debug ~/Orthanc # make diff -r aabc3b430890 -r 427a1f996b7b LinuxCompilation.txt --- a/LinuxCompilation.txt Mon Jun 02 13:24:44 2014 +0200 +++ b/LinuxCompilation.txt Wed Jun 25 11:56:48 2014 +0200 @@ -12,6 +12,10 @@ automatically compile them. This process should work on all the Linux distributions. +We make the assumption that Orthanc source code is placed in the +folder "~/Orthanc" and that the binaries will be compiled to +"~/OrthancBuild". + To build binaries with debug information: @@ -73,6 +77,8 @@ -DUSE_SYSTEM_DCMTK=OFF \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_SYSTEM_JSONCPP=OFF \ + -DENABLE_JPEG=OFF \ + -DENABLE_JPEG_LOSSLESS=OFF \ ~/Orthanc @@ -89,6 +95,8 @@ -DUSE_SYSTEM_GOOGLE_LOG=OFF \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DENABLE_JPEG=OFF \ + -DENABLE_JPEG_LOSSLESS=OFF \ ~/Orthanc @@ -104,6 +112,8 @@ # cmake -DALLOW_DOWNLOADS=ON \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DENABLE_JPEG=OFF \ + -DENABLE_JPEG_LOSSLESS=OFF \ ~/Orthanc Note: Have also a look at the official package: @@ -124,6 +134,8 @@ -DUSE_SYSTEM_JSONCPP=OFF \ -DUSE_SYSTEM_GOOGLE_LOG=OFF \ -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DENABLE_JPEG=OFF \ + -DENABLE_JPEG_LOSSLESS=OFF \ ~/Orthanc @@ -134,13 +146,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 @@ -157,6 +183,8 @@ -DALLOW_DOWNLOADS=ON \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DENABLE_JPEG=OFF \ + -DENABLE_JPEG_LOSSLESS=OFF \ ~/Orthanc @@ -169,8 +197,10 @@ gtest-devel libpng-devel libsqlite3x-devel libuuid-devel \ mongoose-devel openssl-devel jsoncpp-devel lua-devel -# cmake ~/Orthanc - +# cmake -DENABLE_JPEG=OFF \ + -DENABLE_JPEG_LOSSLESS=OFF \ + ~/Orthanc + Note: Have also a look at the official package: http://pkgs.fedoraproject.org/cgit/orthanc.git/tree/?h=f18 diff -r aabc3b430890 -r 427a1f996b7b NEWS --- a/NEWS Mon Jun 02 13:24:44 2014 +0200 +++ b/NEWS Wed Jun 25 11:56:48 2014 +0200 @@ -1,6 +1,16 @@ Pending changes in the mainline =============================== +* Official support of OS X (Darwin) +* Options to limit the number of results for an incoming C-FIND query +* Support of kFreeBSD + + +Version 0.7.6 (2014/06/11) +========================== + +* Support of JPEG and JPEG-LS decompression +* Download DICOM images as Matlab/Octave arrays * Precompiled headers for Microsoft Visual Studio diff -r aabc3b430890 -r 427a1f996b7b OrthancCppClient/OrthancCppClient.cpp --- a/OrthancCppClient/OrthancCppClient.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancCppClient/OrthancCppClient.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -35,6 +35,7 @@ * avoid problems with precompiled headers. **/ +#include "../Core/ChunkedBuffer.cpp" #include "../Core/Enumerations.cpp" #include "../Core/HttpClient.cpp" #include "../Core/ImageFormats/ImageAccessor.cpp" diff -r aabc3b430890 -r 427a1f996b7b OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp --- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -1486,12 +1486,12 @@ LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFileVersion() { - return "0.7.0.5"; + return "0.7.0.6"; } LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFullVersion() { - return "0.7.5"; + return "0.7.6"; } LAAW_EXPORT_DLL_API void LAAW_CALL_CONVENTION LAAW_EXTERNC_FreeString(char* str) diff -r aabc3b430890 -r 427a1f996b7b OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h --- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h Wed Jun 25 11:56:48 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 aabc3b430890 -r 427a1f996b7b OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc --- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc Wed Jun 25 11:56:48 2014 +0200 @@ -1,7 +1,7 @@ #include VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,7,0,5 + FILEVERSION 0,7,0,6 PRODUCTVERSION 0,7,0,0 FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_DLL @@ -10,10 +10,10 @@ BEGIN BLOCK "040904E4" BEGIN - VALUE "Comments", "Release 0.7.5" + VALUE "Comments", "Release 0.7.6" VALUE "CompanyName", "CHU of Liege" VALUE "FileDescription", "Native client to the REST API of Orthanc" - VALUE "FileVersion", "0.7.0.5" + VALUE "FileVersion", "0.7.0.6" VALUE "InternalName", "OrthancClient" VALUE "LegalCopyright", "(c) 2012-2014, Sebastien Jodogne, CHU of Liege" VALUE "LegalTrademarks", "Licensing information is available on https://code.google.com/p/orthanc/" diff -r aabc3b430890 -r 427a1f996b7b OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc --- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc Wed Jun 25 11:56:48 2014 +0200 @@ -1,7 +1,7 @@ #include VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,7,0,5 + FILEVERSION 0,7,0,6 PRODUCTVERSION 0,7,0,0 FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_DLL @@ -10,10 +10,10 @@ BEGIN BLOCK "040904E4" BEGIN - VALUE "Comments", "Release 0.7.5" + VALUE "Comments", "Release 0.7.6" VALUE "CompanyName", "CHU of Liege" VALUE "FileDescription", "Native client to the REST API of Orthanc" - VALUE "FileVersion", "0.7.0.5" + VALUE "FileVersion", "0.7.0.6" VALUE "InternalName", "OrthancClient" VALUE "LegalCopyright", "(c) 2012-2014, Sebastien Jodogne, CHU of Liege" VALUE "LegalTrademarks", "Licensing information is available on https://code.google.com/p/orthanc/" diff -r aabc3b430890 -r 427a1f996b7b OrthancCppClient/SharedLibrary/Product.json --- a/OrthancCppClient/SharedLibrary/Product.json Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/Product.json Wed Jun 25 11:56:48 2014 +0200 @@ -4,5 +4,5 @@ "Company" : "CHU of Liege", "Copyright" : "(c) 2012-2014, Sebastien Jodogne, CHU of Liege", "Legal" : "Licensing information is available on https://code.google.com/p/orthanc/", - "Version" : "0.7.5" + "Version" : "0.7.6" } diff -r aabc3b430890 -r 427a1f996b7b OrthancServer/DicomModification.cpp --- a/OrthancServer/DicomModification.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancServer/DicomModification.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -37,6 +37,7 @@ #include "FromDcmtkBridge.h" #include // For std::auto_ptr +#include namespace Orthanc { @@ -238,6 +239,7 @@ if (level_ == ResourceType_Patient && !IsReplaced(DICOM_TAG_PATIENT_ID)) { + LOG(ERROR) << "When modifying a patient, her PatientID is required to be modified"; throw OrthancException(ErrorCode_BadRequest); } diff -r aabc3b430890 -r 427a1f996b7b OrthancServer/DicomProtocol/DicomServer.cpp --- a/OrthancServer/DicomProtocol/DicomServer.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancServer/DicomProtocol/DicomServer.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -116,7 +116,7 @@ LoadEmbeddedDictionary(d, EmbeddedResources::DICTIONARY_DICOM); LoadEmbeddedDictionary(d, EmbeddedResources::DICTIONARY_PRIVATE); -#elif defined(__linux) +#elif defined(__linux) || defined(__FreeBSD_kernel__) std::string path = DCMTK_DICTIONARY_DIR; const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE); diff -r aabc3b430890 -r 427a1f996b7b OrthancServer/DicomProtocol/DicomUserConnection.cpp --- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -107,6 +107,18 @@ #endif +#if !defined(HOST_NAME_MAX) && defined(_POSIX_HOST_NAME_MAX) +/** + * TO IMPROVE: "_POSIX_HOST_NAME_MAX is only the minimum value that + * HOST_NAME_MAX can ever have [...] Therefore you cannot allocate an + * array of size _POSIX_HOST_NAME_MAX, invoke gethostname() and expect + * that the result will fit." + * http://lists.gnu.org/archive/html/bug-gnulib/2009-08/msg00128.html + **/ +#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX +#endif + + static const char* DEFAULT_PREFERRED_TRANSFER_SYNTAX = UID_LittleEndianImplicitTransferSyntax; /** @@ -205,21 +217,21 @@ unsigned int presentationContextId = 1; for (std::list::const_iterator it = reservedStorageSOPClasses_.begin(); - it != reservedStorageSOPClasses_.end(); it++) + it != reservedStorageSOPClasses_.end(); ++it) { RegisterStorageSOPClass(pimpl_->params_, presentationContextId, *it, asPreferred, asFallback); } for (std::set::const_iterator it = storageSOPClasses_.begin(); - it != storageSOPClasses_.end(); it++) + it != storageSOPClasses_.end(); ++it) { RegisterStorageSOPClass(pimpl_->params_, presentationContextId, *it, asPreferred, asFallback); } for (std::set::const_iterator it = defaultStorageSOPClasses_.begin(); - it != defaultStorageSOPClasses_.end(); it++) + it != defaultStorageSOPClasses_.end(); ++it) { RegisterStorageSOPClass(pimpl_->params_, presentationContextId, *it, asPreferred, asFallback); diff -r aabc3b430890 -r 427a1f996b7b OrthancServer/DicomProtocol/IFindRequestHandler.h --- a/OrthancServer/DicomProtocol/IFindRequestHandler.h Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancServer/DicomProtocol/IFindRequestHandler.h Wed Jun 25 11:56:48 2014 +0200 @@ -47,7 +47,13 @@ { } - virtual void Handle(DicomFindAnswers& answers, + /** + * Can throw exceptions. Returns "false" iff too many results have + * to be returned. In such a case, a "Matching terminated due to + * Cancel request" DIMSE code would be returned. + * https://www.dabsoft.ch/dicom/4/V.4.1/ + **/ + virtual bool Handle(DicomFindAnswers& answers, const DicomMap& input, const std::string& callingAETitle) = 0; }; diff -r aabc3b430890 -r 427a1f996b7b OrthancServer/DicomProtocol/RemoteModalityParameters.cpp --- a/OrthancServer/DicomProtocol/RemoteModalityParameters.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancServer/DicomProtocol/RemoteModalityParameters.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -40,12 +40,12 @@ namespace Orthanc { - RemoteModalityParameters::RemoteModalityParameters() + RemoteModalityParameters::RemoteModalityParameters() : + aet_("ORTHANC"), + host_("localhost"), + port_(104), + manufacturer_(ModalityManufacturer_Generic) { - aet_ = "ORTHANC"; - host_ = "localhost"; - port_ = 104; - manufacturer_ = ModalityManufacturer_Generic; } void RemoteModalityParameters::SetPort(int port) diff -r aabc3b430890 -r 427a1f996b7b OrthancServer/DicomProtocol/ReusableDicomUserConnection.h --- a/OrthancServer/DicomProtocol/ReusableDicomUserConnection.h Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancServer/DicomProtocol/ReusableDicomUserConnection.h Wed Jun 25 11:56:48 2014 +0200 @@ -90,7 +90,7 @@ unsigned int GetMillisecondsBeforeClose() const { - return timeBeforeClose_.total_milliseconds(); + return static_cast(timeBeforeClose_.total_milliseconds()); } void SetMillisecondsBeforeClose(unsigned int ms); diff -r aabc3b430890 -r 427a1f996b7b OrthancServer/FromDcmtkBridge.cpp --- a/OrthancServer/FromDcmtkBridge.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancServer/FromDcmtkBridge.cpp Wed Jun 25 11:56:48 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,345 +428,6 @@ } - 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; - - DicomMap m; - FromDcmtkBridge::Convert(m, dataset); - - std::string privateContent; - - DcmElement* e; - if (dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), e).good() && - e != NULL) - { - Uint8* pixData = NULL; - if (e->getUint8Array(pixData) == EC_Normal) - { - accessor.reset(new DicomIntegerPixelAccessor(m, pixData, e->getLength())); - accessor->SetCurrentFrame(frame); - } - } - 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) - { - 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); - } - } - } - - - void FromDcmtkBridge::ExtractPngImage(std::string& result, - const std::string& dicomContent, - unsigned int frame, - ImageExtractionMode mode) - { - DcmInputBufferStream is; - if (dicomContent.size() > 0) - { - is.setBuffer(&dicomContent[0], dicomContent.size()); - } - is.setEos(); - - DcmFileFormat dicom; - if (dicom.read(is).good()) - { - ExtractPngImage(result, *dicom.getDataset(), frame, mode); - } - else - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - std::string FromDcmtkBridge::GetName(const DicomTag& t) { diff -r aabc3b430890 -r 427a1f996b7b OrthancServer/FromDcmtkBridge.h --- a/OrthancServer/FromDcmtkBridge.h Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancServer/FromDcmtkBridge.h Wed Jun 25 11:56:48 2014 +0200 @@ -58,16 +58,6 @@ const std::string& path, unsigned int maxStringLength = 256); - static void ExtractPngImage(std::string& result, - DcmDataset& dataset, - unsigned int frame, - ImageExtractionMode mode); - - static void ExtractPngImage(std::string& result, - const std::string& dicomContent, - unsigned int frame, - ImageExtractionMode mode); - static std::string GetName(const DicomTag& tag); static DicomTag ParseTag(const char* name); diff -r aabc3b430890 -r 427a1f996b7b OrthancServer/Internals/DicomImageDecoder.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Internals/DicomImageDecoder.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -0,0 +1,685 @@ +/** + * 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 "../PrecompiledHeadersServer.h" +#include "DicomImageDecoder.h" + +#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 +{ + static const DicomTag DICOM_TAG_CONTENT(0x07a1, 0x100a); + static const DicomTag DICOM_TAG_COMPRESSION_TYPE(0x07a1, 0x1011); + + + static bool IsJpegLossless(const DcmDataset& dataset) + { + // http://support.dcmtk.org/docs/dcxfer_8h-source.html + return (dataset.getOriginalXfer() == EXS_JPEGLSLossless || + dataset.getOriginalXfer() == EXS_JPEGLSLossy); + } + + + static bool 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; + } + } + + + static bool 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; + } + + + 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 (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_; + } + }; + + + 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::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, static_cast(-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 aabc3b430890 -r 427a1f996b7b OrthancServer/Internals/DicomImageDecoder.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Internals/DicomImageDecoder.h Wed Jun 25 11:56:48 2014 +0200 @@ -0,0 +1,81 @@ +/** + * 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); + + static bool IsUncompressedImage(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 + + public: + 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 aabc3b430890 -r 427a1f996b7b OrthancServer/Internals/FindScp.cpp --- a/OrthancServer/Internals/FindScp.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancServer/Internals/FindScp.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -100,6 +100,7 @@ DicomFindAnswers answers_; DcmDataset* lastRequest_; const std::string* callingAETitle_; + bool noCroppingOfResults_; }; @@ -125,12 +126,12 @@ try { - data.handler_->Handle(data.answers_, data.input_, *data.callingAETitle_); + data.noCroppingOfResults_ = data.handler_->Handle(data.answers_, data.input_, *data.callingAETitle_); } catch (OrthancException& e) { // Internal error! - LOG(ERROR) << "IFindRequestHandler Failed: " << e.What(); + LOG(ERROR) << "C-FIND request handler has failed: " << e.What(); response->DimseStatus = STATUS_FIND_Failed_UnableToProcess; *responseIdentifiers = NULL; return; @@ -148,12 +149,21 @@ if (responseCount <= static_cast(data.answers_.GetSize())) { + // There are pending results that are still to be sent response->DimseStatus = STATUS_Pending; *responseIdentifiers = ToDcmtkBridge::Convert(data.answers_.GetAnswer(responseCount - 1)); } + else if (data.noCroppingOfResults_) + { + // Success: All the results have been sent + response->DimseStatus = STATUS_Success; + *responseIdentifiers = NULL; + } else { - response->DimseStatus = STATUS_Success; + // Success, but the results were too numerous and had to be cropped + LOG(WARNING) << "Too many results for an incoming C-FIND query"; + response->DimseStatus = STATUS_FIND_Cancel_MatchingTerminatedDueToCancelRequest; *responseIdentifiers = NULL; } } @@ -170,6 +180,7 @@ data.lastRequest_ = NULL; data.handler_ = &handler; data.callingAETitle_ = &callingAETitle; + data.noCroppingOfResults_ = true; OFCondition cond = DIMSE_findProvider(assoc, presID, &msg->msg.CFindRQ, FindScpCallback, &data, diff -r aabc3b430890 -r 427a1f996b7b OrthancServer/OrthancFindRequestHandler.cpp --- a/OrthancServer/OrthancFindRequestHandler.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancServer/OrthancFindRequestHandler.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -442,7 +442,26 @@ } - void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, + bool OrthancFindRequestHandler::HasReachedLimit(const DicomFindAnswers& answers, + ResourceType level) const + { + switch (level) + { + case ResourceType_Patient: + case ResourceType_Study: + case ResourceType_Series: + return (maxResults_ != 0 && answers.GetSize() >= maxResults_); + + case ResourceType_Instance: + return (maxInstances_ != 0 && answers.GetSize() >= maxInstances_); + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + + bool OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, const DicomMap& input, const std::string& callingAETitle) { @@ -580,6 +599,12 @@ if (Matches(info, query)) { + if (HasReachedLimit(answers, level)) + { + // Too many results, stop before recording this new match + return false; + } + AddAnswer(answers, info, query); } } @@ -589,6 +614,8 @@ // This resource has probably been deleted during the find request } } + + return true; // All the matching resources have been returned } } diff -r aabc3b430890 -r 427a1f996b7b OrthancServer/OrthancFindRequestHandler.h --- a/OrthancServer/OrthancFindRequestHandler.h Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancServer/OrthancFindRequestHandler.h Wed Jun 25 11:56:48 2014 +0200 @@ -41,15 +41,42 @@ { private: ServerContext& context_; + unsigned int maxResults_; + unsigned int maxInstances_; + + bool HasReachedLimit(const DicomFindAnswers& answers, + ResourceType level) const; public: OrthancFindRequestHandler(ServerContext& context) : - context_(context) + context_(context), + maxResults_(0), + maxInstances_(0) { } - virtual void Handle(DicomFindAnswers& answers, + virtual bool Handle(DicomFindAnswers& answers, const DicomMap& input, const std::string& callingAETitle); + + unsigned int GetMaxResults() const + { + return maxResults_; + } + + void SetMaxResults(unsigned int results) + { + maxResults_ = results; + } + + unsigned int GetMaxInstances() const + { + return maxInstances_; + } + + void SetMaxInstances(unsigned int instances) + { + maxInstances_ = instances; + } }; } diff -r aabc3b430890 -r 427a1f996b7b OrthancServer/OrthancInitialization.cpp --- a/OrthancServer/OrthancInitialization.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancServer/OrthancInitialization.cpp Wed Jun 25 11:56:48 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 } @@ -268,7 +299,7 @@ { LOG(ERROR) << "Syntax error in the definition of modality \"" << name << "\". Please check your configuration file."; - throw e; + throw; } } @@ -299,7 +330,7 @@ { LOG(ERROR) << "Syntax error in the definition of peer \"" << name << "\". Please check your configuration file."; - throw e; + throw; } } diff -r aabc3b430890 -r 427a1f996b7b OrthancServer/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -202,9 +202,11 @@ std::string dicomContent, png; context.ReadFile(dicomContent, publicId, FileContentType_Dicom); + ParsedDicomFile dicom(dicomContent); + try { - FromDcmtkBridge::ExtractPngImage(png, dicomContent, frame, mode); + dicom.ExtractPngImage(png, frame, mode); call.GetOutput().AnswerBuffer(png, "image/png"); } catch (OrthancException& e) @@ -228,6 +230,39 @@ } + static void GetMatlabImage(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string frameId = call.GetUriComponent("frame", "0"); + + unsigned int frame; + try + { + frame = boost::lexical_cast(frameId); + } + catch (boost::bad_lexical_cast) + { + return; + } + + std::string publicId = call.GetUriComponent("id", ""); + std::string dicomContent; + context.ReadFile(dicomContent, publicId, FileContentType_Dicom); + + ParsedDicomFile dicom(dicomContent); + ImageBuffer buffer; + dicom.ExtractImage(buffer, frame); + + ImageAccessor accessor(buffer.GetConstAccessor()); + + std::string result; + accessor.ToMatlabString(result); + + call.GetOutput().AnswerBuffer(result, "text/plain"); + } + + static void GetResourceStatistics(RestApi::GetCall& call) { @@ -587,10 +622,12 @@ Register("/instances/{id}/frames/{frame}/image-uint8", GetImage); Register("/instances/{id}/frames/{frame}/image-uint16", GetImage); Register("/instances/{id}/frames/{frame}/image-int16", GetImage); + Register("/instances/{id}/frames/{frame}/matlab", GetMatlabImage); Register("/instances/{id}/preview", GetImage); Register("/instances/{id}/image-uint8", GetImage); Register("/instances/{id}/image-uint16", GetImage); Register("/instances/{id}/image-int16", GetImage); + Register("/instances/{id}/matlab", GetMatlabImage); Register("/patients/{id}/protected", IsProtectedPatient); Register("/patients/{id}/protected", SetPatientProtection); diff -r aabc3b430890 -r 427a1f996b7b OrthancServer/ParsedDicomFile.cpp --- a/OrthancServer/ParsedDicomFile.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancServer/ParsedDicomFile.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -82,8 +82,10 @@ #include "FromDcmtkBridge.h" #include "ToDcmtkBridge.h" +#include "Internals/DicomImageDecoder.h" #include "../Core/Toolbox.h" #include "../Core/OrthancException.h" +#include "../Core/ImageFormats/ImageBuffer.h" #include "../Core/ImageFormats/PngWriter.h" #include "../Core/Uuid.h" #include "../Core/DicomFormat/DicomString.h" @@ -999,15 +1001,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 +1021,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]); } @@ -1214,4 +1216,67 @@ throw OrthancException(ErrorCode_InternalError); } } + + + void ParsedDicomFile::ExtractImage(ImageBuffer& result, + unsigned int frame) + { + DcmDataset& dataset = *pimpl_->file_->getDataset(); + + if (!DicomImageDecoder::Decode(result, dataset, frame)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + void ParsedDicomFile::ExtractImage(ImageBuffer& result, + unsigned int frame, + ImageExtractionMode mode) + { + DcmDataset& dataset = *pimpl_->file_->getDataset(); + + bool ok = false; + + switch (mode) + { + case ImageExtractionMode_UInt8: + ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_Grayscale8); + break; + + case ImageExtractionMode_UInt16: + ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_Grayscale16); + break; + + case ImageExtractionMode_Int16: + ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_SignedGrayscale16); + break; + + case ImageExtractionMode_Preview: + ok = DicomImageDecoder::DecodePreview(result, dataset, frame); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + if (!ok) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + void ParsedDicomFile::ExtractPngImage(std::string& result, + unsigned int frame, + ImageExtractionMode mode) + { + ImageBuffer buffer; + ExtractImage(buffer, frame, mode); + + ImageAccessor accessor(buffer.GetConstAccessor()); + PngWriter writer; + writer.WriteToMemory(result, accessor); + } + } diff -r aabc3b430890 -r 427a1f996b7b OrthancServer/ParsedDicomFile.h --- a/OrthancServer/ParsedDicomFile.h Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancServer/ParsedDicomFile.h Wed Jun 25 11:56:48 2014 +0200 @@ -36,6 +36,7 @@ #include "../Core/RestApi/RestApiOutput.h" #include "ServerEnumerations.h" #include "../Core/ImageFormats/ImageAccessor.h" +#include "../Core/ImageFormats/ImageBuffer.h" namespace Orthanc { @@ -92,6 +93,17 @@ void EmbedImage(const ImageAccessor& accessor); void EmbedImage(const std::string& dataUriScheme); + + void ExtractImage(ImageBuffer& result, + unsigned int frame); + + void ExtractImage(ImageBuffer& result, + unsigned int frame, + ImageExtractionMode mode); + + void ExtractPngImage(std::string& result, + unsigned int frame, + ImageExtractionMode mode); }; } diff -r aabc3b430890 -r 427a1f996b7b OrthancServer/ServerContext.cpp --- a/OrthancServer/ServerContext.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancServer/ServerContext.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -286,7 +286,7 @@ LogMissingRequiredTag(dicomSummary); } - throw e; + throw; } } diff -r aabc3b430890 -r 427a1f996b7b OrthancServer/main.cpp --- a/OrthancServer/main.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/OrthancServer/main.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -98,7 +98,32 @@ virtual IFindRequestHandler* ConstructFindRequestHandler() { - return new OrthancFindRequestHandler(context_); + std::auto_ptr result(new OrthancFindRequestHandler(context_)); + + result->SetMaxResults(Configuration::GetGlobalIntegerParameter("LimitFindResults", 0)); + result->SetMaxInstances(Configuration::GetGlobalIntegerParameter("LimitFindInstances", 0)); + + if (result->GetMaxResults() == 0) + { + LOG(INFO) << "No limit on the number of C-FIND results at the Patient, Study and Series levels"; + } + else + { + LOG(INFO) << "Maximum " << result->GetMaxResults() + << " results for C-FIND queries at the Patient, Study and Series levels"; + } + + if (result->GetMaxInstances() == 0) + { + LOG(INFO) << "No limit on the number of C-FIND results at the Instance level"; + } + else + { + LOG(INFO) << "Maximum " << result->GetMaxInstances() + << " instances will be returned for C-FIND queries at the Instance level"; + } + + return result.release(); } virtual IMoveRequestHandler* ConstructMoveRequestHandler() diff -r aabc3b430890 -r 427a1f996b7b Resources/CMake/BoostConfiguration.cmake --- a/Resources/CMake/BoostConfiguration.cmake Mon Jun 02 13:24:44 2014 +0200 +++ b/Resources/CMake/BoostConfiguration.cmake Wed Jun 25 11:56:48 2014 +0200 @@ -8,7 +8,7 @@ #set(Boost_USE_STATIC_LIBS ON) find_package(Boost - COMPONENTS filesystem thread system date_time regex) + COMPONENTS filesystem thread system date_time regex locale) if (NOT Boost_FOUND) message(FATAL_ERROR "Unable to locate Boost on this system") @@ -53,7 +53,10 @@ ) set(BOOST_SOURCES) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") list(APPEND BOOST_SOURCES ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/once.cpp ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/thread.cpp @@ -76,10 +79,17 @@ add_definitions( -DBOOST_LOCALE_WITH_WCONV=1 ) + else() message(FATAL_ERROR "Support your platform here") endif() + if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + list(APPEND BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/filesystem/src/utf8_codecvt_facet.cpp + ) + endif() + aux_source_directory(${BOOST_SOURCES_DIR}/libs/regex/src BOOST_REGEX_SOURCES) list(APPEND BOOST_SOURCES @@ -120,6 +130,6 @@ source_group(ThirdParty\\Boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*) else() add_definitions( - -DBOOST_HAS_LOCALE=0 + -DBOOST_HAS_LOCALE=1 ) endif() diff -r aabc3b430890 -r 427a1f996b7b Resources/CMake/Compiler.cmake --- a/Resources/CMake/Compiler.cmake Mon Jun 02 13:24:44 2014 +0200 +++ b/Resources/CMake/Compiler.cmake Wed Jun 25 11:56:48 2014 +0200 @@ -37,7 +37,8 @@ endif() -if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -I${LSB_PATH}/include") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -nostdinc++ -I${LSB_PATH}/include -I${LSB_PATH}/include/c++ -I${LSB_PATH}/include/c++/backward -fpermissive") @@ -69,6 +70,12 @@ SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++") endif() +elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + add_definitions( + -D_XOPEN_SOURCE=1 + ) + link_libraries(iconv) + endif() diff -r aabc3b430890 -r 427a1f996b7b Resources/CMake/DcmtkConfiguration.cmake --- a/Resources/CMake/DcmtkConfiguration.cmake Mon Jun 02 13:24:44 2014 +0200 +++ b/Resources/CMake/DcmtkConfiguration.cmake Wed Jun 25 11:56:48 2014 +0200 @@ -12,7 +12,6 @@ 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) @@ -49,9 +48,47 @@ 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") + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") list(REMOVE_ITEM DCMTK_SOURCES ${DCMTK_SOURCES_DIR}/oflog/libsrc/windebap.cc ${DCMTK_SOURCES_DIR}/oflog/libsrc/winsock.cc diff -r aabc3b430890 -r 427a1f996b7b Resources/CMake/GoogleLogConfiguration.cmake --- a/Resources/CMake/GoogleLogConfiguration.cmake Mon Jun 02 13:24:44 2014 +0200 +++ b/Resources/CMake/GoogleLogConfiguration.cmake Wed Jun 25 11:56:48 2014 +0200 @@ -28,7 +28,9 @@ set(ac_google_start_namespace "namespace google {") set(ac_google_end_namespace "}") - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") set(ac_cv_have_unistd_h 1) set(ac_cv_have_stdint_h 1) set(ac_cv_have_systypes_h 0) @@ -83,13 +85,21 @@ ) endif() - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") # Install the specific configuration for LSB SDK configure_file( ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfigurationLSB.h ${GOOGLE_LOG_SOURCES_DIR}/src/config.h COPYONLY) + elseif ("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") + # Install the specific configuration for Mac OS + configure_file( + ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfigurationDarwin.h + ${GOOGLE_LOG_SOURCES_DIR}/src/config.h + COPYONLY) else() configure_file( ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfiguration.h @@ -130,13 +140,10 @@ # This is a patch for MinGW64 add_definitions(-D_TIME_H__S=1) endif() - endif() - - add_library(GoogleLog STATIC ${GOOGLE_LOG_SOURCES}) - link_libraries(GoogleLog) + set(STATIC_GOOGLE_LOG GoogleLog) else() CHECK_INCLUDE_FILE_CXX(glog/logging.h HAVE_GOOGLE_LOG_H) diff -r aabc3b430890 -r 427a1f996b7b Resources/CMake/GoogleLogConfigurationDarwin.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/GoogleLogConfigurationDarwin.h Wed Jun 25 11:56:48 2014 +0200 @@ -0,0 +1,175 @@ +/* src/config.h. Generated from config.h.in by configure. */ +/* src/config.h.in. Generated from configure.ac by autoheader. */ + +/* Namespace for Google classes */ +#define GOOGLE_NAMESPACE google + +/* Define if you have the `dladdr' function */ +/* #undef HAVE_DLADDR */ + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_EXECINFO_H 1 + +/* Define if you have the `fcntl' function */ +#define HAVE_FCNTL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_GLOB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `pthread' library (-lpthread). */ +#define HAVE_LIBPTHREAD 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_LIBUNWIND_H */ + +/* define if you have google gflags library */ +/* #undef HAVE_LIB_GFLAGS */ + +/* define if you have google gmock library */ +/* #undef HAVE_LIB_GMOCK */ + +/* define if you have google gtest library */ +/* #undef HAVE_LIB_GTEST */ + +/* define if you have libunwind */ +/* #undef HAVE_LIB_UNWIND */ + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* define if the compiler implements namespaces */ +#define HAVE_NAMESPACES 1 + +/* Define if you have POSIX threads libraries and header files. */ +#define HAVE_PTHREAD 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_PWD_H 1 + +/* define if the compiler implements pthread_rwlock_* */ +#define HAVE_RWLOCK 1 + +/* Define if you have the `sigaltstack' function */ +#define HAVE_SIGALTSTACK 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYSCALL_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYSLOG_H 1 + +/* Define to 1 if you have the header file. */ +/* #define HAVE_SYS_STAT_H 1 */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SYSCALL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +/* #define HAVE_SYS_TYPES_H 1 */ + +/* Define to 1 if you have the header file. */ +/* #define HAVE_SYS_UCONTEXT_H 1 */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_UTSNAME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UCONTEXT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* define if the compiler supports using expression for operator */ +#define HAVE_USING_OPERATOR 1 + +/* define if your compiler has __attribute__ */ +#define HAVE___ATTRIBUTE__ 1 + +/* define if your compiler has __builtin_expect */ +#define HAVE___BUILTIN_EXPECT 1 + +/* define if your compiler has __sync_val_compare_and_swap */ +#define HAVE___SYNC_VAL_COMPARE_AND_SWAP 1 + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#define LT_OBJDIR ".libs/" + +/* Name of package */ +#define PACKAGE "glog" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "opensource@google.com" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "glog" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "glog 0.3.2" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "glog" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "0.3.2" + +/* How to access the PC from a struct ucontext */ +/*#include +#include +#ifdef REG_RIP +#define PC_FROM_UCONTEXT uc_mcontext.gregs[REG_RIP] +#else +#undef PC_FROM_UCONTEXT +#endif*/ + +// This is required for older versions of Linux +#undef PC_FROM_UCONTEXT + +/* Define to necessary symbol if this constant uses a non-standard name on + your system. */ +/* #undef PTHREAD_CREATE_JOINABLE */ + +/* The size of `void *', as computed by sizeof. */ +#define SIZEOF_VOID_P 8 + +/* Define to 1 if you have the ANSI C header files. */ +/* #undef STDC_HEADERS */ + +/* the namespace where STL code like vector<> is defined */ +#define STL_NAMESPACE std + +/* location of source code */ +#define TEST_SRC_DIR "." + +/* Version number of package */ +#define VERSION "0.3.2" + +/* Stops putting the code inside the Google namespace */ +#define _END_GOOGLE_NAMESPACE_ } + +/* Puts following code inside the Google namespace */ +#define _START_GOOGLE_NAMESPACE_ namespace google { diff -r aabc3b430890 -r 427a1f996b7b Resources/CMake/LibCurlConfiguration.cmake --- a/Resources/CMake/LibCurlConfiguration.cmake Mon Jun 02 13:24:44 2014 +0200 +++ b/Resources/CMake/LibCurlConfiguration.cmake Wed Jun 25 11:56:48 2014 +0200 @@ -40,7 +40,9 @@ ) endif() - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") SET(TMP_OS "x86_64") else() diff -r aabc3b430890 -r 427a1f996b7b Resources/CMake/LuaConfiguration.cmake --- a/Resources/CMake/LuaConfiguration.cmake Mon Jun 02 13:24:44 2014 +0200 +++ b/Resources/CMake/LuaConfiguration.cmake Wed Jun 25 11:56:48 2014 +0200 @@ -51,7 +51,7 @@ ) add_library(Lua STATIC ${LUA_SOURCES}) - link_libraries(Lua) + set(STATIC_LUA Lua) source_group(ThirdParty\\Lua REGULAR_EXPRESSION ${LUA_SOURCES_DIR}/.*) diff -r aabc3b430890 -r 427a1f996b7b Resources/CMake/MongooseConfiguration.cmake --- a/Resources/CMake/MongooseConfiguration.cmake Mon Jun 02 13:24:44 2014 +0200 +++ b/Resources/CMake/MongooseConfiguration.cmake Wed Jun 25 11:56:48 2014 +0200 @@ -24,7 +24,8 @@ add_definitions( -DNO_SSL_DL=1 ) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") link_libraries(dl) endif() diff -r aabc3b430890 -r 427a1f996b7b Resources/Configuration.json --- a/Resources/Configuration.json Mon Jun 02 13:24:44 2014 +0200 +++ b/Resources/Configuration.json Wed Jun 25 11:56:48 2014 +0200 @@ -160,5 +160,14 @@ // will be computed and stored in the Orthanc database. This // information can be used to detect disk corruption, at the price // of a small performance overhead. - "StoreMD5ForAttachments" : true + "StoreMD5ForAttachments" : true, + + // The maximum number of results for a single C-FIND request at the + // Patient, Study or Series level. Setting this option to "0" means + // no limit. + "LimitFindResults" : 0, + + // The maximum number of results for a single C-FIND request at the + // Instance level. Setting this option to "0" means no limit. + "LimitFindInstances" : 0 } diff -r aabc3b430890 -r 427a1f996b7b UnitTestsSources/ImageProcessingTests.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/ImageProcessingTests.cpp Wed Jun 25 11:56:48 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 aabc3b430890 -r 427a1f996b7b UnitTestsSources/JpegLossless.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/JpegLossless.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -0,0 +1,54 @@ +/** + * 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; + + + +// TODO Write a test + + +#endif diff -r aabc3b430890 -r 427a1f996b7b UnitTestsSources/SQLite.cpp --- a/UnitTestsSources/SQLite.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/UnitTestsSources/SQLite.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -316,7 +316,7 @@ ASSERT_EQ(42ll, s.ColumnInt64(1)); ASSERT_TRUE(s.Step()); ASSERT_EQ(SQLite::COLUMN_TYPE_FLOAT, s.GetColumnType(1)); - ASSERT_FLOAT_EQ(42.5, s.ColumnDouble(1)); + ASSERT_DOUBLE_EQ(42.5, s.ColumnDouble(1)); ASSERT_TRUE(s.Step()); ASSERT_EQ(SQLite::COLUMN_TYPE_TEXT, s.GetColumnType(1)); ASSERT_EQ("Hello", s.ColumnString(1)); diff -r aabc3b430890 -r 427a1f996b7b UnitTestsSources/UnitTestsMain.cpp --- a/UnitTestsSources/UnitTestsMain.cpp Mon Jun 02 13:24:44 2014 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Wed Jun 25 11:56:48 2014 +0200 @@ -595,7 +595,10 @@ #if defined(_WIN32) ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness()); -#elif defined(__linux) +#elif defined(__APPLE__) + ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness()); + +#elif defined(__linux) || defined(__FreeBSD_kernel__) #if !defined(__BYTE_ORDER) # error Support your platform here