# HG changeset patch # User Sebastien Jodogne # Date 1531117102 -7200 # Node ID 83c991aeb61102ba70db00492e9e349bf32e604f # Parent 33de4a82c46641555511599d3d89bfa5f9336847# Parent 38a3054b22ffb80b7c2fe2e237f60f057a17cb1c integration mainline->jobs diff -r 33de4a82c466 -r 83c991aeb611 .hgignore --- a/.hgignore Thu Jul 05 17:10:10 2018 +0200 +++ b/.hgignore Mon Jul 09 08:18:22 2018 +0200 @@ -1,2 +1,5 @@ syntax: glob -ThirdPartyDownloads/ \ No newline at end of file +ThirdPartyDownloads/ +CMakeLists.txt.user +*.cpp.orig +*.h.orig diff -r 33de4a82c466 -r 83c991aeb611 Core/DicomParsing/Internals/DicomImageDecoder.cpp --- a/Core/DicomParsing/Internals/DicomImageDecoder.cpp Thu Jul 05 17:10:10 2018 +0200 +++ b/Core/DicomParsing/Internals/DicomImageDecoder.cpp Mon Jul 09 08:18:22 2018 +0200 @@ -93,6 +93,7 @@ #if ORTHANC_ENABLE_JPEG == 1 # include "../../Images/JpegWriter.h" #endif +#include "../../Images/PamWriter.h" #include @@ -952,6 +953,17 @@ } + void DicomImageDecoder::ExtractPamImage(std::string& result, + std::auto_ptr& image, + ImageExtractionMode mode, + bool invert) + { + ApplyExtractionMode(image, mode, invert); + + PamWriter writer; + writer.WriteToMemory(result, *image); + } + #if ORTHANC_ENABLE_PNG == 1 void DicomImageDecoder::ExtractPngImage(std::string& result, std::auto_ptr& image, diff -r 33de4a82c466 -r 83c991aeb611 Core/DicomParsing/Internals/DicomImageDecoder.h --- a/Core/DicomParsing/Internals/DicomImageDecoder.h Thu Jul 05 17:10:10 2018 +0200 +++ b/Core/DicomParsing/Internals/DicomImageDecoder.h Mon Jul 09 08:18:22 2018 +0200 @@ -101,6 +101,11 @@ static ImageAccessor *Decode(ParsedDicomFile& dicom, unsigned int frame); + static void ExtractPamImage(std::string& result, + std::auto_ptr& image, + ImageExtractionMode mode, + bool invert); + #if ORTHANC_ENABLE_PNG == 1 static void ExtractPngImage(std::string& result, std::auto_ptr& image, diff -r 33de4a82c466 -r 83c991aeb611 Core/Endianness.h --- a/Core/Endianness.h Thu Jul 05 17:10:10 2018 +0200 +++ b/Core/Endianness.h Mon Jul 09 08:18:22 2018 +0200 @@ -145,7 +145,13 @@ static inline uint16_t __orthanc_bswap16(uint16_t a) { - return (a << 8) | (a >> 8); + const uint8_t* p = reinterpret_cast(&a); + return (static_cast(p[0]) << 8 | + static_cast(p[1])); + + // WARNING: The implementation below makes LSB (Linux Standard + // Base) segfault in release builds. Don't use it!!! + // return (a << 8) | (a >> 8); } static inline uint32_t __orthanc_bswap32(uint32_t a) diff -r 33de4a82c466 -r 83c991aeb611 Core/EnumerationDictionary.h --- a/Core/EnumerationDictionary.h Thu Jul 05 17:10:10 2018 +0200 +++ b/Core/EnumerationDictionary.h Mon Jul 09 08:18:22 2018 +0200 @@ -84,14 +84,11 @@ Enumeration Translate(const std::string& str) const { - try + int value; + if (boost::conversion::try_lexical_convert(str, value)) { - int value = boost::lexical_cast(str); return static_cast(value); } - catch (boost::bad_lexical_cast) - { - } typename StringToEnumeration::const_iterator found = stringToEnumeration_.find(str); diff -r 33de4a82c466 -r 83c991aeb611 Core/Images/JpegWriter.cpp --- a/Core/Images/JpegWriter.cpp Thu Jul 05 17:10:10 2018 +0200 +++ b/Core/Images/JpegWriter.cpp Mon Jul 09 08:18:22 2018 +0200 @@ -166,7 +166,6 @@ #endif -#if ORTHANC_SANDBOXED == 0 void JpegWriter::WriteToMemoryInternal(std::string& jpeg, unsigned int width, unsigned int height, @@ -211,5 +210,4 @@ jpeg.assign(reinterpret_cast(data), size); free(data); } -#endif } diff -r 33de4a82c466 -r 83c991aeb611 Core/Images/JpegWriter.h --- a/Core/Images/JpegWriter.h Thu Jul 05 17:10:10 2018 +0200 +++ b/Core/Images/JpegWriter.h Mon Jul 09 08:18:22 2018 +0200 @@ -48,21 +48,21 @@ class JpegWriter : public IImageWriter { protected: +#if ORTHANC_SANDBOXED == 0 virtual void WriteToFileInternal(const std::string& filename, unsigned int width, unsigned int height, unsigned int pitch, PixelFormat format, const void* buffer); +#endif -#if ORTHANC_SANDBOXED == 0 virtual void WriteToMemoryInternal(std::string& jpeg, unsigned int width, unsigned int height, unsigned int pitch, PixelFormat format, const void* buffer); -#endif private: uint8_t quality_; diff -r 33de4a82c466 -r 83c991aeb611 Core/Images/PamReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/PamReader.cpp Mon Jul 09 08:18:22 2018 +0200 @@ -0,0 +1,241 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "PamReader.h" + +#include "../Endianness.h" +#include "../OrthancException.h" +#include "../Toolbox.h" + +#if ORTHANC_SANDBOXED == 0 +# include "../SystemToolbox.h" +#endif + +#include +#include + + +namespace Orthanc +{ + static void GetPixelFormat(PixelFormat& format, + unsigned int& bytesPerChannel, + const unsigned int& maxValue, + const unsigned int& channelCount, + const std::string& tupleType) + { + if (tupleType == "GRAYSCALE" && + channelCount == 1) + { + switch (maxValue) + { + case 255: + format = PixelFormat_Grayscale8; + bytesPerChannel = 1; + return; + + case 65535: + format = PixelFormat_Grayscale16; + bytesPerChannel = 2; + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + else if (tupleType == "RGB" && + channelCount == 3) + { + switch (maxValue) + { + case 255: + format = PixelFormat_RGB24; + bytesPerChannel = 1; + return; + + case 65535: + format = PixelFormat_RGB48; + bytesPerChannel = 2; + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + else + { + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + typedef std::map Parameters; + + + static std::string LookupStringParameter(const Parameters& parameters, + const std::string& key) + { + Parameters::const_iterator found = parameters.find(key); + + if (found == parameters.end()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + return found->second; + } + } + + + static unsigned int LookupIntegerParameter(const Parameters& parameters, + const std::string& key) + { + try + { + int value = boost::lexical_cast(LookupStringParameter(parameters, key)); + + if (value < 0) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + return static_cast(value); + } + } + catch (boost::bad_lexical_cast&) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + void PamReader::ParseContent() + { + static const std::string headerDelimiter = "ENDHDR\n"; + + boost::iterator_range headerRange = + boost::algorithm::find_first(content_, headerDelimiter); + + if (!headerRange) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + std::string header(static_cast(content_).begin(), headerRange.begin()); + + std::vector lines; + Toolbox::TokenizeString(lines, header, '\n'); + + if (lines.size() < 2 || + lines.front() != "P7" || + !lines.back().empty()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Parameters parameters; + + for (size_t i = 1; i + 1 < lines.size(); i++) + { + std::vector tokens; + Toolbox::TokenizeString(tokens, lines[i], ' '); + + if (tokens.size() != 2) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + parameters[tokens[0]] = tokens[1]; + } + } + + const unsigned int width = LookupIntegerParameter(parameters, "WIDTH"); + const unsigned int height = LookupIntegerParameter(parameters, "HEIGHT"); + const unsigned int channelCount = LookupIntegerParameter(parameters, "DEPTH"); + const unsigned int maxValue = LookupIntegerParameter(parameters, "MAXVAL"); + const std::string tupleType = LookupStringParameter(parameters, "TUPLTYPE"); + + unsigned int bytesPerChannel; + PixelFormat format; + GetPixelFormat(format, bytesPerChannel, maxValue, channelCount, tupleType.c_str()); + + unsigned int pitch = width * channelCount * bytesPerChannel; + + if (content_.size() != header.size() + headerDelimiter.size() + pitch * height) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + size_t offset = content_.size() - pitch * height; + AssignWritable(format, width, height, pitch, &content_[offset]); + + // Byte swapping if needed + if (bytesPerChannel != 1 && + bytesPerChannel != 2) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + if (Toolbox::DetectEndianness() == Endianness_Little && bytesPerChannel == 2) + { + for (unsigned int h = 0; h < height; ++h) + { + uint16_t* pixel = reinterpret_cast(GetRow(h)); + + for (unsigned int w = 0; w < GetWidth(); ++w, ++pixel) + { + *pixel = htobe16(*pixel); + } + } + } + } + + +#if ORTHANC_SANDBOXED == 0 + void PamReader::ReadFromFile(const std::string& filename) + { + SystemToolbox::ReadFile(content_, filename); + ParseContent(); + } +#endif + + + void PamReader::ReadFromMemory(const std::string& buffer) + { + content_ = buffer; + ParseContent(); + } +} diff -r 33de4a82c466 -r 83c991aeb611 Core/Images/PamReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/PamReader.h Mon Jul 09 08:18:22 2018 +0200 @@ -0,0 +1,62 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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" + +#if !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +#include + +namespace Orthanc +{ + class PamReader : + public ImageAccessor, + public boost::noncopyable + { + private: + void ParseContent(); + + std::string content_; + + public: +#if ORTHANC_SANDBOXED == 0 + void ReadFromFile(const std::string& filename); +#endif + + void ReadFromMemory(const std::string& buffer); + }; +} diff -r 33de4a82c466 -r 83c991aeb611 Core/Images/PamWriter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/PamWriter.cpp Mon Jul 09 08:18:22 2018 +0200 @@ -0,0 +1,153 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "PamWriter.h" + +#include "../Endianness.h" +#include "../OrthancException.h" +#include "../Toolbox.h" + +#include + + +namespace Orthanc +{ + static void GetPixelFormatInfo(const PixelFormat& format, + unsigned int& maxValue, + unsigned int& channelCount, + unsigned int& bytesPerChannel, + std::string& tupleType) + { + switch (format) + { + case PixelFormat_Grayscale8: + maxValue = 255; + channelCount = 1; + bytesPerChannel = 1; + tupleType = "GRAYSCALE"; + break; + + case PixelFormat_Grayscale16: + maxValue = 65535; + channelCount = 1; + bytesPerChannel = 2; + tupleType = "GRAYSCALE"; + break; + + case PixelFormat_RGB24: + maxValue = 255; + channelCount = 3; + bytesPerChannel = 1; + tupleType = "RGB"; + break; + + case PixelFormat_RGB48: + maxValue = 255; + channelCount = 3; + bytesPerChannel = 2; + tupleType = "RGB"; + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void PamWriter::WriteToMemoryInternal(std::string& target, + unsigned int width, + unsigned int height, + unsigned int sourcePitch, + PixelFormat format, + const void* buffer) + { + unsigned int maxValue, channelCount, bytesPerChannel; + std::string tupleType; + GetPixelFormatInfo(format, maxValue, channelCount, bytesPerChannel, tupleType); + + target = (std::string("P7") + + std::string("\nWIDTH ") + boost::lexical_cast(width) + + std::string("\nHEIGHT ") + boost::lexical_cast(height) + + std::string("\nDEPTH ") + boost::lexical_cast(channelCount) + + std::string("\nMAXVAL ") + boost::lexical_cast(maxValue) + + std::string("\nTUPLTYPE ") + tupleType + + std::string("\nENDHDR\n")); + + if (bytesPerChannel != 1 && + bytesPerChannel != 2) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + size_t targetPitch = channelCount * bytesPerChannel * width; + size_t offset = target.size(); + + target.resize(offset + targetPitch * height); + + assert(target.size() != 0); + + if (Toolbox::DetectEndianness() == Endianness_Little && + bytesPerChannel == 2) + { + // Byte swapping + for (unsigned int h = 0; h < height; ++h) + { + const uint16_t* p = reinterpret_cast + (reinterpret_cast(buffer) + h * sourcePitch); + uint16_t* q = reinterpret_cast + (reinterpret_cast(&target[offset]) + h * targetPitch); + + for (unsigned int w = 0; w < width * channelCount; ++w) + { + *q = htobe16(*p); + p++; + q++; + } + } + } + else + { + // Either "bytesPerChannel == 1" (and endianness is not + // relevant), or we run on a big endian architecture (and no + // byte swapping is necessary, as PAM uses big endian) + + for (unsigned int h = 0; h < height; ++h) + { + const void* p = reinterpret_cast(buffer) + h * sourcePitch; + void* q = reinterpret_cast(&target[offset]) + h * targetPitch; + memcpy(q, p, targetPitch); + } + } + } +} diff -r 33de4a82c466 -r 83c991aeb611 Core/Images/PamWriter.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/PamWriter.h Mon Jul 09 08:18:22 2018 +0200 @@ -0,0 +1,51 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IImageWriter.h" + +namespace Orthanc +{ + // https://en.wikipedia.org/wiki/Netpbm#PAM_graphics_format + class PamWriter : public IImageWriter + { + protected: + virtual void WriteToMemoryInternal(std::string& target, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + }; +} diff -r 33de4a82c466 -r 83c991aeb611 Core/Images/PngWriter.cpp --- a/Core/Images/PngWriter.cpp Thu Jul 05 17:10:10 2018 +0200 +++ b/Core/Images/PngWriter.cpp Mon Jul 09 08:18:22 2018 +0200 @@ -248,7 +248,6 @@ -#if ORTHANC_SANDBOXED == 0 void PngWriter::WriteToMemoryInternal(std::string& png, unsigned int width, unsigned int height, @@ -272,5 +271,4 @@ chunks.Flatten(png); } -#endif } diff -r 33de4a82c466 -r 83c991aeb611 Core/Images/PngWriter.h --- a/Core/Images/PngWriter.h Thu Jul 05 17:10:10 2018 +0200 +++ b/Core/Images/PngWriter.h Mon Jul 09 08:18:22 2018 +0200 @@ -50,21 +50,21 @@ class PngWriter : public IImageWriter { protected: +#if ORTHANC_SANDBOXED == 0 virtual void WriteToFileInternal(const std::string& filename, unsigned int width, unsigned int height, unsigned int pitch, PixelFormat format, const void* buffer); +#endif -#if ORTHANC_SANDBOXED == 0 virtual void WriteToMemoryInternal(std::string& png, unsigned int width, unsigned int height, unsigned int pitch, PixelFormat format, const void* buffer); -#endif private: struct PImpl; diff -r 33de4a82c466 -r 83c991aeb611 NEWS --- a/NEWS Thu Jul 05 17:10:10 2018 +0200 +++ b/NEWS Mon Jul 09 08:18:22 2018 +0200 @@ -32,6 +32,9 @@ for data already in Orthanc, you'll need to reconstruct the data by sending a POST request to the ".../reconstruct" URI. This change triggered an update of ORTHANC_API_VERSION from 1.0 to 1.1 +* "/instances/.../frame/../image-uint8 and friends now accepts a + "image/pam" MIME type to retrieve images in PAM format + (https://en.wikipedia.org/wiki/Netpbm#PAM_graphics_format) Plugins ------- diff -r 33de4a82c466 -r 83c991aeb611 OrthancServer/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Thu Jul 05 17:10:10 2018 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Mon Jul 09 08:18:22 2018 +0200 @@ -364,6 +364,20 @@ DicomImageDecoder::ExtractPngImage(answer_, image_, mode_, invert_); } + void EncodeUsingPam() + { + /** + * "No Internet Media Type (aka MIME type, content type) for + * PBM has been registered with IANA, but the unofficial value + * image/x-portable-arbitrarymap is assigned by this + * specification, to be consistent with conventional values + * for the older Netpbm formats." + * http://netpbm.sourceforge.net/doc/pam.html + **/ + format_ = "image/x-portable-arbitrarymap"; + DicomImageDecoder::ExtractPamImage(answer_, image_, mode_, invert_); + } + void EncodeUsingJpeg(uint8_t quality) { format_ = "image/jpeg"; @@ -390,6 +404,25 @@ } }; + class EncodePam : public HttpContentNegociation::IHandler + { + private: + ImageToEncode& image_; + + public: + EncodePam(ImageToEncode& image) : image_(image) + { + } + + virtual void Handle(const std::string& type, + const std::string& subtype) + { + assert(type == "image"); + assert(subtype == "pam"); + image_.EncodeUsingPam(); + } + }; + class EncodeJpeg : public HttpContentNegociation::IHandler { private: @@ -524,8 +557,14 @@ ImageToEncode image(decoded, mode, invert); HttpContentNegociation negociation; - EncodePng png(image); negociation.Register("image/png", png); - EncodeJpeg jpeg(image, call); negociation.Register("image/jpeg", jpeg); + EncodePng png(image); + negociation.Register("image/png", png); + + EncodeJpeg jpeg(image, call); + negociation.Register("image/jpeg", jpeg); + + EncodePam pam(image); + negociation.Register("image/x-portable-arbitrarymap", pam); if (negociation.Apply(call.GetHttpHeaders())) { diff -r 33de4a82c466 -r 83c991aeb611 OrthancServer/main.cpp --- a/OrthancServer/main.cpp Thu Jul 05 17:10:10 2018 +0200 +++ b/OrthancServer/main.cpp Mon Jul 09 08:18:22 2018 +0200 @@ -1049,7 +1049,7 @@ database.Open(); unsigned int currentVersion = database.GetDatabaseVersion(); - + if (upgradeDatabase) { UpgradeDatabase(database, storageArea); diff -r 33de4a82c466 -r 83c991aeb611 Resources/CMake/OrthancFrameworkConfiguration.cmake --- a/Resources/CMake/OrthancFrameworkConfiguration.cmake Thu Jul 05 17:10:10 2018 +0200 +++ b/Resources/CMake/OrthancFrameworkConfiguration.cmake Mon Jul 09 08:18:22 2018 +0200 @@ -140,6 +140,8 @@ ${ORTHANC_ROOT}/Core/Images/ImageAccessor.cpp ${ORTHANC_ROOT}/Core/Images/ImageBuffer.cpp ${ORTHANC_ROOT}/Core/Images/ImageProcessing.cpp + ${ORTHANC_ROOT}/Core/Images/PamReader.cpp + ${ORTHANC_ROOT}/Core/Images/PamWriter.cpp ${ORTHANC_ROOT}/Core/JobsEngine/GenericJobUnserializer.cpp ${ORTHANC_ROOT}/Core/JobsEngine/JobInfo.cpp ${ORTHANC_ROOT}/Core/JobsEngine/JobStatus.cpp diff -r 33de4a82c466 -r 83c991aeb611 UnitTestsSources/ImageTests.cpp --- a/UnitTestsSources/ImageTests.cpp Thu Jul 05 17:10:10 2018 +0200 +++ b/UnitTestsSources/ImageTests.cpp Mon Jul 09 08:18:22 2018 +0200 @@ -41,6 +41,8 @@ #include "../Core/Images/JpegWriter.h" #include "../Core/Images/PngReader.h" #include "../Core/Images/PngWriter.h" +#include "../Core/Images/PamReader.h" +#include "../Core/Images/PamWriter.h" #include "../Core/Toolbox.h" #include "../Core/TemporaryFile.h" #include "../OrthancServer/OrthancInitialization.h" // For the FontRegistry @@ -269,3 +271,161 @@ w.WriteToFile("UnitTestsResults/font.png", s); } +TEST(PamWriter, ColorPattern) +{ + Orthanc::PamWriter w; + unsigned int width = 17; + unsigned int height = 61; + unsigned int pitch = width * 3; + + std::vector image(height * pitch); + for (unsigned int y = 0; y < height; y++) + { + uint8_t *p = &image[0] + y * pitch; + for (unsigned 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; + } + } + + Orthanc::ImageAccessor accessor; + accessor.AssignReadOnly(Orthanc::PixelFormat_RGB24, width, height, pitch, &image[0]); + + w.WriteToFile("UnitTestsResults/ColorPattern.pam", accessor); + + std::string f, md5; + Orthanc::SystemToolbox::ReadFile(f, "UnitTestsResults/ColorPattern.pam"); + Orthanc::Toolbox::ComputeMD5(md5, f); + ASSERT_EQ("81a3441754e88969ebbe53e69891e841", md5); +} + +TEST(PamWriter, Gray8Pattern) +{ + Orthanc::PamWriter 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; + } + } + + Orthanc::ImageAccessor accessor; + accessor.AssignReadOnly(Orthanc::PixelFormat_Grayscale8, width, height, pitch, &image[0]); + + w.WriteToFile("UnitTestsResults/Gray8Pattern.pam", accessor); + + std::string f, md5; + Orthanc::SystemToolbox::ReadFile(f, "UnitTestsResults/Gray8Pattern.pam"); + Orthanc::Toolbox::ComputeMD5(md5, f); + ASSERT_EQ("7873c408d26a9d11dd1c1de5e69cc0a3", md5); +} + +TEST(PamWriter, Gray16Pattern) +{ + Orthanc::PamWriter w; + int width = 256; + int height = 256; + int pitch = width * 2 + 16; + + 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; + } + } + + Orthanc::ImageAccessor accessor; + accessor.AssignReadOnly(Orthanc::PixelFormat_Grayscale16, width, height, pitch, &image[0]); + w.WriteToFile("UnitTestsResults/Gray16Pattern.pam", accessor); + + std::string f, md5; + Orthanc::SystemToolbox::ReadFile(f, "UnitTestsResults/Gray16Pattern.pam"); + Orthanc::Toolbox::ComputeMD5(md5, f); + ASSERT_EQ("b268772bf28f3b2b8520ff21c5e3dcb6", md5); +} + +TEST(PamWriter, EndToEnd) +{ + Orthanc::PamWriter w; + unsigned int width = 256; + unsigned int height = 256; + unsigned int pitch = width * 2 + 16; + + std::vector image(height * pitch); + + int v = 0; + for (unsigned int y = 0; y < height; y++) + { + uint16_t *p = reinterpret_cast(&image[0] + y * pitch); + for (unsigned int x = 0; x < width; x++, p++, v++) + { + *p = v; + } + } + + Orthanc::ImageAccessor accessor; + accessor.AssignReadOnly(Orthanc::PixelFormat_Grayscale16, width, height, pitch, &image[0]); + + std::string s; + w.WriteToMemory(s, accessor); + + { + Orthanc::PamReader r; + r.ReadFromMemory(s); + + ASSERT_EQ(r.GetFormat(), Orthanc::PixelFormat_Grayscale16); + ASSERT_EQ(r.GetWidth(), width); + ASSERT_EQ(r.GetHeight(), height); + + v = 0; + for (unsigned int y = 0; y < height; y++) + { + const uint16_t *p = reinterpret_cast + ((const uint8_t*) r.GetConstBuffer() + y * r.GetPitch()); + ASSERT_EQ(p, r.GetConstRow(y)); + for (unsigned int x = 0; x < width; x++, p++, v++) + { + ASSERT_EQ(v, *p); + } + } + } + + { + Orthanc::TemporaryFile tmp; + Orthanc::SystemToolbox::WriteFile(s, tmp.GetPath()); + + Orthanc::PamReader r2; + r2.ReadFromFile(tmp.GetPath()); + + ASSERT_EQ(r2.GetFormat(), Orthanc::PixelFormat_Grayscale16); + ASSERT_EQ(r2.GetWidth(), width); + ASSERT_EQ(r2.GetHeight(), height); + + v = 0; + for (unsigned int y = 0; y < height; y++) + { + const uint16_t *p = reinterpret_cast + ((const uint8_t*) r2.GetConstBuffer() + y * r2.GetPitch()); + ASSERT_EQ(p, r2.GetConstRow(y)); + for (unsigned int x = 0; x < width; x++, p++, v++) + { + ASSERT_EQ(*p, v); + } + } + } +} +