Mercurial > hg > orthanc-dicomweb
changeset 49:b8b1ea028da9
support of WADO
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Sun, 02 Aug 2015 11:14:19 +0200 |
parents | 42ddc9bbc168 |
children | 69b004011c4b |
files | CMakeLists.txt NEWS Orthanc/Core/ImageFormats/ImageAccessor.cpp Orthanc/Core/ImageFormats/ImageAccessor.h Orthanc/Core/ImageFormats/ImageBuffer.cpp Orthanc/Core/ImageFormats/ImageBuffer.h Orthanc/Core/ImageFormats/PngReader.cpp Orthanc/Core/ImageFormats/PngReader.h Orthanc/Resources/CMake/LibPngConfiguration.cmake Plugin/Configuration.cpp Plugin/Configuration.h Plugin/JpegWriter.cpp Plugin/JpegWriter.h Plugin/Plugin.cpp Plugin/Wado.cpp Plugin/Wado.h Resources/CMake/LibJpegConfiguration.cmake Resources/SyncOrthancFolder.py |
diffstat | 18 files changed, 1952 insertions(+), 44 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Sun Aug 02 09:34:56 2015 +0200 +++ b/CMakeLists.txt Sun Aug 02 11:14:19 2015 +0200 @@ -32,7 +32,9 @@ set(USE_SYSTEM_GDCM ON CACHE BOOL "Use the system version of Grassroot DICOM (GDCM)") set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test") set(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") -SET(USE_SYSTEM_PUGIXML ON CACHE BOOL "Use the system version of Pugixml)") +set(USE_SYSTEM_LIBJPEG ON CACHE BOOL "Use the system version of libjpeg") +set(USE_SYSTEM_LIBPNG ON CACHE BOOL "Use the system version of libpng") +SET(USE_SYSTEM_PUGIXML ON CACHE BOOL "Use the system version of Pugixml") # Distribution-specific settings set(USE_GTEST_DEBIAN_SOURCE_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") @@ -50,9 +52,11 @@ include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) include(${ORTHANC_ROOT}/Resources/CMake/GoogleTestConfiguration.cmake) include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/LibPngConfiguration.cmake) include(${ORTHANC_ROOT}/Resources/CMake/PugixmlConfiguration.cmake) include(${CMAKE_SOURCE_DIR}/Resources/CMake/GdcmConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibJpegConfiguration.cmake) if (STATIC_BUILD) @@ -101,10 +105,15 @@ set(CORE_SOURCES ${BOOST_SOURCES} ${JSONCPP_SOURCES} + ${LIBJPEG_SOURCES} + ${LIBPNG_SOURCES} ${PUGIXML_SOURCES} ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp ${ORTHANC_ROOT}/Core/Enumerations.cpp + ${ORTHANC_ROOT}/Core/ImageFormats/ImageAccessor.cpp + ${ORTHANC_ROOT}/Core/ImageFormats/ImageBuffer.cpp + ${ORTHANC_ROOT}/Core/ImageFormats/PngReader.cpp ${ORTHANC_ROOT}/Core/OrthancException.cpp ${ORTHANC_ROOT}/Core/Toolbox.cpp ${ORTHANC_ROOT}/Resources/ThirdParty/base64/base64.cpp @@ -114,12 +123,14 @@ Plugin/Configuration.cpp Plugin/Dicom.cpp Plugin/DicomResults.cpp + Plugin/JpegWriter.cpp ) add_library(OrthancDicomWeb SHARED ${CORE_SOURCES} ${CMAKE_SOURCE_DIR}/Plugin/Plugin.cpp ${CMAKE_SOURCE_DIR}/Plugin/QidoRs.cpp ${CMAKE_SOURCE_DIR}/Plugin/StowRs.cpp + ${CMAKE_SOURCE_DIR}/Plugin/Wado.cpp ${CMAKE_SOURCE_DIR}/Plugin/WadoRs.cpp ${AUTOGENERATED_SOURCES} )
--- a/NEWS Sun Aug 02 09:34:56 2015 +0200 +++ b/NEWS Sun Aug 02 11:14:19 2015 +0200 @@ -3,6 +3,7 @@ No official release yet. Still work in progress. +* Support of WADO, in addition to DICOMweb * All the APIs are now under the same root * Inject version information into Windows binaries * Use of Orthanc built-in API for multipart answers (requires Orthanc >= 0.9.1)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Core/ImageFormats/ImageAccessor.cpp Sun Aug 02 11:14:19 2015 +0200 @@ -0,0 +1,231 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "ImageAccessor.h" + +#include "../OrthancException.h" +#include "../ChunkedBuffer.h" + +#include <stdint.h> +#include <cassert> +#include <boost/lexical_cast.hpp> + +#if HAVE_GOOGLE_LOG == 1 +#include <glog/logging.h> +#endif + + +namespace Orthanc +{ + template <typename PixelType> + 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<const PixelType*>(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<std::string>(static_cast<int>(*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<const uint8_t*>(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<std::string>(static_cast<int>(*p)) + " "; + } + + target.AddChunk(s); + } + + target.AddChunk("], [ 3 " + boost::lexical_cast<std::string>(source.GetHeight()) + + " " + boost::lexical_cast<std::string>(source.GetWidth()) + " ]), [ 3 2 1 ]))"); + } + + + void* ImageAccessor::GetBuffer() const + { + if (readOnly_) + { +#if HAVE_GOOGLE_LOG == 1 + LOG(ERROR) << "Trying to write on a read-only image"; +#endif + + throw OrthancException(ErrorCode_ReadOnly); + } + + return buffer_; + } + + + const void* ImageAccessor::GetConstRow(unsigned int y) const + { + if (buffer_ != NULL) + { + return reinterpret_cast<const uint8_t*>(buffer_) + y * pitch_; + } + else + { + return NULL; + } + } + + + void* ImageAccessor::GetRow(unsigned int y) const + { + if (readOnly_) + { +#if HAVE_GOOGLE_LOG == 1 + LOG(ERROR) << "Trying to write on a read-only image"; +#endif + + throw OrthancException(ErrorCode_ReadOnly); + } + + if (buffer_ != NULL) + { + return reinterpret_cast<uint8_t*>(buffer_) + y * pitch_; + } + else + { + return NULL; + } + } + + + void ImageAccessor::AssignEmpty(PixelFormat format) + { + readOnly_ = false; + format_ = format; + width_ = 0; + height_ = 0; + pitch_ = 0; + buffer_ = NULL; + } + + + void ImageAccessor::AssignReadOnly(PixelFormat format, + unsigned int width, + unsigned int height, + unsigned int pitch, + const void *buffer) + { + readOnly_ = true; + format_ = format; + width_ = width; + height_ = height; + pitch_ = pitch; + buffer_ = const_cast<void*>(buffer); + + assert(GetBytesPerPixel() * width_ <= pitch_); + } + + + void ImageAccessor::AssignWritable(PixelFormat format, + unsigned int width, + unsigned int height, + unsigned int pitch, + void *buffer) + { + readOnly_ = false; + format_ = format; + width_ = width; + height_ = height; + pitch_ = pitch; + buffer_ = buffer; + + assert(GetBytesPerPixel() * width_ <= pitch_); + } + + + void ImageAccessor::ToMatlabString(std::string& target) const + { + ChunkedBuffer buffer; + + switch (GetFormat()) + { + case PixelFormat_Grayscale8: + ToMatlabStringInternal<uint8_t>(buffer, *this); + break; + + case PixelFormat_Grayscale16: + ToMatlabStringInternal<uint16_t>(buffer, *this); + break; + + case PixelFormat_SignedGrayscale16: + ToMatlabStringInternal<int16_t>(buffer, *this); + break; + + case PixelFormat_RGB24: + RGB24ToMatlabString(buffer, *this); + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + + buffer.Flatten(target); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Core/ImageFormats/ImageAccessor.h Sun Aug 02 11:14:19 2015 +0200 @@ -0,0 +1,119 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Enumerations.h" + +#include <string> + +namespace Orthanc +{ + class ImageAccessor + { + private: + bool readOnly_; + PixelFormat format_; + unsigned int width_; + unsigned int height_; + unsigned int pitch_; + void *buffer_; + + public: + ImageAccessor() + { + AssignEmpty(PixelFormat_Grayscale8); + } + + bool IsReadOnly() const + { + return readOnly_; + } + + PixelFormat GetFormat() const + { + return format_; + } + + unsigned int GetBytesPerPixel() const + { + return ::Orthanc::GetBytesPerPixel(format_); + } + + unsigned int GetWidth() const + { + return width_; + } + + unsigned int GetHeight() const + { + return height_; + } + + unsigned int GetPitch() const + { + return pitch_; + } + + unsigned int GetSize() const + { + return GetHeight() * GetPitch(); + } + + const void* GetConstBuffer() const + { + return buffer_; + } + + void* GetBuffer() const; + + const void* GetConstRow(unsigned int y) const; + + void* GetRow(unsigned int y) const; + + void AssignEmpty(PixelFormat format); + + void AssignReadOnly(PixelFormat format, + unsigned int width, + unsigned int height, + unsigned int pitch, + const void *buffer); + + void AssignWritable(PixelFormat format, + unsigned int width, + unsigned int height, + unsigned int pitch, + void *buffer); + + void ToMatlabString(std::string& target) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Core/ImageFormats/ImageBuffer.cpp Sun Aug 02 11:14:19 2015 +0200 @@ -0,0 +1,192 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "ImageBuffer.h" + +#include "../OrthancException.h" + +#include <stdio.h> +#include <stdlib.h> + +namespace Orthanc +{ + void ImageBuffer::Allocate() + { + if (changed_) + { + Deallocate(); + + /* + 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_ = NULL; + } + else + { + buffer_ = malloc(size); + if (buffer_ == NULL) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + + 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; + pitch_ = 0; + buffer_ = NULL; + } + + + void ImageBuffer::SetFormat(PixelFormat format) + { + if (format != format_) + { + changed_ = true; + format_ = format; + } + } + + + void ImageBuffer::SetWidth(unsigned int width) + { + if (width != width_) + { + changed_ = true; + width_ = width; + } + } + + + void ImageBuffer::SetHeight(unsigned int height) + { + if (height != height_) + { + changed_ = true; + height_ = height; + } + } + + + ImageAccessor ImageBuffer::GetAccessor() + { + Allocate(); + + ImageAccessor accessor; + accessor.AssignWritable(format_, width_, height_, pitch_, buffer_); + return accessor; + } + + + ImageAccessor ImageBuffer::GetConstAccessor() + { + Allocate(); + + ImageAccessor accessor; + 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(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Core/ImageFormats/ImageBuffer.h Sun Aug 02 11:14:19 2015 +0200 @@ -0,0 +1,115 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ImageAccessor.h" + +#include <vector> +#include <stdint.h> +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class ImageBuffer : public boost::noncopyable + { + private: + bool changed_; + + bool forceMinimalPitch_; // Currently unused + PixelFormat format_; + unsigned int width_; + unsigned int height_; + unsigned int pitch_; + void *buffer_; + + void Initialize(); + + void Allocate(); + + void Deallocate(); + + public: + ImageBuffer(unsigned int width, + unsigned int height, + PixelFormat format); + + ImageBuffer() + { + Initialize(); + } + + ~ImageBuffer() + { + Deallocate(); + } + + PixelFormat GetFormat() const + { + return format_; + } + + void SetFormat(PixelFormat format); + + unsigned int GetWidth() const + { + return width_; + } + + void SetWidth(unsigned int width); + + unsigned int GetHeight() const + { + return height_; + } + + void SetHeight(unsigned int height); + + unsigned int GetBytesPerPixel() const + { + return ::Orthanc::GetBytesPerPixel(format_); + } + + ImageAccessor GetAccessor(); + + ImageAccessor GetConstAccessor(); + + bool IsMinimalPitchForced() const + { + return forceMinimalPitch_; + } + + void SetMinimalPitchForced(bool force); + + void AcquireOwnership(ImageBuffer& other); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Core/ImageFormats/PngReader.cpp Sun Aug 02 11:14:19 2015 +0200 @@ -0,0 +1,313 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "PngReader.h" + +#include "../OrthancException.h" +#include "../Toolbox.h" + +#include <png.h> +#include <string.h> // For memcpy() + +namespace Orthanc +{ + namespace + { + struct FileRabi + { + FILE* fp_; + + FileRabi(const char* filename) + { + fp_ = fopen(filename, "rb"); + if (!fp_) + { + throw OrthancException(ErrorCode_InexistentFile); + } + } + + ~FileRabi() + { + if (fp_) + fclose(fp_); + } + }; + } + + + struct PngReader::PngRabi + { + png_structp png_; + png_infop info_; + png_infop endInfo_; + + void Destruct() + { + if (png_) + { + png_destroy_read_struct(&png_, &info_, &endInfo_); + + png_ = NULL; + info_ = NULL; + endInfo_ = NULL; + } + } + + PngRabi() + { + png_ = NULL; + info_ = NULL; + endInfo_ = NULL; + + png_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + info_ = png_create_info_struct(png_); + if (!info_) + { + png_destroy_read_struct(&png_, NULL, NULL); + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + endInfo_ = png_create_info_struct(png_); + if (!info_) + { + png_destroy_read_struct(&png_, &info_, NULL); + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + + ~PngRabi() + { + Destruct(); + } + + static void MemoryCallback(png_structp png_ptr, + png_bytep data, + png_size_t size); + }; + + + void PngReader::CheckHeader(const void* header) + { + int is_png = !png_sig_cmp((png_bytep) header, 0, 8); + if (!is_png) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + PngReader::PngReader() + { + } + + void PngReader::Read(PngRabi& rabi) + { + png_set_sig_bytes(rabi.png_, 8); + + png_read_info(rabi.png_, rabi.info_); + + png_uint_32 width, height; + int bit_depth, color_type, interlace_type; + int compression_type, filter_method; + // get size and bit-depth of the PNG-image + png_get_IHDR(rabi.png_, rabi.info_, + &width, &height, + &bit_depth, &color_type, &interlace_type, + &compression_type, &filter_method); + + PixelFormat format; + unsigned int pitch; + + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 8) + { + format = PixelFormat_Grayscale8; + pitch = width; + } + else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 16) + { + format = PixelFormat_Grayscale16; + pitch = 2 * width; + + if (Toolbox::DetectEndianness() == Endianness_Little) + { + png_set_swap(rabi.png_); + } + } + else if (color_type == PNG_COLOR_TYPE_RGB && bit_depth == 8) + { + format = PixelFormat_RGB24; + pitch = 3 * width; + } + else if (color_type == PNG_COLOR_TYPE_RGBA && bit_depth == 8) + { + format = PixelFormat_RGBA32; + pitch = 4 * width; + } + else + { + throw OrthancException(ErrorCode_NotImplemented); + } + + data_.resize(height * pitch); + + if (height == 0 || width == 0) + { + // Empty image, we are done + AssignEmpty(format); + return; + } + + png_read_update_info(rabi.png_, rabi.info_); + + std::vector<png_bytep> rows(height); + for (size_t i = 0; i < height; i++) + { + rows[i] = &data_[0] + i * pitch; + } + + png_read_image(rabi.png_, &rows[0]); + + AssignReadOnly(format, width, height, pitch, &data_[0]); + } + + void PngReader::ReadFromFile(const char* filename) + { + FileRabi f(filename); + + char header[8]; + if (fread(header, 1, 8, f.fp_) != 8) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + CheckHeader(header); + + PngRabi rabi; + + if (setjmp(png_jmpbuf(rabi.png_))) + { + rabi.Destruct(); + throw OrthancException(ErrorCode_BadFileFormat); + } + + png_init_io(rabi.png_, f.fp_); + + Read(rabi); + } + + + namespace + { + struct MemoryBuffer + { + const uint8_t* buffer_; + size_t size_; + size_t pos_; + bool ok_; + }; + } + + + void PngReader::PngRabi::MemoryCallback(png_structp png_ptr, + png_bytep outBytes, + png_size_t byteCountToRead) + { + MemoryBuffer* from = reinterpret_cast<MemoryBuffer*>(png_get_io_ptr(png_ptr)); + + if (!from->ok_) + { + return; + } + + if (from->pos_ + byteCountToRead > from->size_) + { + from->ok_ = false; + return; + } + + memcpy(outBytes, from->buffer_ + from->pos_, byteCountToRead); + + from->pos_ += byteCountToRead; + } + + + void PngReader::ReadFromMemory(const void* buffer, + size_t size) + { + if (size < 8) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + CheckHeader(buffer); + + PngRabi rabi; + + if (setjmp(png_jmpbuf(rabi.png_))) + { + rabi.Destruct(); + throw OrthancException(ErrorCode_BadFileFormat); + } + + MemoryBuffer tmp; + tmp.buffer_ = reinterpret_cast<const uint8_t*>(buffer) + 8; // We skip the header + tmp.size_ = size - 8; + tmp.pos_ = 0; + tmp.ok_ = true; + + png_set_read_fn(rabi.png_, &tmp, PngRabi::MemoryCallback); + + Read(rabi); + + if (!tmp.ok_) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + void PngReader::ReadFromMemory(const std::string& buffer) + { + if (buffer.size() != 0) + { + ReadFromMemory(&buffer[0], buffer.size()); + } + else + { + ReadFromMemory(NULL, 0); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Core/ImageFormats/PngReader.h Sun Aug 02 11:14:19 2015 +0200 @@ -0,0 +1,71 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ImageAccessor.h" + +#include "../Enumerations.h" + +#include <vector> +#include <stdint.h> +#include <boost/shared_ptr.hpp> + +namespace Orthanc +{ + class PngReader : public ImageAccessor + { + private: + struct PngRabi; + + std::vector<uint8_t> data_; + + void CheckHeader(const void* header); + + void Read(PngRabi& rabi); + + public: + PngReader(); + + void ReadFromFile(const char* filename); + + void ReadFromFile(const std::string& filename) + { + ReadFromFile(filename.c_str()); + } + + void ReadFromMemory(const void* buffer, + size_t size); + + void ReadFromMemory(const std::string& buffer); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Resources/CMake/LibPngConfiguration.cmake Sun Aug 02 11:14:19 2015 +0200 @@ -0,0 +1,61 @@ +if (STATIC_BUILD OR NOT USE_SYSTEM_LIBPNG) + SET(LIBPNG_SOURCES_DIR ${CMAKE_BINARY_DIR}/libpng-1.5.12) + DownloadPackage( + "8ea7f60347a306c5faf70b977fa80e28" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/libpng-1.5.12.tar.gz" + "${LIBPNG_SOURCES_DIR}") + + include_directories( + ${LIBPNG_SOURCES_DIR} + ) + + configure_file( + ${LIBPNG_SOURCES_DIR}/scripts/pnglibconf.h.prebuilt + ${LIBPNG_SOURCES_DIR}/pnglibconf.h + COPY_ONLY) + + set(LIBPNG_SOURCES + #${LIBPNG_SOURCES_DIR}/example.c + ${LIBPNG_SOURCES_DIR}/png.c + ${LIBPNG_SOURCES_DIR}/pngerror.c + ${LIBPNG_SOURCES_DIR}/pngget.c + ${LIBPNG_SOURCES_DIR}/pngmem.c + ${LIBPNG_SOURCES_DIR}/pngpread.c + ${LIBPNG_SOURCES_DIR}/pngread.c + ${LIBPNG_SOURCES_DIR}/pngrio.c + ${LIBPNG_SOURCES_DIR}/pngrtran.c + ${LIBPNG_SOURCES_DIR}/pngrutil.c + ${LIBPNG_SOURCES_DIR}/pngset.c + #${LIBPNG_SOURCES_DIR}/pngtest.c + ${LIBPNG_SOURCES_DIR}/pngtrans.c + ${LIBPNG_SOURCES_DIR}/pngwio.c + ${LIBPNG_SOURCES_DIR}/pngwrite.c + ${LIBPNG_SOURCES_DIR}/pngwtran.c + ${LIBPNG_SOURCES_DIR}/pngwutil.c + ) + + #set_property( + # SOURCE ${LIBPNG_SOURCES} + # PROPERTY COMPILE_FLAGS -UHAVE_CONFIG_H) + + add_definitions( + -DPNG_NO_CONSOLE_IO=1 + -DPNG_NO_STDIO=1 + # The following declaration avoids "__declspec(dllexport)" in + # libpng to prevent publicly exposing its symbols by the DLLs + -DPNG_IMPEXP= + ) + + source_group(ThirdParty\\Libpng REGULAR_EXPRESSION ${LIBPNG_SOURCES_DIR}/.*) + +else() + include(FindPNG) + + if (NOT ${PNG_FOUND}) + message(FATAL_ERROR "Unable to find LibPNG") + endif() + + include_directories(${PNG_INCLUDE_DIRS}) + link_libraries(${PNG_LIBRARIES}) + add_definitions(${PNG_DEFINITIONS}) +endif()
--- a/Plugin/Configuration.cpp Sun Aug 02 09:34:56 2015 +0200 +++ b/Plugin/Configuration.cpp Sun Aug 02 11:14:19 2015 +0200 @@ -128,10 +128,20 @@ bool RestApiGetString(std::string& result, OrthancPluginContext* context, - const std::string& uri) + const std::string& uri, + bool applyPlugins) { OrthancPluginMemoryBuffer buffer; - int code = OrthancPluginRestApiGet(context, &buffer, uri.c_str()); + int code; + if (applyPlugins) + { + code = OrthancPluginRestApiGetAfterPlugins(context, &buffer, uri.c_str()); + } + else + { + code = OrthancPluginRestApiGet(context, &buffer, uri.c_str()); + } + if (code) { // Error @@ -242,20 +252,11 @@ std::string GetRoot(const Json::Value& configuration) { - std::string root; - - if (configuration.isMember("DicomWeb")) - { - root = GetStringValue(configuration["DicomWeb"], "Root", ""); - } - - if (root.empty()) - { - root = "/dicom-web/"; - } + std::string root = GetStringValue(configuration, "Root", "/dicom-web/"); // Make sure the root URI starts and ends with a slash - if (root[0] != '/') + if (root.size() == 0 || + root[0] != '/') { root = "/" + root; } @@ -269,17 +270,32 @@ } + std::string GetWadoRoot(const Json::Value& configuration) + { + std::string root = GetStringValue(configuration, "WadoRoot", "/wado/"); + + // Make sure the root URI starts with a slash + if (root.size() == 0 || + root[0] != '/') + { + root = "/" + root; + } + + // Remove the trailing slash, if any + if (root[root.length() - 1] == '/') + { + root = root.substr(0, root.length() - 1); + } + + return root; + } + + std::string GetBaseUrl(const Json::Value& configuration, const OrthancPluginHttpRequest* request) { - std::string host; - bool ssl = false; - - if (configuration.isMember("DicomWeb")) - { - host = GetStringValue(configuration["DicomWeb"], "Host", ""); - ssl = GetBoolValue(configuration["DicomWeb"], "Ssl", false); - } + std::string host = GetStringValue(configuration, "Host", ""); + bool ssl = GetBoolValue(configuration, "Ssl", false); if (host.empty() && !LookupHttpHeader(host, request, "host"))
--- a/Plugin/Configuration.h Sun Aug 02 09:34:56 2015 +0200 +++ b/Plugin/Configuration.h Sun Aug 02 11:14:19 2015 +0200 @@ -47,7 +47,8 @@ bool RestApiGetString(std::string& result, OrthancPluginContext* context, - const std::string& uri); + const std::string& uri, + bool applyPlugins = false); bool RestApiGetJson(Json::Value& result, OrthancPluginContext* context, @@ -68,6 +69,8 @@ std::string GetRoot(const Json::Value& configuration); + std::string GetWadoRoot(const Json::Value& configuration); + std::string GetBaseUrl(const Json::Value& configuration, const OrthancPluginHttpRequest* request); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/JpegWriter.cpp Sun Aug 02 11:14:19 2015 +0200 @@ -0,0 +1,246 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "JpegWriter.h" + +#include "../Orthanc/Core/OrthancException.h" + +#include <jpeglib.h> +#include <setjmp.h> +#include <stdio.h> +#include <vector> +#include <string.h> +#include <stdlib.h> + +namespace OrthancPlugins +{ + namespace + { + class ErrorManager + { + private: + struct jpeg_error_mgr pub; /* "public" fields */ + jmp_buf setjmp_buffer; /* for return to caller */ + std::string message; + + static void OutputMessage(j_common_ptr cinfo) + { + char message[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message) (cinfo, message); + + ErrorManager* that = reinterpret_cast<ErrorManager*>(cinfo->err); + that->message = std::string(message); + } + + + static void ErrorExit(j_common_ptr cinfo) + { + (*cinfo->err->output_message) (cinfo); + + ErrorManager* that = reinterpret_cast<ErrorManager*>(cinfo->err); + longjmp(that->setjmp_buffer, 1); + } + + + public: + ErrorManager() + { + memset(&pub, 0, sizeof(struct jpeg_error_mgr)); + memset(&setjmp_buffer, 0, sizeof(jmp_buf)); + + jpeg_std_error(&pub); + pub.error_exit = ErrorExit; + pub.output_message = OutputMessage; + } + + struct jpeg_error_mgr* GetPublic() + { + return &pub; + } + + jmp_buf& GetJumpBuffer() + { + return setjmp_buffer; + } + + const std::string& GetMessage() const + { + return message; + } + }; + } + + + static void GetLines(std::vector<uint8_t*>& lines, + unsigned int height, + unsigned int pitch, + Orthanc::PixelFormat format, + const void* buffer) + { + if (format != Orthanc::PixelFormat_Grayscale8 && + format != Orthanc::PixelFormat_RGB24) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + lines.resize(height); + + uint8_t* base = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer)); + for (unsigned int y = 0; y < height; y++) + { + lines[y] = base + static_cast<intptr_t>(y) * static_cast<intptr_t>(pitch); + } + } + + + static void Compress(struct jpeg_compress_struct& cinfo, + std::vector<uint8_t*>& lines, + unsigned int width, + unsigned int height, + Orthanc::PixelFormat format, + int quality) + { + cinfo.image_width = width; + cinfo.image_height = height; + + switch (format) + { + case Orthanc::PixelFormat_Grayscale8: + cinfo.input_components = 1; + cinfo.in_color_space = JCS_GRAYSCALE; + break; + + case Orthanc::PixelFormat_RGB24: + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, TRUE); + jpeg_start_compress(&cinfo, TRUE); + jpeg_write_scanlines(&cinfo, &lines[0], height); + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + } + + + void JpegWriter::SetQuality(uint8_t quality) + { + if (quality <= 0 || quality > 100) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + quality_ = quality; + } + + + void JpegWriter::WriteToFile(const char* filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + Orthanc::PixelFormat format, + const void* buffer) + { + FILE* fp = fopen(filename, "wb"); + if (fp == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_FullStorage); + } + + std::vector<uint8_t*> lines; + GetLines(lines, height, pitch, format, buffer); + + struct jpeg_compress_struct cinfo; + memset(&cinfo, 0, sizeof(struct jpeg_compress_struct)); + + ErrorManager jerr; + cinfo.err = jerr.GetPublic(); + + if (setjmp(jerr.GetJumpBuffer())) + { + /* If we get here, the JPEG code has signaled an error. + * We need to clean up the JPEG object, close the input file, and return. + */ + jpeg_destroy_compress(&cinfo); + fclose(fp); + throw Orthanc::OrthancException("Error during JPEG encoding: " + jerr.GetMessage()); + } + + // Do not allocate data on the stack below this line! + + jpeg_create_compress(&cinfo); + jpeg_stdio_dest(&cinfo, fp); + Compress(cinfo, lines, width, height, format, quality_); + + // Everything went fine, "setjmp()" didn't get called + + fclose(fp); + } + + + void JpegWriter::WriteToMemory(std::string& jpeg, + unsigned int width, + unsigned int height, + unsigned int pitch, + Orthanc::PixelFormat format, + const void* buffer) + { + std::vector<uint8_t*> lines; + GetLines(lines, height, pitch, format, buffer); + + struct jpeg_compress_struct cinfo; + memset(&cinfo, 0, sizeof(struct jpeg_compress_struct)); + + ErrorManager jerr; + + unsigned char* data = NULL; + unsigned long size; + + if (setjmp(jerr.GetJumpBuffer())) + { + jpeg_destroy_compress(&cinfo); + + if (data != NULL) + { + free(data); + } + + throw Orthanc::OrthancException("Error during JPEG encoding: " + jerr.GetMessage()); + } + + // Do not allocate data on the stack below this line! + + jpeg_create_compress(&cinfo); + cinfo.err = jerr.GetPublic(); + jpeg_mem_dest(&cinfo, &data, &size); + + Compress(cinfo, lines, width, height, format, quality_); + + // Everything went fine, "setjmp()" didn't get called + + jpeg.assign(reinterpret_cast<const char*>(data), size); + free(data); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/JpegWriter.h Sun Aug 02 11:14:19 2015 +0200 @@ -0,0 +1,75 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Orthanc/Core/ImageFormats/ImageAccessor.h" + +#include <string> +#include <stdint.h> + +namespace OrthancPlugins +{ + class JpegWriter + { + private: + int quality_; + + public: + JpegWriter() : quality_(90) + { + } + + void SetQuality(uint8_t quality); + + uint8_t GetQuality() const + { + return quality_; + } + + void WriteToFile(const char* filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + Orthanc::PixelFormat format, + const void* buffer); + + void WriteToMemory(std::string& jpeg, + unsigned int width, + unsigned int height, + unsigned int pitch, + Orthanc::PixelFormat format, + const void* buffer); + + void WriteToFile(const char* filename, + const Orthanc::ImageAccessor& accessor) + { + WriteToFile(filename, accessor.GetWidth(), accessor.GetHeight(), + accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); + } + + void WriteToMemory(std::string& jpeg, + const Orthanc::ImageAccessor& accessor) + { + WriteToMemory(jpeg, accessor.GetWidth(), accessor.GetHeight(), + accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); + } + }; +}
--- a/Plugin/Plugin.cpp Sun Aug 02 09:34:56 2015 +0200 +++ b/Plugin/Plugin.cpp Sun Aug 02 11:14:19 2015 +0200 @@ -23,6 +23,7 @@ #include "QidoRs.h" #include "StowRs.h" #include "WadoRs.h" +#include "Wado.h" #include "Configuration.h" @@ -120,38 +121,70 @@ } + OrthancPluginSetDescription(context_, "Implementation of DICOM Web (QIDO-RS, STOW-RS and WADO-RS) and WADO."); + + // Read the configuration dictionary_ = &gdcm::Global::GetInstance().GetDicts().GetPublicDict(); + configuration_ = Json::objectValue; - if (!OrthancPlugins::Configuration::Read(configuration_, context) || - configuration_.type() != Json::objectValue) { - OrthancPluginLogError(context_, "Unable to read the configuration file"); - return -1; + Json::Value tmp; + if (!OrthancPlugins::Configuration::Read(tmp, context) || + tmp.type() != Json::objectValue) + { + OrthancPluginLogError(context_, "Unable to read the configuration file"); + return -1; + } + + if (tmp.isMember("DicomWeb") && + tmp["DicomWeb"].type() == Json::objectValue) + { + configuration_ = tmp["DicomWeb"]; + } } - std::string root = OrthancPlugins::Configuration::GetRoot(configuration_); + // Configure the DICOMweb callbacks + if (OrthancPlugins::Configuration::GetBoolValue(configuration_, "Enable", true)) + { + std::string root = OrthancPlugins::Configuration::GetRoot(configuration_); - { std::string message = "URI to the DICOMweb REST API: " + root; OrthancPluginLogWarning(context_, message.c_str()); + + Register(root, "instances", SearchForInstances); + Register(root, "series", SearchForSeries); + Register(root, "studies", SwitchStudies); + Register(root, "studies/([^/]*)", SwitchStudy); + Register(root, "studies/([^/]*)/instances", SearchForInstances); + Register(root, "studies/([^/]*)/metadata", RetrieveStudyMetadata); + Register(root, "studies/([^/]*)/series", SearchForSeries); + Register(root, "studies/([^/]*)/series/([^/]*)", RetrieveDicomSeries); + Register(root, "studies/([^/]*)/series/([^/]*)/instances", SearchForInstances); + Register(root, "studies/([^/]*)/series/([^/]*)/instances/([^/]*)", RetrieveDicomInstance); + Register(root, "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/bulk/(.*)", RetrieveBulkData); + Register(root, "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/metadata", RetrieveInstanceMetadata); + Register(root, "studies/([^/]*)/series/([^/]*)/metadata", RetrieveSeriesMetadata); + } + else + { + OrthancPluginLogWarning(context_, "DICOMweb support is disabled"); } - OrthancPluginSetDescription(context_, "Implementation of DICOM Web (QIDO-RS, STOW-RS and WADO-RS)."); + // Configure the WADO callback + if (OrthancPlugins::Configuration::GetBoolValue(configuration_, "EnableWado", true)) + { + std::string wado = OrthancPlugins::Configuration::GetWadoRoot(configuration_); - Register(root, "instances", SearchForInstances); - Register(root, "series", SearchForSeries); - Register(root, "studies", SwitchStudies); - Register(root, "studies/([^/]*)", SwitchStudy); - Register(root, "studies/([^/]*)/instances", SearchForInstances); - Register(root, "studies/([^/]*)/metadata", RetrieveStudyMetadata); - Register(root, "studies/([^/]*)/series", SearchForSeries); - Register(root, "studies/([^/]*)/series/([^/]*)", RetrieveDicomSeries); - Register(root, "studies/([^/]*)/series/([^/]*)/instances", SearchForInstances); - Register(root, "studies/([^/]*)/series/([^/]*)/instances/([^/]*)", RetrieveDicomInstance); - Register(root, "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/bulk/(.*)", RetrieveBulkData); - Register(root, "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/metadata", RetrieveInstanceMetadata); - Register(root, "studies/([^/]*)/series/([^/]*)/metadata", RetrieveSeriesMetadata); + std::string message = "URI to the WADO API: " + wado; + OrthancPluginLogWarning(context_, message.c_str()); + + OrthancPluginRegisterRestCallback(context_, wado.c_str(), WadoCallback); + } + else + { + OrthancPluginLogWarning(context_, "WADO support is disabled"); + } return 0; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/Wado.cpp Sun Aug 02 11:14:19 2015 +0200 @@ -0,0 +1,283 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "Wado.h" +#include "Plugin.h" + +#include "../Orthanc/Core/OrthancException.h" +#include "../Orthanc/Core/ImageFormats/PngReader.h" +#include "JpegWriter.h" +#include "Configuration.h" + +#include <string> + +static bool MapWadoToOrthancIdentifier(std::string& orthanc, + char* (*func) (OrthancPluginContext*, const char*), + const std::string& dicom) +{ + char* tmp = func(context_, dicom.c_str()); + + if (tmp) + { + orthanc = tmp; + OrthancPluginFreeString(context_, tmp); + return true; + } + else + { + return false; + } +} + + +static bool LocateInstance(std::string& instance, + std::string& contentType, + const OrthancPluginHttpRequest* request) +{ + std::string requestType, studyUid, seriesUid, objectUid; + + for (uint32_t i = 0; i < request->getCount; i++) + { + std::string key(request->getKeys[i]); + std::string value(request->getValues[i]); + + if (key == "studyUID") + { + studyUid = value; + } + else if (key == "seriesUID") + { + seriesUid = value; + } + else if (key == "objectUID") // In WADO, "objectUID" corresponds to "SOPInstanceUID" + { + objectUid = value; + } + else if (key == "requestType") + { + requestType = value; + } + else if (key == "contentType") + { + contentType = value; + } + } + + if (requestType != "WADO") + { + std::string msg = "WADO: Invalid requestType: \"" + requestType + "\""; + OrthancPluginLogError(context_, msg.c_str()); + return false; + } + + if (objectUid.empty()) + { + OrthancPluginLogError(context_, "WADO: No SOPInstanceUID provided"); + return false; + } + + if (!MapWadoToOrthancIdentifier(instance, OrthancPluginLookupInstance, objectUid)) + { + std::string msg = "WADO: No such SOPInstanceUID in Orthanc: \"" + objectUid + "\""; + OrthancPluginLogError(context_, msg.c_str()); + return false; + } + + /** + * Below are only sanity checks to ensure that the possibly provided + * "seriesUID" and "studyUID" match that of the provided instance. + **/ + + if (!seriesUid.empty()) + { + std::string series; + if (!MapWadoToOrthancIdentifier(series, OrthancPluginLookupSeries, seriesUid)) + { + std::string msg = "WADO: No such SeriesInstanceUID in Orthanc: \"" + seriesUid + "\""; + OrthancPluginLogError(context_, msg.c_str()); + return false; + } + else + { + Json::Value info; + if (!OrthancPlugins::RestApiGetJson(info, context_, "/instances/" + instance + "/series") || + info["MainDicomTags"]["SeriesInstanceUID"] != seriesUid) + { + std::string msg = "WADO: Instance " + objectUid + " does not belong to series " + seriesUid; + OrthancPluginLogError(context_, msg.c_str()); + return false; + } + } + } + + if (!studyUid.empty()) + { + std::string study; + if (!MapWadoToOrthancIdentifier(study, OrthancPluginLookupStudy, studyUid)) + { + std::string msg = "WADO: No such StudyInstanceUID in Orthanc: \"" + studyUid + "\""; + OrthancPluginLogError(context_, msg.c_str()); + return false; + } + else + { + Json::Value info; + if (!OrthancPlugins::RestApiGetJson(info, context_, "/instances/" + instance + "/study") || + info["MainDicomTags"]["StudyInstanceUID"] != studyUid) + { + std::string msg = "WADO: Instance " + objectUid + " does not belong to study " + studyUid; + OrthancPluginLogError(context_, msg.c_str()); + return false; + } + } + } + + return true; +} + + +static int32_t AnswerDicom(OrthancPluginRestOutput* output, + const std::string& instance) +{ + std::string uri = "/instances/" + instance + "/file"; + + std::string dicom; + if (OrthancPlugins::RestApiGetString(dicom, context_, uri)) + { + OrthancPluginAnswerBuffer(context_, output, dicom.c_str(), dicom.size(), "application/dicom"); + return 0; + } + else + { + std::string msg = "WADO: Unable to retrieve DICOM file from " + uri; + OrthancPluginLogError(context_, msg.c_str()); + return -1; + } +} + + +static bool RetrievePngPreview(std::string& png, + const std::string& instance) +{ + std::string uri = "/instances/" + instance + "/preview"; + + if (OrthancPlugins::RestApiGetString(png, context_, uri)) + { + return true; + } + else + { + std::string msg = "WADO: Unable to generate a preview image for " + uri; + OrthancPluginLogError(context_, msg.c_str()); + return false; + } +} + + +static int32_t AnswerPngPreview(OrthancPluginRestOutput* output, + const std::string& instance) +{ + std::string png; + if (!RetrievePngPreview(png, instance)) + { + return -1; + } + + OrthancPluginAnswerBuffer(context_, output, png.c_str(), png.size(), "image/png"); + return 0; +} + + +static int32_t AnswerJpegPreview(OrthancPluginRestOutput* output, + const std::string& instance) +{ + // Retrieve the preview in the PNG format + std::string png; + if (!RetrievePngPreview(png, instance)) + { + return -1; + } + + // Decode the PNG file + Orthanc::PngReader reader; + reader.ReadFromMemory(png); + + // Convert to JPEG + OrthancPlugins::JpegWriter writer; + std::string jpeg; + writer.WriteToMemory(jpeg, reader); + + OrthancPluginAnswerBuffer(context_, output, jpeg.c_str(), jpeg.size(), "image/jpeg"); + return 0; +} + + +int32_t WadoCallback(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + try + { + if (request->method != OrthancPluginHttpMethod_Get) + { + OrthancPluginSendMethodNotAllowed(context_, output, "GET"); + return -1; + } + + std::string instance; + std::string contentType = "image/jpg"; // By default, JPEG image will be returned + if (!LocateInstance(instance, contentType, request)) + { + return -1; + } + + if (contentType == "application/dicom") + { + return AnswerDicom(output, instance); + } + else if (contentType == "image/png") + { + return AnswerPngPreview(output, instance); + } + else if (contentType == "image/jpeg" || + contentType == "image/jpg") + { + return AnswerJpegPreview(output, instance); + } + else + { + std::string msg = "WADO: Unsupported content type: \"" + contentType + "\""; + OrthancPluginLogError(context_, msg.c_str()); + return -1; + } + + return 0; + } + catch (Orthanc::OrthancException& e) + { + OrthancPluginLogError(context_, e.What()); + return -1; + } + catch (std::runtime_error& e) + { + OrthancPluginLogError(context_, e.what()); + return -1; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/Wado.h Sun Aug 02 11:14:19 2015 +0200 @@ -0,0 +1,27 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <orthanc/OrthancCPlugin.h> + +int32_t WadoCallback(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/LibJpegConfiguration.cmake Sun Aug 02 11:14:19 2015 +0200 @@ -0,0 +1,104 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# 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 +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +if (STATIC_BUILD OR NOT USE_SYSTEM_LIBJPEG) + set(LIBJPEG_SOURCES_DIR ${CMAKE_BINARY_DIR}/jpeg-9a) + DownloadPackage( + "3353992aecaee1805ef4109aadd433e7" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jpegsrc.v9a.tar.gz" + "${LIBJPEG_SOURCES_DIR}") + + include_directories( + ${LIBJPEG_SOURCES_DIR}/ + ) + + list(APPEND LIBJPEG_SOURCES + ${LIBJPEG_SOURCES_DIR}/jaricom.c + ${LIBJPEG_SOURCES_DIR}/jcapimin.c + ${LIBJPEG_SOURCES_DIR}/jcapistd.c + ${LIBJPEG_SOURCES_DIR}/jcarith.c + ${LIBJPEG_SOURCES_DIR}/jccoefct.c + ${LIBJPEG_SOURCES_DIR}/jccolor.c + ${LIBJPEG_SOURCES_DIR}/jcdctmgr.c + ${LIBJPEG_SOURCES_DIR}/jchuff.c + ${LIBJPEG_SOURCES_DIR}/jcinit.c + ${LIBJPEG_SOURCES_DIR}/jcmarker.c + ${LIBJPEG_SOURCES_DIR}/jcmaster.c + ${LIBJPEG_SOURCES_DIR}/jcomapi.c + ${LIBJPEG_SOURCES_DIR}/jcparam.c + ${LIBJPEG_SOURCES_DIR}/jcprepct.c + ${LIBJPEG_SOURCES_DIR}/jcsample.c + ${LIBJPEG_SOURCES_DIR}/jctrans.c + ${LIBJPEG_SOURCES_DIR}/jdapimin.c + ${LIBJPEG_SOURCES_DIR}/jdapistd.c + ${LIBJPEG_SOURCES_DIR}/jdarith.c + ${LIBJPEG_SOURCES_DIR}/jdatadst.c + ${LIBJPEG_SOURCES_DIR}/jdatasrc.c + ${LIBJPEG_SOURCES_DIR}/jdcoefct.c + ${LIBJPEG_SOURCES_DIR}/jdcolor.c + ${LIBJPEG_SOURCES_DIR}/jddctmgr.c + ${LIBJPEG_SOURCES_DIR}/jdhuff.c + ${LIBJPEG_SOURCES_DIR}/jdinput.c + ${LIBJPEG_SOURCES_DIR}/jcmainct.c + ${LIBJPEG_SOURCES_DIR}/jdmainct.c + ${LIBJPEG_SOURCES_DIR}/jdmarker.c + ${LIBJPEG_SOURCES_DIR}/jdmaster.c + ${LIBJPEG_SOURCES_DIR}/jdmerge.c + ${LIBJPEG_SOURCES_DIR}/jdpostct.c + ${LIBJPEG_SOURCES_DIR}/jdsample.c + ${LIBJPEG_SOURCES_DIR}/jdtrans.c + ${LIBJPEG_SOURCES_DIR}/jerror.c + ${LIBJPEG_SOURCES_DIR}/jfdctflt.c + ${LIBJPEG_SOURCES_DIR}/jfdctfst.c + ${LIBJPEG_SOURCES_DIR}/jfdctint.c + ${LIBJPEG_SOURCES_DIR}/jidctflt.c + ${LIBJPEG_SOURCES_DIR}/jidctfst.c + ${LIBJPEG_SOURCES_DIR}/jidctint.c + #${LIBJPEG_SOURCES_DIR}/jmemansi.c + #${LIBJPEG_SOURCES_DIR}/jmemdos.c + #${LIBJPEG_SOURCES_DIR}/jmemmac.c + ${LIBJPEG_SOURCES_DIR}/jmemmgr.c + #${LIBJPEG_SOURCES_DIR}/jmemname.c + ${LIBJPEG_SOURCES_DIR}/jmemnobs.c + ${LIBJPEG_SOURCES_DIR}/jquant1.c + ${LIBJPEG_SOURCES_DIR}/jquant2.c + ${LIBJPEG_SOURCES_DIR}/jutils.c + + # ${LIBJPEG_SOURCES_DIR}/rdbmp.c + # ${LIBJPEG_SOURCES_DIR}/rdcolmap.c + # ${LIBJPEG_SOURCES_DIR}/rdgif.c + # ${LIBJPEG_SOURCES_DIR}/rdppm.c + # ${LIBJPEG_SOURCES_DIR}/rdrle.c + # ${LIBJPEG_SOURCES_DIR}/rdswitch.c + # ${LIBJPEG_SOURCES_DIR}/rdtarga.c + # ${LIBJPEG_SOURCES_DIR}/transupp.c + # ${LIBJPEG_SOURCES_DIR}/wrbmp.c + # ${LIBJPEG_SOURCES_DIR}/wrgif.c + # ${LIBJPEG_SOURCES_DIR}/wrppm.c + # ${LIBJPEG_SOURCES_DIR}/wrrle.c + # ${LIBJPEG_SOURCES_DIR}/wrtarga.c + ) + + configure_file( + ${LIBJPEG_SOURCES_DIR}/jconfig.txt + ${LIBJPEG_SOURCES_DIR}/jconfig.h COPYONLY + ) + +else() + link_libraries(jpeg) +endif()
--- a/Resources/SyncOrthancFolder.py Sun Aug 02 09:34:56 2015 +0200 +++ b/Resources/SyncOrthancFolder.py Sun Aug 02 11:14:19 2015 +0200 @@ -20,6 +20,12 @@ 'Core/ChunkedBuffer.h', 'Core/Enumerations.cpp', 'Core/Enumerations.h', + 'Core/ImageFormats/ImageAccessor.cpp', + 'Core/ImageFormats/ImageAccessor.h', + 'Core/ImageFormats/ImageBuffer.cpp', + 'Core/ImageFormats/ImageBuffer.h', + 'Core/ImageFormats/PngReader.cpp', + 'Core/ImageFormats/PngReader.h', 'Core/OrthancException.cpp', 'Core/OrthancException.h', 'Core/PrecompiledHeaders.h', @@ -31,6 +37,7 @@ 'Resources/CMake/DownloadPackage.cmake', 'Resources/CMake/GoogleTestConfiguration.cmake', 'Resources/CMake/JsonCppConfiguration.cmake', + 'Resources/CMake/LibPngConfiguration.cmake', 'Resources/CMake/PugixmlConfiguration.cmake', 'Resources/MinGW-W64-Toolchain32.cmake', 'Resources/MinGW-W64-Toolchain64.cmake',