# HG changeset patch # User Sebastien Jodogne # Date 1361200048 -3600 # Node ID 80011cd589e641fee934de0ded8c7c51047a131e # Parent 301f2831489c186a710dcf6f60d1298c8e037934 support of rgb images diff -r 301f2831489c -r 80011cd589e6 CMakeLists.txt --- a/CMakeLists.txt Thu Feb 07 22:08:59 2013 +0100 +++ b/CMakeLists.txt Mon Feb 18 16:07:28 2013 +0100 @@ -190,14 +190,15 @@ include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake) add_executable(UnitTests ${GTEST_SOURCES} + UnitTests/FileStorage.cpp + UnitTests/MemoryCache.cpp + UnitTests/PngWriter.cpp UnitTests/RestApi.cpp UnitTests/SQLite.cpp UnitTests/SQLiteChromium.cpp UnitTests/ServerIndex.cpp UnitTests/Versions.cpp UnitTests/Zip.cpp - UnitTests/FileStorage.cpp - UnitTests/MemoryCache.cpp UnitTests/main.cpp ) target_link_libraries(UnitTests ServerLibrary CoreLibrary) diff -r 301f2831489c -r 80011cd589e6 Core/DicomFormat/DicomIntegerPixelAccessor.cpp --- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Thu Feb 07 22:08:59 2013 +0100 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Mon Feb 18 16:07:28 2013 +0100 @@ -40,6 +40,7 @@ #include #include #include +#include namespace Orthanc { @@ -50,6 +51,7 @@ static const DicomTag BITS_STORED(0x0028, 0x0101); static const DicomTag HIGH_BIT(0x0028, 0x0102); static const DicomTag PIXEL_REPRESENTATION(0x0028, 0x0103); + static const DicomTag PLANAR_CONFIGURATION(0x0028, 0x0006); DicomIntegerPixelAccessor::DicomIntegerPixelAccessor(const DicomMap& values, const void* pixelData, @@ -61,6 +63,7 @@ unsigned int bitsStored; unsigned int highBit; unsigned int pixelRepresentation; + planarConfiguration_ = 0; try { @@ -71,11 +74,22 @@ bitsStored = boost::lexical_cast(values.GetValue(BITS_STORED).AsString()); highBit = boost::lexical_cast(values.GetValue(HIGH_BIT).AsString()); pixelRepresentation = boost::lexical_cast(values.GetValue(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(PLANAR_CONFIGURATION).AsString()); + } } catch (boost::bad_lexical_cast) { throw OrthancException(ErrorCode_NotImplemented); } + catch (OrthancException) + { + throw OrthancException(ErrorCode_NotImplemented); + } frame_ = 0; try @@ -94,7 +108,8 @@ if ((bitsAllocated != 8 && bitsAllocated != 16 && bitsAllocated != 24 && bitsAllocated != 32) || - numberOfFrames_ == 0) + numberOfFrames_ == 0 || + (planarConfiguration_ != 0 && planarConfiguration_ != 1)) { throw OrthancException(ErrorCode_NotImplemented); } @@ -106,21 +121,23 @@ throw OrthancException(ErrorCode_NotImplemented); } - if (samplesPerPixel_ != 1) + if (samplesPerPixel_ != 1 && + samplesPerPixel_ != 3) { throw OrthancException(ErrorCode_NotImplemented); } - if (width_ * height_ * bitsAllocated / 8 * numberOfFrames_ > size) + bytesPerPixel_ = bitsAllocated / 8; + shift_ = highBit + 1 - bitsStored; + frameOffset_ = height_ * width_ * bytesPerPixel_ * samplesPerPixel_; + + if (numberOfFrames_ * frameOffset_ > size) { throw OrthancException(ErrorCode_BadFileFormat); } /*printf("%d %d %d %d %d %d %d %d\n", width_, height_, samplesPerPixel_, bitsAllocated, - bitsStored, highBit, pixelRepresentation, numberOfFrames_);*/ - - bytesPerPixel_ = bitsAllocated / 8; - shift_ = highBit + 1 - bitsStored; + bitsStored, highBit, pixelRepresentation, numberOfFrames_);*/ if (pixelRepresentation) { @@ -133,8 +150,25 @@ signMask_ = 0; } - rowOffset_ = width_ * bytesPerPixel_; - frameOffset_ = height_ * width_ * bytesPerPixel_; + if (planarConfiguration_ == 0) + { + /** + * The sample values for the first pixel are followed by the + * sample values for the second pixel, etc. For RGB images, this + * 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_; + } } @@ -154,22 +188,49 @@ { for (unsigned int x = 0; x < width_; x++) { - int32_t v = GetValue(x, y); - if (v < min) - min = v; - if (v > max) - max = v; + for (unsigned int c = 0; c < GetChannelCount(); c++) + { + int32_t v = GetValue(x, y); + if (v < min) + min = v; + if (v > max) + max = v; + } } } } - int32_t DicomIntegerPixelAccessor::GetValue(unsigned int x, unsigned int y) const + int32_t DicomIntegerPixelAccessor::GetValue(unsigned int x, + unsigned int y, + unsigned int channel) const { - assert(x < width_ && y < height_); + assert(x < width_ && y < height_ && channel < samplesPerPixel_); const uint8_t* pixel = reinterpret_cast(pixelData_) + - y * rowOffset_ + x * bytesPerPixel_ + frame_ * frameOffset_; + y * rowOffset_ + frame_ * frameOffset_; + + // https://www.dabsoft.ch/dicom/3/C.7.6.3.1.3/ + if (planarConfiguration_ == 0) + { + /** + * The sample values for the first pixel are followed by the + * sample values for the second pixel, etc. For RGB images, this + * 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_; + } int32_t v; v = pixel[0]; diff -r 301f2831489c -r 80011cd589e6 Core/DicomFormat/DicomIntegerPixelAccessor.h --- a/Core/DicomFormat/DicomIntegerPixelAccessor.h Thu Feb 07 22:08:59 2013 +0100 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.h Mon Feb 18 16:07:28 2013 +0100 @@ -45,6 +45,7 @@ unsigned int height_; unsigned int samplesPerPixel_; unsigned int numberOfFrames_; + unsigned int planarConfiguration_; const void* pixelData_; size_t size_; @@ -87,6 +88,11 @@ void GetExtremeValues(int32_t& min, int32_t& max) const; - int32_t GetValue(unsigned int x, unsigned int y) const; + unsigned int GetChannelCount() const + { + return samplesPerPixel_; + } + + int32_t GetValue(unsigned int x, unsigned int y, unsigned int channel = 0) const; }; } diff -r 301f2831489c -r 80011cd589e6 Core/Enumerations.h --- a/Core/Enumerations.h Thu Feb 07 22:08:59 2013 +0100 +++ b/Core/Enumerations.h Mon Feb 18 16:07:28 2013 +0100 @@ -63,7 +63,7 @@ enum PixelFormat { - PixelFormat_RGB, + PixelFormat_RGB24, PixelFormat_Grayscale8, PixelFormat_Grayscale16 }; diff -r 301f2831489c -r 80011cd589e6 Core/PngWriter.cpp --- a/Core/PngWriter.cpp Thu Feb 07 22:08:59 2013 +0100 +++ b/Core/PngWriter.cpp Mon Feb 18 16:07:28 2013 +0100 @@ -134,6 +134,11 @@ switch (format) { + case PixelFormat_RGB24: + pimpl_->bitDepth_ = 8; + pimpl_->colorType_ = PNG_COLOR_TYPE_RGB; + break; + case PixelFormat_Grayscale8: pimpl_->bitDepth_ = 8; pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; diff -r 301f2831489c -r 80011cd589e6 OrthancServer/FromDcmtkBridge.cpp --- a/OrthancServer/FromDcmtkBridge.cpp Thu Feb 07 22:08:59 2013 +0100 +++ b/OrthancServer/FromDcmtkBridge.cpp Mon Feb 18 16:07:28 2013 +0100 @@ -1020,9 +1020,41 @@ } - static void ExtractPngImagePreview(std::string& result, - DicomIntegerPixelAccessor& accessor) + 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; @@ -1054,6 +1086,8 @@ DicomIntegerPixelAccessor& accessor, PixelFormat format) { + assert(accessor.GetChannelCount() == 1); + PngWriter w; std::vector image(accessor.GetWidth() * accessor.GetHeight(), 0); @@ -1208,9 +1242,32 @@ } PixelFormat format; + + if (accessor->GetChannelCount() != 1 && + (mode == ImageExtractionMode_UInt8 || + mode == ImageExtractionMode_UInt16)) + { + throw OrthancException(ErrorCode_NotImplemented); + } + switch (mode) { case ImageExtractionMode_Preview: + switch (accessor->GetChannelCount()) + { + case 1: + format = PixelFormat_Grayscale8; + break; + + case 3: + format = PixelFormat_RGB24; + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + break; + case ImageExtractionMode_UInt8: format = PixelFormat_Grayscale8; break; @@ -1235,7 +1292,10 @@ switch (mode) { case ImageExtractionMode_Preview: - ExtractPngImagePreview(result, *accessor); + if (format == PixelFormat_Grayscale8) + ExtractPngImageGrayscalePreview(result, *accessor); + else + ExtractPngImageColorPreview(result, *accessor); break; case ImageExtractionMode_UInt8: diff -r 301f2831489c -r 80011cd589e6 UnitTests/PngWriter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTests/PngWriter.cpp Mon Feb 18 16:07:28 2013 +0100 @@ -0,0 +1,67 @@ +#include "gtest/gtest.h" + +#include +#include "../Core/PngWriter.h" + +TEST(PngWriter, ColorPattern) +{ + Orthanc::PngWriter w; + int width = 17; + int height = 61; + int pitch = width * 3; + + std::vector image(height * pitch); + for (int y = 0; y < height; y++) + { + uint8_t *p = &image[0] + y * pitch; + for (int x = 0; x < width; x++, p += 3) + { + p[0] = (y % 3 == 0) ? 255 : 0; + p[1] = (y % 3 == 1) ? 255 : 0; + p[2] = (y % 3 == 2) ? 255 : 0; + } + } + + w.WriteToFile("ColorPattern.png", width, height, pitch, Orthanc::PixelFormat_RGB24, &image[0]); +} + +TEST(PngWriter, Gray8Pattern) +{ + Orthanc::PngWriter w; + int width = 17; + int height = 256; + int pitch = width; + + std::vector image(height * pitch); + for (int y = 0; y < height; y++) + { + uint8_t *p = &image[0] + y * pitch; + for (int x = 0; x < width; x++, p++) + { + *p = y; + } + } + + w.WriteToFile("Gray8Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale8, &image[0]); +} + +TEST(PngWriter, Gray16Pattern) +{ + Orthanc::PngWriter w; + int width = 256; + int height = 256; + int pitch = width * 2 + 17; + + std::vector image(height * pitch); + int v = 0; + for (int y = 0; y < height; y++) + { + uint16_t *p = reinterpret_cast(&image[0] + y * pitch); + for (int x = 0; x < width; x++, p++, v++) + { + *p = v; + } + } + + w.WriteToFile("Gray16Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); +}