Mercurial > hg > orthanc-wsi
changeset 0:4a7a53257c7d
initial commit
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AUTHORS Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,13 @@ +Orthanc for Whole-Slide Imaging +=============================== + + +Authors +------- + +* Sebastien Jodogne <s.jodogne@gmail.com> + Department of Medical Physics + University Hospital of Liege + Belgium + + Overall design and lead developer.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/ApplicationToolbox.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,174 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "ApplicationToolbox.h" + +#include "../Framework/Inputs/OpenSlideLibrary.h" +#include "../Framework/Orthanc/Core/HttpClient.h" +#include "../Framework/Orthanc/Core/Logging.h" +#include "../Framework/Orthanc/Core/MultiThreading/BagOfTasksProcessor.h" +#include "../Framework/Orthanc/OrthancServer/FromDcmtkBridge.h" + +#include <boost/lexical_cast.hpp> +#include <boost/regex.hpp> + +namespace OrthancWSI +{ + namespace ApplicationToolbox + { + void GlobalInitialize() + { + Orthanc::Logging::Initialize(); + Orthanc::HttpClient::InitializeOpenSsl(); + Orthanc::HttpClient::GlobalInitialize(); + Orthanc::FromDcmtkBridge::InitializeDictionary(); + } + + + void GlobalFinalize() + { + OrthancWSI::OpenSlideLibrary::Finalize(); + Orthanc::HttpClient::GlobalFinalize(); + Orthanc::HttpClient::FinalizeOpenSsl(); + } + + + static void PrintProgress(Orthanc::BagOfTasksProcessor::Handle* handle, + bool* done) + { + unsigned int previous = 0; + + while (!*done) + { + unsigned int progress = static_cast<unsigned int>(100.0f * handle->GetProgress()); + if (previous != progress) + { + LOG(WARNING) << "Progress: " << progress << "%"; + previous = progress; + } + + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + } + } + + + void Execute(Orthanc::BagOfTasks& tasks, + unsigned int threadsCount) + { + if (threadsCount > 1) + { + LOG(WARNING) << "Running " << tasks.GetSize() << " tasks"; + LOG(WARNING) << "Using " << threadsCount << " threads for the computation"; + Orthanc::BagOfTasksProcessor processor(threadsCount); + std::auto_ptr<Orthanc::BagOfTasksProcessor::Handle> handle(processor.Submit(tasks)); + + bool done = false; + boost::thread progress(PrintProgress, handle.get(), &done); + + if (handle->Join()) + { + done = true; + LOG(WARNING) << "All tasks have finished"; + } + else + { + done = true; + LOG(ERROR) << "Error has occurred, aborting"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (progress.joinable()) + { + progress.join(); + } + } + else + { + LOG(WARNING) << "Running " << tasks.GetSize() << " tasks without multithreading"; + + unsigned int previous = 0; + unsigned int size = tasks.GetSize(); + + // No multithreading + while (!tasks.IsEmpty()) + { + std::auto_ptr<Orthanc::ICommand> task(tasks.Pop()); + if (task->Execute()) + { + unsigned int progress = static_cast<unsigned int>(100.0f * + static_cast<float>((size - tasks.GetSize())) / + static_cast<float>(size)); + if (progress != previous) + { + LOG(WARNING) << "Progress: " << progress << "%"; + previous = progress; + } + } + else + { + LOG(ERROR) << "Error has occurred, aborting"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + } + } + + + void ParseColor(uint8_t& red, + uint8_t& green, + uint8_t& blue, + const std::string& color) + { + boost::regex pattern("([0-9]*),([0-9]*),([0-9]*)"); + + bool ok = false; + boost::cmatch what; + + try + { + if (regex_match(color.c_str(), what, pattern)) + { + int r = boost::lexical_cast<int>(what[1]); + int g = boost::lexical_cast<int>(what[2]); + int b = boost::lexical_cast<int>(what[3]); + + if (r >= 0 && r <= 255 && + g >= 0 && g <= 255 && + b >= 0 && b <= 255) + { + red = static_cast<uint8_t>(r); + green = static_cast<uint8_t>(g); + blue = static_cast<uint8_t>(b); + ok = true; + } + } + } + catch (boost::bad_lexical_cast&) + { + } + + if (!ok) + { + LOG(ERROR) << "Bad color specification: " << color; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/ApplicationToolbox.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,44 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "../Framework/Orthanc/Core/MultiThreading/BagOfTasks.h" + +#include <string> +#include <stdint.h> + +namespace OrthancWSI +{ + namespace ApplicationToolbox + { + void GlobalInitialize(); + + void GlobalFinalize(); + + void Execute(Orthanc::BagOfTasks& tasks, + unsigned int threadsCount); + + void ParseColor(uint8_t& red, + uint8_t& green, + uint8_t& blue, + const std::string& color); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/CMakeLists.txt Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,232 @@ +cmake_minimum_required(VERSION 2.8) +project(OrthancWSIApplications) + + +##################################################################### +## Parameters of the build +##################################################################### + +# Generic parameters +SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") +SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") + +# Optional components +SET(ENABLE_SSL OFF CACHE BOOL "Include support for SSL") +SET(USE_DCMTK_361 OFF CACHE BOOL "Use forthcoming DCMTK version 3.6.1 in static builds (instead of 3.6.0)") + +if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(TMP ON) +else() + set(TMP OFF) +endif() + + +# Advanced parameters to fine-tune linking against system libraries +SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost") +SET(USE_SYSTEM_CURL ON CACHE BOOL "Use the system version of LibCurl") +SET(USE_SYSTEM_DCMTK ON CACHE BOOL "Use the system version of DCMTK") +SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") +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_LIBTIFF ON CACHE BOOL "Use the system version of libtiff") +SET(USE_SYSTEM_OPENJPEG ON CACHE BOOL "Use the system version of OpenJpeg") +SET(USE_SYSTEM_OPENSSL ON CACHE BOOL "Use the system version of OpenSSL") +SET(USE_SYSTEM_ZLIB ON CACHE BOOL "Use the system version of ZLib") + +SET(DCMTK_DICTIONARY_DIR "" CACHE PATH "Directory containing the DCMTK dictionaries \"dicom.dic\" and \"private.dic\" (only when using system version of DCMTK)") + + +##################################################################### +## Configure mandatory third-party components +##################################################################### + +SET(ORTHANC_WSI_DIR ${CMAKE_CURRENT_LIST_DIR}/..) +SET(ORTHANC_ROOT ${ORTHANC_WSI_DIR}/Framework/Orthanc) +SET(USE_OPENJPEG_JP2 ON) +SET(ENABLE_JPEG OFF) # Disable DCMTK's support for JPEG, that clashes with libtiff +SET(ENABLE_JPEG_LOSSLESS OFF) # Disable DCMTK's support for JPEG-LS +SET(ENABLE_DCMTK_NETWORK OFF) # Disable DCMTK's support for DICOM networking +SET(STANDALONE_BUILD ON) # Embed DCMTK's dictionaries for static builds + +include(CheckIncludeFiles) +include(CheckIncludeFileCXX) +include(CheckLibraryExists) +include(FindPythonInterp) +include(FindPkgConfig) + +include(${ORTHANC_ROOT}/Resources/CMake/Compiler.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/AutoGeneratedCode.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) + +# Third-party components shipped with Orthanc +include(${ORTHANC_ROOT}/Resources/CMake/DcmtkConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/LibCurlConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/LibJpegConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/LibPngConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/ZlibConfiguration.cmake) + +include(${ORTHANC_WSI_DIR}/Resources/CMake/BoostExtendedConfiguration.cmake) +include(${ORTHANC_WSI_DIR}/Resources/CMake/OpenJpegConfiguration.cmake) +include(${ORTHANC_WSI_DIR}/Resources/CMake/LibTiffConfiguration.cmake) + +add_definitions( + -DORTHANC_ENABLE_BASE64=1 + -DORTHANC_ENABLE_CURL=1 + -DORTHANC_ENABLE_DCMTK=1 + -DORTHANC_ENABLE_LOGGING=1 + -DORTHANC_ENABLE_MD5=0 + -DORTHANC_JPEG_ENABLED=0 # Disable DCMTK's support for JPEG + -DORTHANC_PKCS11_ENABLED=0 + -DORTHANC_PLUGINS_ENABLED=1 # To enable class Orthanc::SharedLibrary + -DORTHANC_PUGIXML_ENABLED=0 + ) + + +##################################################################### +## Configure optional third-party components +##################################################################### + +if (ENABLE_SSL) + set(ENABLE_PKCS11 OFF) + add_definitions(-DORTHANC_SSL_ENABLED=1) + include(${ORTHANC_ROOT}/Resources/CMake/OpenSslConfiguration.cmake) +else() + add_definitions(-DORTHANC_SSL_ENABLED=0) +endif() + + +##################################################################### +## Create the static library containing the framework +##################################################################### + +EmbedResources( + ${DCMTK_DICTIONARIES} + BRIGHTFIELD_OPTICAL_PATH ${ORTHANC_WSI_DIR}/Resources/BrightfieldOpticalPath.json + SAMPLE_DATASET ${ORTHANC_WSI_DIR}/Resources/SampleDataset.json + SRGB_ICC_PROFILE ${ORTHANC_WSI_DIR}/Resources/sRGB.icc + ) + +add_library(OrthancWSIFramework STATIC + #${ORTHANC_WSI_DIR}/Framework/Messaging/PluginOrthancConnection.cpp + ${ORTHANC_WSI_DIR}/Framework/Algorithms/PyramidReader.cpp + ${ORTHANC_WSI_DIR}/Framework/Algorithms/ReconstructPyramidCommand.cpp + ${ORTHANC_WSI_DIR}/Framework/Algorithms/TranscodeTileCommand.cpp + ${ORTHANC_WSI_DIR}/Framework/DicomToolbox.cpp + ${ORTHANC_WSI_DIR}/Framework/DicomizerParameters.cpp + ${ORTHANC_WSI_DIR}/Framework/Enumerations.cpp + ${ORTHANC_WSI_DIR}/Framework/ImageToolbox.cpp + ${ORTHANC_WSI_DIR}/Framework/ImagedVolumeParameters.cpp + ${ORTHANC_WSI_DIR}/Framework/Inputs/DecodedTiledPyramid.cpp + ${ORTHANC_WSI_DIR}/Framework/Inputs/DicomPyramid.cpp + ${ORTHANC_WSI_DIR}/Framework/Inputs/DicomPyramidInstance.cpp + ${ORTHANC_WSI_DIR}/Framework/Inputs/DicomPyramidLevel.cpp + ${ORTHANC_WSI_DIR}/Framework/Inputs/HierarchicalTiff.cpp + ${ORTHANC_WSI_DIR}/Framework/Inputs/OpenSlideLibrary.cpp + ${ORTHANC_WSI_DIR}/Framework/Inputs/OpenSlidePyramid.cpp + ${ORTHANC_WSI_DIR}/Framework/Inputs/PyramidWithRawTiles.cpp + ${ORTHANC_WSI_DIR}/Framework/Inputs/SingleLevelDecodedPyramid.cpp + ${ORTHANC_WSI_DIR}/Framework/Inputs/TiledPyramidStatistics.cpp + ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Reader.cpp + ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Writer.cpp + ${ORTHANC_WSI_DIR}/Framework/Messaging/CurlOrthancConnection.cpp + ${ORTHANC_WSI_DIR}/Framework/Messaging/FolderTarget.cpp + ${ORTHANC_WSI_DIR}/Framework/Messaging/IOrthancConnection.cpp + ${ORTHANC_WSI_DIR}/Framework/Messaging/OrthancConnectionBase.cpp + ${ORTHANC_WSI_DIR}/Framework/Messaging/OrthancTarget.cpp + ${ORTHANC_WSI_DIR}/Framework/Outputs/DicomPyramidWriter.cpp + ${ORTHANC_WSI_DIR}/Framework/Outputs/HierarchicalTiffWriter.cpp + ${ORTHANC_WSI_DIR}/Framework/Outputs/InMemoryTiledImage.cpp + ${ORTHANC_WSI_DIR}/Framework/Outputs/MultiframeDicomWriter.cpp + ${ORTHANC_WSI_DIR}/Framework/Outputs/PyramidWriterBase.cpp + ${ORTHANC_WSI_DIR}/Framework/Outputs/TruncatedPyramidWriter.cpp + + ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp + ${ORTHANC_ROOT}/Core/DicomFormat/DicomArray.cpp + ${ORTHANC_ROOT}/Core/DicomFormat/DicomMap.cpp + ${ORTHANC_ROOT}/Core/DicomFormat/DicomTag.cpp + ${ORTHANC_ROOT}/Core/DicomFormat/DicomValue.cpp + ${ORTHANC_ROOT}/Core/Enumerations.cpp + ${ORTHANC_ROOT}/Core/HttpClient.cpp + ${ORTHANC_ROOT}/Core/Images/IImageWriter.cpp + ${ORTHANC_ROOT}/Core/Images/Image.cpp + ${ORTHANC_ROOT}/Core/Images/ImageAccessor.cpp + ${ORTHANC_ROOT}/Core/Images/ImageBuffer.cpp + ${ORTHANC_ROOT}/Core/Images/ImageProcessing.cpp + ${ORTHANC_ROOT}/Core/Images/JpegErrorManager.cpp + ${ORTHANC_ROOT}/Core/Images/JpegReader.cpp + ${ORTHANC_ROOT}/Core/Images/JpegWriter.cpp + ${ORTHANC_ROOT}/Core/Images/PngReader.cpp + ${ORTHANC_ROOT}/Core/Images/PngWriter.cpp + ${ORTHANC_ROOT}/Core/Logging.cpp + ${ORTHANC_ROOT}/Core/MultiThreading/BagOfTasksProcessor.cpp + ${ORTHANC_ROOT}/Core/MultiThreading/SharedMessageQueue.cpp + ${ORTHANC_ROOT}/Core/Toolbox.cpp + ${ORTHANC_ROOT}/Core/Uuid.cpp + ${ORTHANC_ROOT}/Core/WebServiceParameters.cpp + ${ORTHANC_ROOT}/OrthancServer/FromDcmtkBridge.cpp + ${ORTHANC_ROOT}/OrthancServer/ServerEnumerations.cpp + ${ORTHANC_ROOT}/OrthancServer/ToDcmtkBridge.cpp + ${ORTHANC_ROOT}/Plugins/Engine/SharedLibrary.cpp + ${ORTHANC_ROOT}/Resources/ThirdParty/base64/base64.cpp + + # Mandatory components + ${BOOST_SOURCES} + ${JSONCPP_SOURCES} + ${ZLIB_SOURCES} + ${LIBPNG_SOURCES} + ${LIBJPEG_SOURCES} + ${DCMTK_SOURCES} + ${LIBTIFF_SOURCES} + ${OPENJPEG_SOURCES} + + # Optional components + ${OPENSSL_SOURCES} + ${CURL_SOURCES} + + ${AUTOGENERATED_SOURCES} + ) + + +##################################################################### +## Build the WSI DICOM-izer +##################################################################### + +add_executable(OrthancWSIDicomizer + Dicomizer.cpp + ApplicationToolbox.cpp + ) + +target_link_libraries(OrthancWSIDicomizer OrthancWSIFramework ${DCMTK_LIBRARIES}) + + +##################################################################### +## Build the DICOM-to-TIFF conversion tool +##################################################################### + +add_executable(OrthancWSIDicomToTiff + DicomToTiff.cpp + ApplicationToolbox.cpp + ) + +target_link_libraries(OrthancWSIDicomToTiff OrthancWSIFramework ${DCMTK_LIBRARIES}) + + +##################################################################### +## Generate the documentation if Doxygen is present +##################################################################### + +find_package(Doxygen) +if (DOXYGEN_FOUND) + configure_file( + ${ORTHANC_WSI_DIR}/Resources/OrthancWSI.doxygen + ${CMAKE_CURRENT_BINARY_DIR}/OrthancWSI.doxygen + @ONLY) + + add_custom_target(doc + ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancWSI.doxygen + COMMENT "Generating documentation with Doxygen" VERBATIM + ) +else() + message("Doxygen not found. The documentation will not be built.") +endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/DicomToTiff.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,321 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "../Framework/DicomToolbox.h" +#include "../Framework/ImageToolbox.h" +#include "../Framework/Inputs/DicomPyramid.h" +#include "../Framework/Messaging/CurlOrthancConnection.h" +#include "../Framework/Orthanc/Core/Logging.h" +#include "../Framework/Orthanc/Core/OrthancException.h" +#include "../Framework/Outputs/HierarchicalTiffWriter.h" + +#include "ApplicationToolbox.h" + +#include <boost/program_options.hpp> + + +static bool ParseParameters(int& exitStatus, + boost::program_options::variables_map& options, + int argc, + char* argv[]) +{ + // Declare the supported parameters + boost::program_options::options_description generic("Generic options"); + generic.add_options() + ("help", "Display this help and exit") + ("verbose", "Be verbose in logs") + ; + + boost::program_options::options_description source("Options for the source DICOM image"); + source.add_options() + ("orthanc", boost::program_options::value<std::string>()->default_value("http://localhost:8042/"), + "URL to the REST API of the target Orthanc server") + ("username", boost::program_options::value<std::string>(), "Username for the target Orthanc server") + ("password", boost::program_options::value<std::string>(), "Password for the target Orthanc server") + ; + + boost::program_options::options_description target("Options for the target TIFF image"); + target.add_options() + ("color", boost::program_options::value<std::string>(), "Color of the background for missing tiles (e.g. \"255,0,0\")") + ("reencode", boost::program_options::value<bool>(), + "Whether to reencode each tile in JPEG (no transcoding, much slower) (Boolean)") + ("jpeg-quality", boost::program_options::value<int>(), "Set quality level for JPEG (0..100)") + ; + + boost::program_options::options_description hidden; + hidden.add_options() + ("input", boost::program_options::value<std::string>(), "Orthanc identifier of the input series of interest") + ("output", boost::program_options::value<std::string>(), "Output TIFF file"); + ; + + boost::program_options::options_description allWithoutHidden; + allWithoutHidden.add(generic).add(source).add(target); + + boost::program_options::options_description all = allWithoutHidden; + all.add(hidden); + + boost::program_options::positional_options_description positional; + positional.add("input", 1); + positional.add("output", 1); + + bool error = false; + + try + { + boost::program_options::store(boost::program_options::command_line_parser(argc, argv). + options(all).positional(positional).run(), options); + boost::program_options::notify(options); + } + catch (boost::program_options::error& e) + { + LOG(ERROR) << "Error while parsing the command-line arguments: " << e.what(); + error = true; + } + + if (!error && + options.count("help") == 0) + { + if (options.count("input") != 1) + { + LOG(ERROR) << "No input series was specified"; + error = true; + } + + if (options.count("output") != 1) + { + LOG(ERROR) << "No output file was specified"; + error = true; + } + } + + if (error || options.count("help")) + { + std::cout << std::endl + << "Usage: " << argv[0] << " [OPTION]... [INPUT] [OUTPUT]" + << std::endl + << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research." + << std::endl << std::endl + << "Convert a DICOM for digital pathology stored in some Orthanc server as a standard hierarchical TIFF." + << std::endl; + + std::cout << allWithoutHidden << "\n"; + + if (error) + { + exitStatus = -1; + } + + return false; + } + + if (options.count("verbose")) + { + Orthanc::Logging::EnableInfoLevel(true); + } + + return true; +} + + + +static Orthanc::ImageAccessor* CreateEmptyTile(const OrthancWSI::IPyramidWriter& writer, + const boost::program_options::variables_map& options) +{ + std::auto_ptr<Orthanc::ImageAccessor> tile + (OrthancWSI::ImageToolbox::Allocate(writer.GetPixelFormat(), + writer.GetTileWidth(), + writer.GetTileHeight())); + + uint8_t red = 255; + uint8_t green = 255; + uint8_t blue = 255; + + if (options.count("color")) + { + OrthancWSI::ApplicationToolbox::ParseColor(red, green, blue, options["color"].as<std::string>()); + } + + OrthancWSI::ImageToolbox::Set(*tile, red, green, blue); + + return tile.release(); +} + + + +static void RunTranscode(OrthancWSI::ITiledPyramid& source, + const boost::program_options::variables_map& options) +{ + OrthancWSI::HierarchicalTiffWriter target(options["output"].as<std::string>(), + source.GetPixelFormat(), + source.GetImageCompression(), + source.GetTileWidth(), + source.GetTileHeight()); + + std::auto_ptr<Orthanc::ImageAccessor> empty(CreateEmptyTile(target, options)); + + for (unsigned int level = 0; level < source.GetLevelCount(); level++) + { + LOG(WARNING) << "Creating level " << level << " of size " + << source.GetLevelWidth(level) << "x" << source.GetLevelHeight(level); + target.AddLevel(source.GetLevelWidth(level), source.GetLevelHeight(level)); + } + + for (unsigned int level = 0; level < source.GetLevelCount(); level++) + { + LOG(WARNING) << "Transcoding level " << level; + + unsigned int countX = OrthancWSI::CeilingDivision(source.GetLevelWidth(level), source.GetTileWidth()); + unsigned int countY = OrthancWSI::CeilingDivision(source.GetLevelHeight(level), source.GetTileHeight()); + + for (unsigned int tileY = 0; tileY < countY; tileY++) + { + for (unsigned int tileX = 0; tileX < countX; tileX++) + { + LOG(INFO) << "Dealing with tile (" << tileX << "," << tileY << ") at level " << level; + std::string tile; + + if (source.ReadRawTile(tile, level, tileX, tileY)) + { + target.WriteRawTile(tile, source.GetImageCompression(), level, tileX, tileY); + } + else + { + target.EncodeTile(*empty, level, tileX, tileY); + } + } + } + + target.Flush(); + } +} + + +static void RunReencode(OrthancWSI::ITiledPyramid& source, + const boost::program_options::variables_map& options) +{ + OrthancWSI::HierarchicalTiffWriter target(options["output"].as<std::string>(), + source.GetPixelFormat(), + OrthancWSI::ImageCompression_Jpeg, + source.GetTileWidth(), + source.GetTileHeight()); + + if (options.count("jpeg-quality")) + { + target.SetJpegQuality(options["jpeg-quality"].as<int>()); + } + + std::auto_ptr<Orthanc::ImageAccessor> empty(CreateEmptyTile(target, options)); + + for (unsigned int level = 0; level < source.GetLevelCount(); level++) + { + LOG(WARNING) << "Creating level " << level << " of size " + << source.GetLevelWidth(level) << "x" << source.GetLevelHeight(level); + target.AddLevel(source.GetLevelWidth(level), source.GetLevelHeight(level)); + } + + for (unsigned int level = 0; level < source.GetLevelCount(); level++) + { + LOG(WARNING) << "Reencoding level " << level; + + unsigned int countX = OrthancWSI::CeilingDivision(source.GetLevelWidth(level), source.GetTileWidth()); + unsigned int countY = OrthancWSI::CeilingDivision(source.GetLevelHeight(level), source.GetTileHeight()); + + for (unsigned int tileY = 0; tileY < countY; tileY++) + { + for (unsigned int tileX = 0; tileX < countX; tileX++) + { + LOG(INFO) << "Dealing with tile (" << tileX << "," << tileY << ") at level " << level; + + std::auto_ptr<Orthanc::ImageAccessor> tile(source.DecodeTile(level, tileX, tileY)); + if (tile.get() == NULL) + { + target.EncodeTile(*empty, level, tileX, tileY); + } + else + { + target.EncodeTile(*tile, level, tileX, tileY); + } + } + } + + target.Flush(); + } +} + + + +int main(int argc, char* argv[]) +{ + OrthancWSI::ApplicationToolbox::GlobalInitialize(); + + int exitStatus = 0; + boost::program_options::variables_map options; + + try + { + if (ParseParameters(exitStatus, options, argc, argv)) + { + Orthanc::WebServiceParameters params; + + if (options.count("orthanc")) + { + params.SetUrl(options["orthanc"].as<std::string>()); + } + + if (options.count("username")) + { + params.SetUsername(options["username"].as<std::string>()); + } + + if (options.count("password")) + { + params.SetPassword(options["password"].as<std::string>()); + } + + OrthancWSI::CurlOrthancConnection orthanc(params); + OrthancWSI::DicomPyramid source(orthanc, options["input"].as<std::string>()); + + if (options.count("reencode") && + options["reencode"].as<bool>()) + { + RunReencode(source, options); + } + else + { + RunTranscode(source, options); + } + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Terminating on exception: " << e.What(); + + if (options.count("reencode") == 0) + { + LOG(ERROR) << "Consider using option \"--reencode\""; + } + + exitStatus = -1; + } + + OrthancWSI::ApplicationToolbox::GlobalFinalize(); + + return exitStatus; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Dicomizer.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,877 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "../Framework/Algorithms/ReconstructPyramidCommand.h" +#include "../Framework/Algorithms/TranscodeTileCommand.h" +#include "../Framework/DicomToolbox.h" +#include "../Framework/DicomizerParameters.h" +#include "../Framework/ImagedVolumeParameters.h" +#include "../Framework/Inputs/HierarchicalTiff.h" +#include "../Framework/Inputs/OpenSlidePyramid.h" +#include "../Framework/Inputs/TiledJpegImage.h" +#include "../Framework/Inputs/TiledPngImage.h" +#include "../Framework/Inputs/TiledPyramidStatistics.h" +#include "../Framework/Orthanc/Core/HttpClient.h" +#include "../Framework/Orthanc/Core/Logging.h" +#include "../Framework/Orthanc/Core/MultiThreading/BagOfTasksProcessor.h" +#include "../Framework/Orthanc/Core/Toolbox.h" +#include "../Framework/Orthanc/OrthancServer/FromDcmtkBridge.h" +#include "../Framework/Outputs/DicomPyramidWriter.h" +#include "../Framework/Outputs/TruncatedPyramidWriter.h" + +#include "ApplicationToolbox.h" + +#include <EmbeddedResources.h> + +#include <boost/program_options.hpp> + +#include <dcmtk/dcmdata/dcdeftag.h> +#include <dcmtk/dcmdata/dcuid.h> +#include <dcmtk/dcmdata/dcvrobow.h> +#include <dcmtk/dcmdata/dcvrat.h> + + +static void TranscodePyramid(OrthancWSI::PyramidWriterBase& target, + OrthancWSI::ITiledPyramid& source, + const OrthancWSI::DicomizerParameters& parameters) +{ + Orthanc::BagOfTasks tasks; + + for (unsigned int i = 0; i < source.GetLevelCount(); i++) + { + LOG(WARNING) << "Creating level " << i << " of size " + << source.GetLevelWidth(i) << "x" << source.GetLevelHeight(i); + target.AddLevel(source.GetLevelWidth(i), source.GetLevelHeight(i)); + } + + LOG(WARNING) << "Transcoding the source pyramid"; + + OrthancWSI::TranscodeTileCommand::PrepareBagOfTasks(tasks, target, source, parameters); + OrthancWSI::ApplicationToolbox::Execute(tasks, parameters.GetThreadsCount()); +} + + +static void ReconstructPyramid(OrthancWSI::PyramidWriterBase& target, + OrthancWSI::ITiledPyramid& source, + const OrthancWSI::DicomizerParameters& parameters) +{ + Orthanc::BagOfTasks tasks; + + unsigned int levelsCount = parameters.GetPyramidLevelsCount(target, source); + LOG(WARNING) << "The target pyramid will have " << levelsCount << " levels"; + assert(levelsCount >= 1); + + for (unsigned int i = 0; i < levelsCount; i++) + { + unsigned int width = OrthancWSI::CeilingDivision(source.GetLevelWidth(0), 1 << i); + unsigned int height = OrthancWSI::CeilingDivision(source.GetLevelHeight(0), 1 << i); + + LOG(WARNING) << "Creating level " << i << " of size " << width << "x" << height; + target.AddLevel(width, height); + } + + unsigned int lowerLevelsCount = parameters.GetPyramidLowerLevelsCount(target, source); + if (lowerLevelsCount > levelsCount) + { + LOG(WARNING) << "The number of lower levels (" << lowerLevelsCount + << ") exceeds the number of levels (" << levelsCount + << "), cropping it"; + lowerLevelsCount = levelsCount; + } + + assert(lowerLevelsCount <= levelsCount); + if (lowerLevelsCount != levelsCount) + { + LOG(WARNING) << "Constructing the " << lowerLevelsCount << " lower levels of the pyramid"; + OrthancWSI::TruncatedPyramidWriter truncated(target, lowerLevelsCount); + OrthancWSI::ReconstructPyramidCommand::PrepareBagOfTasks + (tasks, truncated, source, lowerLevelsCount + 1, 0, parameters); + OrthancWSI::ApplicationToolbox::Execute(tasks, parameters.GetThreadsCount()); + + assert(tasks.GetSize() == 0); + + const unsigned int upperLevelsCount = levelsCount - lowerLevelsCount; + LOG(WARNING) << "Constructing the " << upperLevelsCount << " upper levels of the pyramid"; + OrthancWSI::ReconstructPyramidCommand::PrepareBagOfTasks + (tasks, target, truncated.GetUpperLevel(), + upperLevelsCount, lowerLevelsCount, parameters); + OrthancWSI::ApplicationToolbox::Execute(tasks, parameters.GetThreadsCount()); + } + else + { + LOG(WARNING) << "Constructing the pyramid"; + OrthancWSI::ReconstructPyramidCommand::PrepareBagOfTasks + (tasks, target, source, levelsCount, 0, parameters); + OrthancWSI::ApplicationToolbox::Execute(tasks, parameters.GetThreadsCount()); + } +} + + +static void Recompress(OrthancWSI::IFileTarget& output, + OrthancWSI::ITiledPyramid& source, + const DcmDataset& dataset, + const OrthancWSI::DicomizerParameters& parameters, + const OrthancWSI::ImagedVolumeParameters& volume) +{ + OrthancWSI::TiledPyramidStatistics stats(source); + + LOG(WARNING) << "Size of source tiles: " << stats.GetTileWidth() << "x" << stats.GetTileHeight(); + LOG(WARNING) << "Source image compression: " << OrthancWSI::EnumerationToString(stats.GetImageCompression()); + LOG(WARNING) << "Pixel format: " << Orthanc::EnumerationToString(stats.GetPixelFormat()); + LOG(WARNING) << "Smoothing is " << (parameters.IsSmoothEnabled() ? "enabled" : "disabled"); + + if (parameters.IsRepaintBackground()) + { + LOG(WARNING) << "Repainting the background with color: (" + << static_cast<int>(parameters.GetBackgroundColorRed()) << "," + << static_cast<int>(parameters.GetBackgroundColorGreen()) << "," + << static_cast<int>(parameters.GetBackgroundColorBlue()) << ")"; + } + else + { + LOG(WARNING) << "No repainting of the background"; + } + + OrthancWSI::DicomPyramidWriter target(output, dataset, + source.GetPixelFormat(), + parameters.GetTargetCompression(), + parameters.GetTargetTileWidth(source), + parameters.GetTargetTileHeight(source), + parameters.GetDicomMaxFileSize(), + volume); + + LOG(WARNING) << "Size of target tiles: " << target.GetTileWidth() << "x" << target.GetTileHeight(); + + if (target.GetImageCompression() == OrthancWSI::ImageCompression_Jpeg) + { + LOG(WARNING) << "Target image compression: Jpeg with quality " << static_cast<int>(target.GetJpegQuality()); + target.SetJpegQuality(target.GetJpegQuality()); + } + else + { + LOG(WARNING) << "Target image compression: " << OrthancWSI::EnumerationToString(target.GetImageCompression()); + } + + if (stats.GetTileWidth() % target.GetTileWidth() != 0 || + stats.GetTileHeight() % target.GetTileHeight() != 0) + { + LOG(ERROR) << "When resampling the tile size, it must be a integer divisor of the original tile size"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + + if (target.GetTileWidth() <= 16 || + target.GetTileHeight() <= 16) + { + LOG(ERROR) << "Tiles are too small (16 pixels minimum): " + << target.GetTileWidth() << "x" << target.GetTileHeight(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + + if (parameters.IsReconstructPyramid()) + { + ReconstructPyramid(target, stats, parameters); + } + else + { + TranscodePyramid(target, stats, parameters); + } + + target.Flush(); +} + + + +static DcmDataset* ParseDataset(const std::string& path) +{ + Json::Value json; + + if (path.empty()) + { + json = Json::objectValue; // Empty dataset => TODO EMBED + } + else + { + std::string content; + Orthanc::Toolbox::ReadFile(content, path); + + Json::Reader reader; + if (!reader.parse(content, json, false)) + { + LOG(ERROR) << "Cannot parse the JSON file in: " << path; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + std::auto_ptr<DcmDataset> dataset(Orthanc::FromDcmtkBridge::FromJson(json, true, true, Orthanc::Encoding_Latin1)); + if (dataset.get() == NULL) + { + LOG(ERROR) << "Cannot convert to JSON file to a DICOM dataset: " << path; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + // VL Whole Slide Microscopy Image IOD + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_SOPClassUID, "1.2.840.10008.5.1.4.1.1.77.1.6"); + + // Slide Microscopy + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_Modality, "SM"); + + // Patient orientation makes no sense in whole-slide images + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_PatientOrientation, ""); + + // Some basic coordinate information + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_VolumetricProperties, "VOLUME"); + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_ImageOrientationSlide, "0\\-1\\0\\-1\\0\\0"); + + std::string date, time; + Orthanc::Toolbox::GetNowDicom(date, time); + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_StudyDate, date); + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_StudyTime, time); + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_SeriesDate, date); + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_SeriesTime, time); + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_ContentDate, date); + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_ContentTime, time); + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_AcquisitionDateTime, date + time); + + return dataset.release(); +} + + + +static void SetupDimension(DcmDataset& dataset, + const std::string& opticalPathId, + const OrthancWSI::ITiledPyramid& source, + const OrthancWSI::ImagedVolumeParameters& volume) +{ + std::string uid; + DcmItem* previous = OrthancWSI::DicomToolbox::ExtractSingleSequenceItem(dataset, DCM_DimensionOrganizationSequence); + + if (previous != NULL) + { + const char* tmp = NULL; + if (previous->findAndGetString(DCM_DimensionOrganizationUID, tmp).good() && + tmp != NULL) + { + uid.assign(tmp); + } + } + + if (uid.empty()) + { + // Generate an unique identifier for the Dimension Organization + uid = Orthanc::FromDcmtkBridge::GenerateUniqueIdentifier(Orthanc::ResourceType_Instance); + } + + dataset.remove(DCM_DimensionIndexSequence); + + std::auto_ptr<DcmItem> item(new DcmItem); + std::auto_ptr<DcmSequenceOfItems> sequence(new DcmSequenceOfItems(DCM_DimensionOrganizationSequence)); + + if (!item->putAndInsertString(DCM_DimensionOrganizationUID, uid.c_str()).good() || + !sequence->insert(item.release(), false, false).good() || + !dataset.insert(sequence.release(), true, false).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + item.reset(new DcmItem); + sequence.reset(new DcmSequenceOfItems(DCM_DimensionIndexSequence)); + + std::auto_ptr<DcmAttributeTag> a1(new DcmAttributeTag(DCM_FunctionalGroupPointer)); + std::auto_ptr<DcmAttributeTag> a2(new DcmAttributeTag(DCM_DimensionIndexPointer)); + + if (!item->putAndInsertString(DCM_DimensionOrganizationUID, uid.c_str()).good() || + !a1->putTagVal(DCM_FrameContentSequence).good() || + !a2->putTagVal(DCM_DimensionIndexValues).good() || + !item->insert(a1.release(), uid.c_str()).good() || + !item->insert(a2.release(), uid.c_str()).good() || + !sequence->insert(item.release(), false, false).good() || + !dataset.insert(sequence.release(), true, false).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + float spacingX = volume.GetWidth() / static_cast<float>(source.GetLevelHeight(0)); // Remember to switch X/Y! + float spacingY = volume.GetHeight() / static_cast<float>(source.GetLevelWidth(0)); // Remember to switch X/Y! + std::string spacing = (boost::lexical_cast<std::string>(spacingX) + '\\' + + boost::lexical_cast<std::string>(spacingY)); + + item.reset(new DcmItem); + sequence.reset(new DcmSequenceOfItems(DCM_SharedFunctionalGroupsSequence)); + std::auto_ptr<DcmItem> item2(new DcmItem); + std::auto_ptr<DcmItem> item3(new DcmItem); + std::auto_ptr<DcmSequenceOfItems> sequence2(new DcmSequenceOfItems(DCM_PixelMeasuresSequence)); + std::auto_ptr<DcmSequenceOfItems> sequence3(new DcmSequenceOfItems(DCM_OpticalPathIdentificationSequence)); + + OrthancWSI::DicomToolbox::SetStringTag(*item2, DCM_SliceThickness, boost::lexical_cast<std::string>(volume.GetDepth())); + OrthancWSI::DicomToolbox::SetStringTag(*item2, DCM_PixelSpacing, spacing); + OrthancWSI::DicomToolbox::SetStringTag(*item3, DCM_OpticalPathIdentifier, opticalPathId); + + if (!sequence2->insert(item2.release(), false, false).good() || + !sequence3->insert(item3.release(), false, false).good() || + !item->insert(sequence2.release(), false, false).good() || + !item->insert(sequence3.release(), false, false).good() || + !sequence->insert(item.release(), false, false).good() || + !dataset.insert(sequence.release(), true, false).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } +} + + +static void EnrichDataset(DcmDataset& dataset, + const OrthancWSI::ITiledPyramid& source, + const OrthancWSI::DicomizerParameters& parameters, + const OrthancWSI::ImagedVolumeParameters& volume) +{ + Orthanc::Encoding encoding = Orthanc::FromDcmtkBridge::DetectEncoding(dataset, Orthanc::Encoding_Latin1); + + if (source.GetImageCompression() == OrthancWSI::ImageCompression_Jpeg || + parameters.GetTargetCompression() == OrthancWSI::ImageCompression_Jpeg) + { + // Takes as estimation a 1:10 compression ratio + OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_LossyImageCompression, "01"); + OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_LossyImageCompressionRatio, "10"); + OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_LossyImageCompressionMethod, "ISO_10918_1"); // JPEG Lossy Compression + } + else + { + OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_LossyImageCompression, "00"); + } + + OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_ImagedVolumeWidth, boost::lexical_cast<std::string>(volume.GetWidth())); + OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_ImagedVolumeHeight, boost::lexical_cast<std::string>(volume.GetHeight())); + OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_ImagedVolumeDepth, boost::lexical_cast<std::string>(volume.GetDepth())); + + std::auto_ptr<DcmItem> origin(new DcmItem); + OrthancWSI::DicomToolbox::SetStringTag(*origin, DCM_XOffsetInSlideCoordinateSystem, + boost::lexical_cast<std::string>(volume.GetOffsetX())); + OrthancWSI::DicomToolbox::SetStringTag(*origin, DCM_YOffsetInSlideCoordinateSystem, + boost::lexical_cast<std::string>(volume.GetOffsetY())); + + std::auto_ptr<DcmSequenceOfItems> sequenceOrigin(new DcmSequenceOfItems(DCM_TotalPixelMatrixOriginSequence)); + if (!sequenceOrigin->insert(origin.release(), false, false).good() || + !dataset.insert(sequenceOrigin.release(), false, false).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + + if (parameters.GetOpticalPath() == OrthancWSI::OpticalPath_Brightfield) + { + if (dataset.tagExists(DCM_OpticalPathSequence)) + { + LOG(ERROR) << "The user DICOM dataset already contains an optical path sequence, giving up"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + std::string brightfield; + Orthanc::EmbeddedResources::GetFileResource(brightfield, Orthanc::EmbeddedResources::BRIGHTFIELD_OPTICAL_PATH); + + Json::Value json; + Json::Reader reader; + if (!reader.parse(brightfield, json, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + std::auto_ptr<DcmElement> element(Orthanc::FromDcmtkBridge::FromJson( + Orthanc::DicomTag(DCM_OpticalPathSequence.getGroup(), + DCM_OpticalPathSequence.getElement()), + json, false, encoding)); + if (!dataset.insert(element.release()).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + std::string profile; + if (parameters.GetIccProfilePath().empty()) + { + Orthanc::EmbeddedResources::GetFileResource(profile, Orthanc::EmbeddedResources::SRGB_ICC_PROFILE); + } + else + { + Orthanc::Toolbox::ReadFile(profile, parameters.GetIccProfilePath()); + } + + + DcmItem* opticalPath = OrthancWSI::DicomToolbox::ExtractSingleSequenceItem(dataset, DCM_OpticalPathSequence); + if (opticalPath == NULL) + { + LOG(ERROR) << "No optical path specified"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + if (!opticalPath->tagExists(DCM_ICCProfile)) + { + std::auto_ptr<DcmOtherByteOtherWord> icc(new DcmOtherByteOtherWord(DCM_ICCProfile)); + + if (!icc->putUint8Array(reinterpret_cast<const Uint8*>(profile.c_str()), profile.size()).good() || + !opticalPath->insert(icc.release()).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + const char* opticalPathId = NULL; + if (!opticalPath->findAndGetString(DCM_OpticalPathIdentifier, opticalPathId).good() || + opticalPathId == NULL) + { + LOG(ERROR) << "No identifier in the optical path"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + SetupDimension(dataset, opticalPathId, source, volume); +} + + +static bool ParseParameters(int& exitStatus, + OrthancWSI::DicomizerParameters& parameters, + OrthancWSI::ImagedVolumeParameters& volume, + int argc, + char* argv[]) +{ + // Declare the supported parameters + boost::program_options::options_description generic("Generic options"); + generic.add_options() + ("help", "Display this help and exit") + ("verbose", "Be verbose in logs") + ("threads", boost::program_options::value<int>()->default_value(parameters.GetThreadsCount()), + "Number of processing threads to be used") + ("openslide", boost::program_options::value<std::string>(), + "Path to the shared library of OpenSlide (not necessary if converting from standard hierarchical TIFF)") + ; + + boost::program_options::options_description source("Options for the source image"); + source.add_options() + ("dataset", boost::program_options::value<std::string>(), "Path to a JSON file containing the DICOM dataset") + ("sample-dataset", "Display a minimalistic sample DICOM dataset in JSON format, then exit") + ("reencode", boost::program_options::value<bool>(), "Whether to reencode each tile (no transcoding, much slower) (Boolean)") + ("repaint", boost::program_options::value<bool>(), "Whether to repaint the background of the image (Boolean)") + ("color", boost::program_options::value<std::string>(), "Color of the background (e.g. \"255,0,0\")") + ; + + boost::program_options::options_description pyramid("Options to construct the pyramid"); + pyramid.add_options() + ("pyramid", boost::program_options::value<bool>()->default_value(false), + "Reconstruct the full pyramid (slow) (Boolean)") + ("smooth", boost::program_options::value<bool>()->default_value(false), + "Apply smoothing when reconstructing the pyramid " + "(slower, but higher quality) (Boolean)") + ("levels", boost::program_options::value<int>(), "Number of levels in the target pyramid") + ; + + boost::program_options::options_description target("Options for the target image"); + target.add_options() + ("tile-width", boost::program_options::value<int>(), "Width of the tiles in the target image") + ("tile-height", boost::program_options::value<int>(), "Height of the tiles in the target image") + ("compression", boost::program_options::value<std::string>(), + "Compression of the target image (\"none\", \"jpeg\" or \"jpeg2000\")") + ("jpeg-quality", boost::program_options::value<int>(), "Set quality level for JPEG (0..100)") + ("max-size", boost::program_options::value<int>()->default_value(10), "Maximum size per DICOM instance (in MB)") + ("folder", boost::program_options::value<std::string>(), + "Folder where to store the output DICOM instances") + ("folder-pattern", boost::program_options::value<std::string>()->default_value("wsi-%06d.dcm"), + "Pattern for the files in the output folder") + ("orthanc", boost::program_options::value<std::string>()->default_value("http://localhost:8042/"), + "URL to the REST API of the target Orthanc server") + ("username", boost::program_options::value<std::string>(), "Username for the target Orthanc server") + ("password", boost::program_options::value<std::string>(), "Password for the target Orthanc server") + ; + + boost::program_options::options_description volumeOptions("Description of the imaged volume"); + volumeOptions.add_options() + ("imaged-width", boost::program_options::value<float>()->default_value(15), "With of the specimen (in mm)") + ("imaged-height", boost::program_options::value<float>()->default_value(15), "Height of the specimen (in mm)") + ("imaged-depth", boost::program_options::value<float>()->default_value(1), "Depth of the specimen (in mm)") + ("offset-x", boost::program_options::value<float>()->default_value(20), + "X offset the specimen, wrt. slide coordinates origin (in mm)") + ("offset-y", boost::program_options::value<float>()->default_value(40), + "Y offset the specimen, wrt. slide coordinates origin (in mm)") + ; + + boost::program_options::options_description advancedOptions("Advanced options"); + advancedOptions.add_options() + ("optical-path", boost::program_options::value<std::string>()->default_value("brightfield"), + "Optical path to be automatically added to the DICOM dataset (\"none\" or \"brightfield\")") + ("icc-profile", boost::program_options::value<std::string>(), + "Path to the ICC profile to be included. If empty, a default sRGB profile will be added.") + ("safety", boost::program_options::value<bool>()->default_value(true), + "Whether to do additional checks to verify the source image is supported (might slow down) (Boolean)") + ("lower-levels", boost::program_options::value<int>(), "Number of pyramid levels up to which multithreading " + "should be applied (only for performance/memory tuning)") + ; + + boost::program_options::options_description hidden; + hidden.add_options() + ("input", boost::program_options::value<std::string>(), "Input file"); + ; + + boost::program_options::options_description allWithoutHidden; + allWithoutHidden.add(generic).add(source).add(pyramid).add(target).add(volumeOptions).add(advancedOptions); + + boost::program_options::options_description all = allWithoutHidden; + all.add(hidden); + + boost::program_options::positional_options_description positional; + positional.add("input", 1); + + boost::program_options::variables_map options; + bool error = false; + + try + { + boost::program_options::store(boost::program_options::command_line_parser(argc, argv). + options(all).positional(positional).run(), options); + boost::program_options::notify(options); + } + catch (boost::program_options::error& e) + { + LOG(ERROR) << "Error while parsing the command-line arguments: " << e.what(); + error = true; + } + + if (!error && + options.count("sample-dataset")) + { + std::string sample; + Orthanc::EmbeddedResources::GetFileResource(sample, Orthanc::EmbeddedResources::SAMPLE_DATASET); + + std::cout << std::endl << sample << std::endl; + + return false; + } + + if (!error && + options.count("help") == 0 && + options.count("input") != 1) + { + LOG(ERROR) << "No input file was specified"; + error = true; + } + + if (error || options.count("help")) + { + std::cout << std::endl + << "Usage: " << argv[0] << " [OPTION]... [INPUT]" + << std::endl + << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research." + << std::endl << std::endl + << "Create a DICOM file from a digital pathology image." + << std::endl; + + std::cout << allWithoutHidden << "\n"; + + if (error) + { + exitStatus = -1; + } + + return false; + } + + if (options.count("verbose")) + { + Orthanc::Logging::EnableInfoLevel(true); + } + + if (options.count("openslide")) + { + OrthancWSI::OpenSlideLibrary::Initialize(options["openslide"].as<std::string>()); + } + + if (options.count("pyramid") && + options["pyramid"].as<bool>()) + { + parameters.SetReconstructPyramid(true); + } + + if (options.count("smooth") && + options["smooth"].as<bool>()) + { + parameters.SetSmoothEnabled(true); + } + + if (options.count("safety") && + options["safety"].as<bool>()) + { + parameters.SetSafetyCheck(true); + } + + if (options.count("reencode") && + options["reencode"].as<bool>()) + { + parameters.SetForceReencode(true); + } + + if (options.count("repaint") && + options["repaint"].as<bool>()) + { + parameters.SetRepaintBackground(true); + } + + if (options.count("tile-width") || + options.count("tile-height")) + { + int w = 0; + if (options.count("tile-width")) + { + w = options["tile-width"].as<int>(); + } + + unsigned int h = 0; + if (options.count("tile-height")) + { + h = options["tile-height"].as<int>(); + } + + if (w < 0 || h < 0) + { + LOG(ERROR) << "Negative target tile size specified: " << w << "x" << h; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + parameters.SetTargetTileSize(w, h); + } + + parameters.SetInputFile(options["input"].as<std::string>()); + + if (options.count("color")) + { + uint8_t r, g, b; + OrthancWSI::ApplicationToolbox::ParseColor(r, g, b, options["color"].as<std::string>()); + parameters.SetBackgroundColor(r, g, b); + } + + if (options.count("compression")) + { + std::string s = options["compression"].as<std::string>(); + if (s == "none") + { + parameters.SetTargetCompression(OrthancWSI::ImageCompression_None); + } + else if (s == "jpeg") + { + parameters.SetTargetCompression(OrthancWSI::ImageCompression_Jpeg); + } + else if (s == "jpeg2000") + { + parameters.SetTargetCompression(OrthancWSI::ImageCompression_Jpeg2000); + } + else + { + LOG(ERROR) << "Unknown image compression for the target image: " << s; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + if (options.count("jpeg-quality")) + { + parameters.SetJpegQuality(options["jpeg-quality"].as<int>()); + } + + if (options.count("levels")) + { + parameters.SetPyramidLevelsCount(options["levels"].as<int>()); + } + + if (options.count("lower-levels")) + { + parameters.SetPyramidLowerLevelsCount(options["lower-levels"].as<int>()); + } + + if (options.count("threads")) + { + parameters.SetThreadsCount(options["threads"].as<int>()); + } + + if (options.count("max-size")) + { + parameters.SetDicomMaxFileSize(options["max-size"].as<int>() * 1024 * 1024); + } + + if (options.count("folder")) + { + parameters.SetTargetFolder(options["folder"].as<std::string>()); + } + + if (options.count("folder-pattern")) + { + parameters.SetTargetFolderPattern(options["folder-pattern"].as<std::string>()); + } + + if (options.count("orthanc")) + { + parameters.GetOrthancParameters().SetUrl(options["orthanc"].as<std::string>()); + + if (options.count("username") && + options.count("password")) + { + parameters.GetOrthancParameters().SetUsername(options["username"].as<std::string>()); + parameters.GetOrthancParameters().SetPassword(options["password"].as<std::string>()); + } + } + + if (options.count("dataset")) + { + parameters.SetDatasetPath(options["dataset"].as<std::string>()); + } + + if (options.count("imaged-width")) + { + volume.SetWidth(options["imaged-width"].as<float>()); + } + + if (options.count("imaged-height")) + { + volume.SetHeight(options["imaged-height"].as<float>()); + } + + if (options.count("imaged-depth")) + { + volume.SetDepth(options["imaged-depth"].as<float>()); + } + + if (options.count("offset-x")) + { + volume.SetOffsetX(options["offset-x"].as<float>()); + } + + if (options.count("offset-y")) + { + volume.SetOffsetY(options["offset-y"].as<float>()); + } + + if (options.count("optical-path")) + { + std::string s = options["optical-path"].as<std::string>(); + if (s == "none") + { + parameters.SetOpticalPath(OrthancWSI::OpticalPath_None); + } + else if (s == "brightfield") + { + parameters.SetOpticalPath(OrthancWSI::OpticalPath_Brightfield); + } + else + { + LOG(ERROR) << "Unknown optical path definition: " << s; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + if (options.count("icc-profile")) + { + parameters.SetIccProfilePath(options["icc-profile"].as<std::string>()); + } + + return true; +} + + +OrthancWSI::ITiledPyramid* OpenInputPyramid(const std::string& path, + const OrthancWSI::DicomizerParameters& parameters) +{ + LOG(WARNING) << "The input image is: " << path; + + OrthancWSI::ImageCompression format = OrthancWSI::DetectFormatFromFile(path); + LOG(WARNING) << "File format of the input image: " << EnumerationToString(format); + + switch (format) + { + case OrthancWSI::ImageCompression_Png: + return new OrthancWSI::TiledPngImage(path, + parameters.GetTargetTileWidth(512), + parameters.GetTargetTileHeight(512)); + + case OrthancWSI::ImageCompression_Jpeg: + return new OrthancWSI::TiledJpegImage(path, + parameters.GetTargetTileWidth(512), + parameters.GetTargetTileHeight(512)); + + case OrthancWSI::ImageCompression_Tiff: + { + try + { + return new OrthancWSI::HierarchicalTiff(path); + } + catch (Orthanc::OrthancException&) + { + LOG(WARNING) << "This is not a standard hierarchical TIFF file"; + } + } + + default: + break; + } + + try + { + LOG(WARNING) << "Trying to open the input pyramid with OpenSlide"; + return new OrthancWSI::OpenSlidePyramid(path, + parameters.GetTargetTileWidth(512), + parameters.GetTargetTileHeight(512)); + } + catch (Orthanc::OrthancException&) + { + LOG(ERROR) << "This file is not supported by OpenSlide"; + return NULL; + } +} + + +int main(int argc, char* argv[]) +{ + OrthancWSI::ApplicationToolbox::GlobalInitialize(); + + int exitStatus = 0; + + try + { + OrthancWSI::DicomizerParameters parameters; + OrthancWSI::ImagedVolumeParameters volume; + + if (ParseParameters(exitStatus, parameters, volume, argc, argv)) + { + std::auto_ptr<OrthancWSI::ITiledPyramid> source(OpenInputPyramid(parameters.GetInputFile(), parameters)); + if (source.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + // Create the shared DICOM tags + std::auto_ptr<DcmDataset> dataset(ParseDataset(parameters.GetDatasetPath())); + EnrichDataset(*dataset, *source, parameters, volume); + + std::auto_ptr<OrthancWSI::IFileTarget> output(parameters.CreateTarget()); + Recompress(*output, *source, *dataset, parameters, volume); + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Terminating on exception: " << e.What(); + exitStatus = -1; + } + + OrthancWSI::ApplicationToolbox::GlobalFinalize(); + + return exitStatus; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/COPYING Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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/>. + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<http://www.gnu.org/licenses/>.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Algorithms/PyramidReader.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,305 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "PyramidReader.h" + +#include "../ImageToolbox.h" +#include "../Orthanc/Core/Logging.h" +#include "../Orthanc/Core/OrthancException.h" + +#include <cassert> + +namespace OrthancWSI +{ + class PyramidReader::SourceTile : public boost::noncopyable + { + private: + PyramidReader& that_; + unsigned int tileX_; + unsigned int tileY_; + bool hasRawTile_; + std::string rawTile_; + + std::auto_ptr<Orthanc::ImageAccessor> decoded_; + + bool IsRepaintNeeded() const + { + return (that_.parameters_.IsRepaintBackground() && + ((tileX_ + 1) * that_.sourceTileWidth_ > that_.levelWidth_ || + (tileY_ + 1) * that_.sourceTileHeight_ > that_.levelHeight_)); + } + + void RepaintBackground() + { + assert(decoded_.get() != NULL); + LOG(INFO) << "Repainting background of tile (" + << tileX_ << "," << tileY_ << ") at level " << that_.level_; + + if ((tileY_ + 1) * that_.sourceTileHeight_ > that_.levelHeight_) + { + // Bottom overflow + assert(tileY_ * that_.sourceTileHeight_ < that_.levelHeight_); + + unsigned int bottom = that_.levelHeight_ - tileY_ * that_.sourceTileHeight_; + Orthanc::ImageAccessor a = decoded_->GetRegion(0, bottom, + that_.sourceTileWidth_, + that_.sourceTileHeight_ - bottom); + ImageToolbox::Set(a, + that_.parameters_.GetBackgroundColorRed(), + that_.parameters_.GetBackgroundColorGreen(), + that_.parameters_.GetBackgroundColorBlue()); + + } + + if ((tileX_ + 1) * that_.sourceTileWidth_ > that_.levelWidth_) + { + // Right overflow + assert(tileX_ * that_.sourceTileWidth_ < that_.levelWidth_); + + unsigned int right = that_.levelWidth_ - tileX_ * that_.sourceTileWidth_; + Orthanc::ImageAccessor a = decoded_->GetRegion(right, 0, + that_.sourceTileWidth_ - right, + that_.sourceTileHeight_); + ImageToolbox::Set(a, + that_.parameters_.GetBackgroundColorRed(), + that_.parameters_.GetBackgroundColorGreen(), + that_.parameters_.GetBackgroundColorBlue()); + } + } + + + public: + SourceTile(PyramidReader& that, + unsigned int tileX, + unsigned int tileY) : + that_(that), + tileX_(tileX), + tileY_(tileY) + { + if (!that_.parameters_.IsForceReencode() && + !IsRepaintNeeded() && + that_.source_.ReadRawTile(rawTile_, that_.level_, tileX, tileY)) + { + hasRawTile_ = true; + } + else + { + hasRawTile_ = false; + decoded_.reset(that_.source_.DecodeTile(that_.level_, tileX, tileY)); + if (decoded_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + RepaintBackground(); + } + } + + bool HasRawTile() const + { + return hasRawTile_; + } + + const std::string& GetRawTile() const + { + if (hasRawTile_) + { + return rawTile_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + const Orthanc::ImageAccessor& GetDecodedTile() + { + if (decoded_.get() == NULL) + { + if (!hasRawTile_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + decoded_.reset(ImageToolbox::DecodeTile(rawTile_, that_.source_.GetImageCompression())); + if (decoded_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + RepaintBackground(); + } + + return *decoded_; + } + }; + + + Orthanc::ImageAccessor& PyramidReader::GetOutsideTile() + { + if (outside_.get() == NULL) + { + outside_.reset(ImageToolbox::Allocate(source_.GetPixelFormat(), targetTileWidth_, targetTileHeight_)); + ImageToolbox::Set(*outside_, + parameters_.GetBackgroundColorRed(), + parameters_.GetBackgroundColorGreen(), + parameters_.GetBackgroundColorBlue()); + } + + return *outside_; + } + + + void PyramidReader::CheckTileSize(const Orthanc::ImageAccessor& tile) const + { + if (tile.GetWidth() != sourceTileWidth_ || + tile.GetHeight() != sourceTileHeight_) + { + LOG(ERROR) << "One tile in the input image has size " << tile.GetWidth() << "x" << tile.GetHeight() + << " instead of required " << source_.GetTileWidth() << "x" << source_.GetTileHeight(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + } + + + void PyramidReader::CheckTileSize(const std::string& tile) const + { + if (parameters_.IsSafetyCheck()) + { + std::auto_ptr<Orthanc::ImageAccessor> decoded(ImageToolbox::DecodeTile(tile, source_.GetImageCompression())); + CheckTileSize(*decoded); + } + } + + + PyramidReader::SourceTile& PyramidReader::AccessSourceTile(const Location& location) + { + Cache::iterator found = cache_.find(location); + if (found != cache_.end()) + { + return *found->second; + } + else + { + SourceTile *tile = new SourceTile(*this, location.first, location.second); + cache_[location] = tile; + return *tile; + } + } + + + PyramidReader::Location PyramidReader::MapTargetToSourceLocation(unsigned int tileX, + unsigned int tileY) + { + return std::make_pair(tileX / (sourceTileWidth_ / targetTileWidth_), + tileY / (sourceTileHeight_ / targetTileHeight_)); + } + + + PyramidReader::PyramidReader(ITiledPyramid& source, + unsigned int level, + unsigned int targetTileWidth, + unsigned int targetTileHeight, + const DicomizerParameters& parameters) : + source_(source), + level_(level), + levelWidth_(source.GetLevelWidth(level)), + levelHeight_(source.GetLevelHeight(level)), + sourceTileWidth_(source.GetTileWidth()), + sourceTileHeight_(source.GetTileHeight()), + targetTileWidth_(targetTileWidth), + targetTileHeight_(targetTileHeight), + parameters_(parameters) + { + if (sourceTileWidth_ % targetTileWidth_ != 0 || + sourceTileHeight_ % targetTileHeight_ != 0) + { + LOG(ERROR) << "When resampling the tile size, it must be a integer divisor of the original tile size"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + } + + + PyramidReader::~PyramidReader() + { + for (Cache::iterator it = cache_.begin(); it != cache_.end(); ++it) + { + assert(it->second != NULL); + delete it->second; + } + } + + + const std::string* PyramidReader::GetRawTile(unsigned int tileX, + unsigned int tileY) + { + if (sourceTileWidth_ != targetTileWidth_ || + sourceTileHeight_ != targetTileHeight_) + { + return NULL; + } + + SourceTile& source = AccessSourceTile(MapTargetToSourceLocation(tileX, tileY)); + if (source.HasRawTile()) + { + CheckTileSize(source.GetRawTile()); + return &source.GetRawTile(); + } + else + { + return NULL; + } + } + + + Orthanc::ImageAccessor PyramidReader::GetDecodedTile(unsigned int tileX, + unsigned int tileY) + { + if (tileX * targetTileWidth_ >= levelWidth_ || + tileY * targetTileHeight_ >= levelHeight_) + { + // Accessing a tile out of the source image + return GetOutsideTile(); + } + + SourceTile& source = AccessSourceTile(MapTargetToSourceLocation(tileX, tileY)); + const Orthanc::ImageAccessor& tile = source.GetDecodedTile(); + + CheckTileSize(tile); + + assert(sourceTileWidth_ % targetTileWidth_ == 0 && + sourceTileHeight_ % targetTileHeight_ == 0); + + unsigned int xx = tileX % (sourceTileWidth_ / targetTileWidth_); + unsigned int yy = tileY % (sourceTileHeight_ / targetTileHeight_); + + const uint8_t* bytes = + reinterpret_cast<const uint8_t*>(tile.GetConstRow(yy * targetTileHeight_)) + + GetBytesPerPixel(tile.GetFormat()) * xx * targetTileWidth_; + + Orthanc::ImageAccessor region; + region.AssignReadOnly(tile.GetFormat(), + targetTileWidth_, + targetTileHeight_, + tile.GetPitch(), bytes); + + return region; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Algorithms/PyramidReader.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,96 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "../Inputs/ITiledPyramid.h" +#include "../DicomizerParameters.h" + +#include <map> +#include <memory> + +namespace OrthancWSI +{ + // This class is not thread-safe + class PyramidReader : public boost::noncopyable + { + private: + class SourceTile; + + typedef std::pair<unsigned int, unsigned int> Location; + typedef std::map<Location, SourceTile*> Cache; + + ITiledPyramid& source_; + unsigned int level_; + unsigned int levelWidth_; + unsigned int levelHeight_; + unsigned int sourceTileWidth_; + unsigned int sourceTileHeight_; + unsigned int targetTileWidth_; + unsigned int targetTileHeight_; + const DicomizerParameters& parameters_; + + Cache cache_; + + std::auto_ptr<Orthanc::ImageAccessor> outside_; + + + Orthanc::ImageAccessor& GetOutsideTile(); + + void CheckTileSize(const Orthanc::ImageAccessor& tile) const; + + void CheckTileSize(const std::string& tile) const; + + SourceTile& AccessSourceTile(const Location& location); + + Location MapTargetToSourceLocation(unsigned int tileX, + unsigned int tileY); + + public: + PyramidReader(ITiledPyramid& source, + unsigned int level, + unsigned int targetTileWidth, + unsigned int targetTileHeight, + const DicomizerParameters& parameters); + + ~PyramidReader(); + + const DicomizerParameters& GetParameters() const + { + return parameters_; + } + + ImageCompression GetImageCompression() const + { + return source_.GetImageCompression(); + } + + Orthanc::PixelFormat GetPixelFormat() const + { + return source_.GetPixelFormat(); + } + + const std::string* GetRawTile(unsigned int tileX, + unsigned int tileY); + + Orthanc::ImageAccessor GetDecodedTile(unsigned int tileX, + unsigned int tileY); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Algorithms/ReconstructPyramidCommand.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,184 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "ReconstructPyramidCommand.h" + +#include "../ImageToolbox.h" +#include "../Orthanc/Core/Logging.h" +#include "../Orthanc/Core/OrthancException.h" +#include "../Orthanc/Core/Images/Image.h" + +#include <cassert> + +namespace OrthancWSI +{ + Orthanc::ImageAccessor* ReconstructPyramidCommand::Explore(unsigned int level, + unsigned int offsetX, + unsigned int offsetY) + { + unsigned int zoom = 1 << level; + assert(x_ % zoom == 0 && y_ % zoom == 0); + unsigned int x = x_ / zoom + offsetX; + unsigned int y = y_ / zoom + offsetY; + + if (x >= target_.GetCountTilesX(level + shiftTargetLevel_) || + y >= target_.GetCountTilesY(level + shiftTargetLevel_)) + { + return NULL; + } + + std::auto_ptr<Orthanc::ImageAccessor> result; + + if (level == 0) + { + result.reset(new Orthanc::ImageAccessor(source_.GetDecodedTile(x, y))); + + const std::string* rawTile = source_.GetRawTile(x, y); + + if (rawTile != NULL) + { + // Simple transcoding + target_.WriteRawTile(*rawTile, source_.GetImageCompression(), level + shiftTargetLevel_, x, y); + } + else + { + // Re-encoding the file + target_.EncodeTile(*result, level + shiftTargetLevel_, x, y); + } + } + else + { + std::auto_ptr<Orthanc::ImageAccessor> mosaic(ImageToolbox::Allocate(source_.GetPixelFormat(), + 2 * target_.GetTileWidth(), + 2 * target_.GetTileHeight())); + ImageToolbox::Set(*mosaic, + source_.GetParameters().GetBackgroundColorRed(), + source_.GetParameters().GetBackgroundColorGreen(), + source_.GetParameters().GetBackgroundColorBlue()); + + { + std::auto_ptr<Orthanc::ImageAccessor> subTile(Explore(level - 1, 2 * offsetX, 2 * offsetY)); + if (subTile.get() != NULL) + { + ImageToolbox::Embed(*mosaic, *subTile, 0, 0); + } + } + + { + std::auto_ptr<Orthanc::ImageAccessor> subTile(Explore(level - 1, 2 * offsetX + 1, 2 * offsetY)); + if (subTile.get() != NULL) + { + ImageToolbox::Embed(*mosaic, *subTile, target_.GetTileWidth(), 0); + } + } + + { + std::auto_ptr<Orthanc::ImageAccessor> subTile(Explore(level - 1, 2 * offsetX, 2 * offsetY + 1)); + if (subTile.get() != NULL) + { + ImageToolbox::Embed(*mosaic, *subTile, 0, target_.GetTileHeight()); + } + } + + { + std::auto_ptr<Orthanc::ImageAccessor> subTile(Explore(level - 1, 2 * offsetX + 1, 2 * offsetY + 1)); + if (subTile.get() != NULL) + { + ImageToolbox::Embed(*mosaic, *subTile, target_.GetTileWidth(), target_.GetTileHeight()); + } + } + + result.reset(ImageToolbox::Halve(*mosaic, source_.GetParameters().IsSmoothEnabled())); + + target_.EncodeTile(*result, level + shiftTargetLevel_, x, y); + } + + return result.release(); + } + + + ReconstructPyramidCommand::ReconstructPyramidCommand(IPyramidWriter& target, + ITiledPyramid& source, + unsigned int upToLevel, + unsigned int x, + unsigned int y, + const DicomizerParameters& parameters) : + target_(target), + source_(source, 0, target.GetTileWidth(), target.GetTileHeight(), parameters), + upToLevel_(upToLevel), + x_(x), + y_(y), + shiftTargetLevel_(0) + { + unsigned int zoom = 1 << upToLevel; + if (x % zoom != 0 || + y % zoom != 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (target.GetPixelFormat() != source.GetPixelFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + } + + + bool ReconstructPyramidCommand::Execute() + { + std::auto_ptr<Orthanc::ImageAccessor> root(Explore(upToLevel_, 0, 0)); + return true; + } + + + void ReconstructPyramidCommand::PrepareBagOfTasks(Orthanc::BagOfTasks& tasks, + IPyramidWriter& target, + ITiledPyramid& source, + unsigned int countLevels, + unsigned int shiftTargetLevel, + const DicomizerParameters& parameters) + { + if (countLevels == 0) + { + return; + } + + if (shiftTargetLevel + countLevels > target.GetLevelCount()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + const unsigned int targetCountTilesX = target.GetCountTilesX(shiftTargetLevel); + const unsigned int targetCountTilesY = target.GetCountTilesY(shiftTargetLevel); + const unsigned int step = 1 << (countLevels - 1); + + for (unsigned int y = 0; y < targetCountTilesY; y += step) + { + for (unsigned int x = 0; x < targetCountTilesX; x += step) + { + std::auto_ptr<ReconstructPyramidCommand> command; + command.reset(new ReconstructPyramidCommand + (target, source, countLevels - 1, x, y, parameters)); + command->SetShiftTargetLevel(shiftTargetLevel); + tasks.Push(command.release()); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Algorithms/ReconstructPyramidCommand.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,72 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "PyramidReader.h" +#include "../Outputs/IPyramidWriter.h" +#include "../Orthanc/Core/MultiThreading/BagOfTasks.h" + + +namespace OrthancWSI +{ + class ReconstructPyramidCommand : public Orthanc::ICommand + { + private: + IPyramidWriter& target_; + PyramidReader source_; + + unsigned int upToLevel_; + unsigned int x_; + unsigned int y_; + unsigned int shiftTargetLevel_; + + Orthanc::ImageAccessor* Explore(unsigned int level, + unsigned int offsetX, + unsigned int offsetY); + + public: + ReconstructPyramidCommand(IPyramidWriter& target, + ITiledPyramid& source, + unsigned int upToLevel, + unsigned int x, + unsigned int y, + const DicomizerParameters& parameters); + + void SetShiftTargetLevel(unsigned int shift) + { + shiftTargetLevel_ = shift; + } + + unsigned int GetShiftTargetLevel() const + { + return shiftTargetLevel_; + } + + virtual bool Execute(); + + static void PrepareBagOfTasks(Orthanc::BagOfTasks& tasks, + IPyramidWriter& target, + ITiledPyramid& source, + unsigned int countLevels, + unsigned int shiftTargetLevel, + const DicomizerParameters& parameters); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Algorithms/TranscodeTileCommand.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,120 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "TranscodeTileCommand.h" + +#include "../Orthanc/Core/OrthancException.h" +#include "../Orthanc/Core/Logging.h" + +#include <cassert> + +namespace OrthancWSI +{ + TranscodeTileCommand::TranscodeTileCommand(IPyramidWriter& target, + ITiledPyramid& source, + unsigned int level, + unsigned int x, + unsigned int y, + unsigned int countTilesX, + unsigned int countTilesY, + const DicomizerParameters& parameters) : + target_(target), + source_(source, level, target.GetTileWidth(), target.GetTileHeight(), parameters), + level_(level), + x_(x), + y_(y), + countTilesX_(countTilesX), + countTilesY_(countTilesY) + { + assert(x_ + countTilesX_ <= target_.GetCountTilesX(level_) && + y_ + countTilesY_ <= target_.GetCountTilesY(level_)); + + if (target.GetPixelFormat() != source.GetPixelFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + } + + + bool TranscodeTileCommand::Execute() + { + for (unsigned int x = x_; x < x_ + countTilesX_; x++) + { + for (unsigned int y = y_; y < y_ + countTilesY_; y++) + { + LOG(INFO) << "Adding tile (" << x << "," << y << ") at level " << level_; + const std::string* rawTile = source_.GetRawTile(x, y); + + if (rawTile != NULL) + { + // Simple transcoding + target_.WriteRawTile(*rawTile, source_.GetImageCompression(), level_, x, y); + } + else + { + Orthanc::ImageAccessor tile = source_.GetDecodedTile(x, y); + + // Re-encoding the file + target_.EncodeTile(tile, level_, x, y); + } + } + } + + return true; + } + + + void TranscodeTileCommand::PrepareBagOfTasks(Orthanc::BagOfTasks& tasks, + IPyramidWriter& target, + ITiledPyramid& source, + const DicomizerParameters& parameters) + { + const unsigned int stepX = source.GetTileWidth() / target.GetTileWidth(); + const unsigned int stepY = source.GetTileHeight() / target.GetTileHeight(); + assert(stepX >= 1 && stepY >= 1); + + for (unsigned int level = 0; level < source.GetLevelCount(); level++) + { + const unsigned int targetCountTilesX = target.GetCountTilesX(level); + const unsigned int targetCountTilesY = target.GetCountTilesY(level); + + for (unsigned int y = 0; y < targetCountTilesY; y += stepY) + { + unsigned int cy = stepY; + if (y + cy > targetCountTilesY) + { + cy = targetCountTilesY - y; + } + + for (unsigned int x = 0; x < targetCountTilesX; x += stepX) + { + unsigned int cx = stepX; + if (x + cx > targetCountTilesX) + { + cx = targetCountTilesX - x; + } + + tasks.Push(new TranscodeTileCommand + (target, source, level, x, y, cx, cy, parameters)); + } + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Algorithms/TranscodeTileCommand.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,59 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "PyramidReader.h" +#include "../Outputs/IPyramidWriter.h" + +#include "../Orthanc/Core/MultiThreading/BagOfTasks.h" + +namespace OrthancWSI +{ + class TranscodeTileCommand : public Orthanc::ICommand + { + private: + IPyramidWriter& target_; + PyramidReader source_; + + unsigned int level_; + unsigned int x_; + unsigned int y_; + unsigned int countTilesX_; + unsigned int countTilesY_; + + public: + TranscodeTileCommand(IPyramidWriter& target, + ITiledPyramid& source, + unsigned int level, + unsigned int x, + unsigned int y, + unsigned int countTilesX, + unsigned int countTilesY, + const DicomizerParameters& parameters); + + virtual bool Execute(); + + static void PrepareBagOfTasks(Orthanc::BagOfTasks& tasks, + IPyramidWriter& target, + ITiledPyramid& source, + const DicomizerParameters& parameters); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/DicomToolbox.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,250 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "DicomToolbox.h" + +#include "Orthanc/Core/Logging.h" +#include "Orthanc/Core/OrthancException.h" +#include "Orthanc/Core/Toolbox.h" + +#if ORTHANC_ENABLE_DCMTK == 1 +# include <dcmtk/dcmdata/dcelem.h> +# include <dcmtk/dcmdata/dcsequen.h> +#endif + +#include <boost/lexical_cast.hpp> + +namespace OrthancWSI +{ + namespace DicomToolbox + { +#if ORTHANC_ENABLE_DCMTK == 1 + void SetStringTag(DcmItem& dataset, + const DcmTagKey& key, + const std::string& value) + { + if (!dataset.tagExists(key) && + !dataset.putAndInsertString(key, value.c_str()).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + void SetUint16Tag(DcmItem& dataset, + const DcmTagKey& key, + uint16_t value) + { + if (!dataset.tagExists(key) && + !dataset.putAndInsertUint16(key, value).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + void SetUint32Tag(DcmItem& dataset, + const DcmTagKey& key, + uint32_t value) + { + if (!dataset.tagExists(key) && + !dataset.putAndInsertUint32(key, value).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + DcmItem* ExtractSingleSequenceItem(DcmItem& dataset, + const DcmTagKey& key) + { + DcmElement* element = NULL; + if (!const_cast<DcmItem&>(dataset).findAndGetElement(key, element).good() || + element == NULL) + { + return NULL; + } + + if (element->getVR() != EVR_SQ) + { + DcmTag tag(key); + LOG(ERROR) << "The following element in the DICOM dataset is not a sequence as expected: " << tag.getTagName(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element); + if (sequence.card() != 1) + { + LOG(ERROR) << "Bad number of elements in the sequence (it must contain exactly 1 element)"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + return sequence.getItem(0); + } + + + uint16_t GetUint16Tag(DcmItem& dataset, + const DcmTagKey& key) + { + uint16_t value; + if (dataset.findAndGetUint16(key, value).good()) + { + return value; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentTag); + } + } + + + uint32_t GetUint32Tag(DcmItem& dataset, + const DcmTagKey& key) + { + Uint32 value; + if (dataset.findAndGetUint32(key, value).good()) + { + return value; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentTag); + } + } + + + int32_t GetInt32Tag(DcmItem& dataset, + const DcmTagKey& key) + { + Sint32 value; + if (dataset.findAndGetSint32(key, value).good()) + { + return value; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentTag); + } + } + + + std::string GetStringTag(DcmItem& dataset, + const DcmTagKey& key) + { + const char* value = NULL; + if (dataset.findAndGetString(key, value).good() && + value != NULL) + { + return Orthanc::Toolbox::StripSpaces(value); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentTag); + } + } +#endif + + + bool GetStringTag(std::string& result, + const Json::Value& simplifiedTags, + const std::string& tagName, + const std::string& defaultValue) + { + if (simplifiedTags.type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + if (!simplifiedTags.isMember(tagName)) + { + result = defaultValue; + return false; + } + else if (simplifiedTags[tagName].type() != Json::stringValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + result = simplifiedTags[tagName].asString(); + return true; + } + } + + + std::string GetMandatoryStringTag(const Json::Value& simplifiedTags, + const std::string& tagName) + { + std::string s; + if (GetStringTag(s, simplifiedTags, tagName, "")) + { + return s; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentTag); + } + } + + + const Json::Value& GetSequenceTag(const Json::Value& simplifiedTags, + const std::string& tagName) + { + if (simplifiedTags.type() != Json::objectValue || + !simplifiedTags.isMember(tagName) || + simplifiedTags[tagName].type() != Json::arrayValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + return simplifiedTags[tagName]; + } + + + int GetIntegerTag(const Json::Value& simplifiedTags, + const std::string& tagName) + { + try + { + std::string s = Orthanc::Toolbox::StripSpaces(GetMandatoryStringTag(simplifiedTags, tagName)); + return boost::lexical_cast<int>(s); + } + catch (boost::bad_lexical_cast&) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + + unsigned int GetUnsignedIntegerTag(const Json::Value& simplifiedTags, + const std::string& tagName) + { + int value = GetIntegerTag(simplifiedTags, tagName); + + if (value >= 0) + { + return static_cast<unsigned int>(value); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/DicomToolbox.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,83 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 + +#if ORTHANC_ENABLE_DCMTK == 1 +# include <dcmtk/dcmdata/dcdeftag.h> +# include <dcmtk/dcmdata/dcitem.h> +# include <dcmtk/dcmdata/dctagkey.h> +#endif + +#include <string> +#include <stdint.h> +#include <json/value.h> + +namespace OrthancWSI +{ + namespace DicomToolbox + { +#if ORTHANC_ENABLE_DCMTK == 1 + void SetStringTag(DcmItem& dataset, + const DcmTagKey& key, + const std::string& value); + + void SetUint16Tag(DcmItem& dataset, + const DcmTagKey& key, + uint16_t value); + + void SetUint32Tag(DcmItem& dataset, + const DcmTagKey& key, + uint32_t value); + + DcmItem* ExtractSingleSequenceItem(DcmItem& dataset, + const DcmTagKey& key); + + uint16_t GetUint16Tag(DcmItem& dataset, + const DcmTagKey& key); + + uint32_t GetUint32Tag(DcmItem& dataset, + const DcmTagKey& key); + + int32_t GetInt32Tag(DcmItem& dataset, + const DcmTagKey& key); + + std::string GetStringTag(DcmItem& dataset, + const DcmTagKey& key); +#endif + + bool GetStringTag(std::string& result, + const Json::Value& simplifiedTags, + const std::string& tagName, + const std::string& defaultValue); + + std::string GetMandatoryStringTag(const Json::Value& simplifiedTags, + const std::string& tagName); + + const Json::Value& GetSequenceTag(const Json::Value& simplifiedTags, + const std::string& tagName); + + int GetIntegerTag(const Json::Value& simplifiedTags, + const std::string& tagName); + + unsigned int GetUnsignedIntegerTag(const Json::Value& simplifiedTags, + const std::string& tagName); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/DicomizerParameters.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,274 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "DicomizerParameters.h" + +#include "Messaging/FolderTarget.h" +#include "Messaging/OrthancTarget.h" + +#include "Orthanc/Core/OrthancException.h" + +#include <boost/thread.hpp> +#include <boost/lexical_cast.hpp> + +namespace OrthancWSI +{ + static unsigned int ChooseNumberOfThreads() + { + unsigned int nthreads = boost::thread::hardware_concurrency(); + + if (nthreads % 2 == 0) + { + nthreads = nthreads / 2; + } + else + { + nthreads = nthreads / 2 + 1; + } + + if (nthreads == 0) + { + nthreads = 1; + } + + return nthreads; + } + + + DicomizerParameters::DicomizerParameters() + { + safetyCheck_ = false; + repaintBackground_ = false; + backgroundColor_[0] = 255; + backgroundColor_[1] = 255; + backgroundColor_[2] = 255; + targetCompression_ = ImageCompression_Jpeg; + hasTargetTileSize_ = false; + threadsCount_ = ChooseNumberOfThreads(); + maxDicomFileSize_ = 10 * 1024 * 1024; // 10MB + reconstructPyramid_ = false; + pyramidLevelsCount_ = 0; + pyramidLowerLevelsCount_ = 0; + smooth_ = false; + jpegQuality_ = 90; + forceReencode_ = false; + opticalPath_ = OpticalPath_Brightfield; + } + + + void DicomizerParameters::SetBackgroundColor(uint8_t red, + uint8_t green, + uint8_t blue) + { + repaintBackground_ = true; + backgroundColor_[0] = red; + backgroundColor_[1] = green; + backgroundColor_[2] = blue; + } + + + void DicomizerParameters::SetTargetTileSize(unsigned int width, + unsigned int height) + { + hasTargetTileSize_ = true; + targetTileWidth_ = width; + targetTileHeight_ = height; + } + + + unsigned int DicomizerParameters::GetTargetTileWidth(unsigned int defaultWidth) const + { + if (hasTargetTileSize_ && + targetTileWidth_ != 0) + { + return targetTileWidth_; + } + else + { + return defaultWidth; + } + } + + + unsigned int DicomizerParameters::GetTargetTileHeight(unsigned int defaultHeight) const + { + if (hasTargetTileSize_ && + targetTileHeight_ != 0) + { + return targetTileHeight_; + } + else + { + return defaultHeight; + } + } + + + void DicomizerParameters::SetThreadsCount(unsigned int threads) + { + if (threads == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + threadsCount_ = threads; + } + + + void DicomizerParameters::SetDicomMaxFileSize(unsigned int size) + { + if (size <= 1024) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + maxDicomFileSize_ = size; + } + + + void DicomizerParameters::SetPyramidLevelsCount(unsigned int count) + { + if (count <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + pyramidLevelsCount_ = count; + } + + + unsigned int DicomizerParameters::GetPyramidLevelsCount(const IPyramidWriter& target, + const ITiledPyramid& source) const + { + if (!reconstructPyramid_) + { + // Only makes sense if reconstructing the pyramid + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (pyramidLevelsCount_ != 0) + { + return pyramidLevelsCount_; + } + + // By default, the number of levels for the pyramid is taken so that + // the upper level is reduced either to 1 column of tiles, or to 1 + // row of tiles. + unsigned int totalWidth = source.GetLevelWidth(0); + unsigned int totalHeight = source.GetLevelHeight(0); + + unsigned int countLevels = 1; + for (;;) + { + unsigned int zoom = 1 << (countLevels - 1); + + if (CeilingDivision(totalWidth, zoom) <= target.GetTileWidth() || + CeilingDivision(totalHeight, zoom) <= target.GetTileHeight()) + { + break; + } + + countLevels += 1; + } + + return countLevels; + } + + + void DicomizerParameters::SetPyramidLowerLevelsCount(unsigned int count) + { + if (count <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + pyramidLowerLevelsCount_ = count; + } + + + unsigned int DicomizerParameters::GetPyramidLowerLevelsCount(const IPyramidWriter& target, + const ITiledPyramid& source) const + { + if (!reconstructPyramid_) + { + // Only makes sense if reconstructing the pyramid + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (pyramidLowerLevelsCount_ != 0) + { + return pyramidLowerLevelsCount_; + } + + unsigned int fullNumberOfTiles = ( + CeilingDivision(source.GetLevelWidth(0), source.GetTileWidth()) * + CeilingDivision(source.GetLevelHeight(0), source.GetTileHeight())); + + // By default, the number of lower levels in the pyramid is chosen + // as a compromise between the number of tasks (there should not be + // too few tasks, otherwise multithreading would not be efficient) + // and memory consumption (maximum 64MB of RAM due to the decoding + // of the tiles of the source image per thread: cf. PyramidReader). + unsigned int result = 1; + for (;;) + { + unsigned int zoom = 1 << (result - 1); + unsigned int numberOfTiles = CeilingDivision(fullNumberOfTiles, zoom * zoom); + + if (result + 1 > target.GetLevelCount() || + numberOfTiles < 4 * GetThreadsCount() || + zoom * target.GetTileWidth() > 4096 || + zoom * target.GetTileHeight() > 4096) + { + break; + } + + result += 1; + } + + return result - 1; + } + + + void DicomizerParameters::SetJpegQuality(int quality) + { + if (quality <= 0 || + quality > 100) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + jpegQuality_ = quality; + } + + + IFileTarget* DicomizerParameters::CreateTarget() const + { + if (folder_.empty() || + folderPattern_.empty()) + { + return new OrthancTarget(orthanc_); + } + else + { + return new FolderTarget(folder_ + "/" + folderPattern_); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/DicomizerParameters.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,257 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "Inputs/ITiledPyramid.h" +#include "Outputs/IPyramidWriter.h" +#include "Messaging/IFileTarget.h" +#include "DicomToolbox.h" +#include "Orthanc/Core/WebServiceParameters.h" + +#include <stdint.h> + +namespace OrthancWSI +{ + class DicomizerParameters + { + private: + bool safetyCheck_; + bool repaintBackground_; + uint8_t backgroundColor_[3]; + ImageCompression targetCompression_; + bool hasTargetTileSize_; + unsigned int targetTileWidth_; + unsigned int targetTileHeight_; + unsigned int threadsCount_; + unsigned int maxDicomFileSize_; + bool reconstructPyramid_; + unsigned int pyramidLevelsCount_; // "0" means use default choice + unsigned int pyramidLowerLevelsCount_; // "0" means use default choice + bool smooth_; + std::string inputFile_; + uint8_t jpegQuality_; + bool forceReencode_; + std::string folder_; + std::string folderPattern_; + std::string dataset_; + OpticalPath opticalPath_; + std::string iccProfile_; + + Orthanc::WebServiceParameters orthanc_; + + public: + DicomizerParameters(); + + void SetSafetyCheck(bool safety) + { + safetyCheck_ = safety; + } + + bool IsSafetyCheck() const + { + return safetyCheck_; + } + + bool IsRepaintBackground() const + { + return repaintBackground_; + } + + void SetRepaintBackground(bool repaint) + { + repaintBackground_ = repaint; + } + + void SetBackgroundColor(uint8_t red, + uint8_t green, + uint8_t blue); + + uint8_t GetBackgroundColorRed() const + { + return backgroundColor_[0]; + } + + uint8_t GetBackgroundColorGreen() const + { + return backgroundColor_[1]; + } + + uint8_t GetBackgroundColorBlue() const + { + return backgroundColor_[2]; + } + + void SetTargetCompression(ImageCompression compression) + { + targetCompression_ = compression; + } + + ImageCompression GetTargetCompression() const + { + return targetCompression_; + } + + void SetTargetTileSize(unsigned int width, + unsigned int height); + + unsigned int GetTargetTileWidth(unsigned int defaultWidth) const; + + unsigned int GetTargetTileWidth(const ITiledPyramid& source) const + { + return GetTargetTileWidth(source.GetTileWidth()); + } + + unsigned int GetTargetTileHeight(unsigned int defaultHeight) const; + + unsigned int GetTargetTileHeight(const ITiledPyramid& source) const + { + return GetTargetTileHeight(source.GetTileHeight()); + } + + void SetThreadsCount(unsigned int threads); + + unsigned int GetThreadsCount() const + { + return threadsCount_; + } + + void SetDicomMaxFileSize(unsigned int size); + + unsigned int GetDicomMaxFileSize() const + { + return maxDicomFileSize_; + } + + bool IsReconstructPyramid() const + { + return reconstructPyramid_; + } + + void SetReconstructPyramid(bool reconstruct) + { + reconstructPyramid_ = reconstruct; + } + + void SetPyramidLevelsCount(unsigned int count); + + unsigned int GetPyramidLevelsCount(const IPyramidWriter& target, + const ITiledPyramid& source) const; + + void SetPyramidLowerLevelsCount(unsigned int count); + + unsigned int GetPyramidLowerLevelsCount(const IPyramidWriter& target, + const ITiledPyramid& source) const; + + void SetSmoothEnabled(bool smooth) + { + smooth_ = smooth; + } + + bool IsSmoothEnabled() const + { + return smooth_; + } + + void SetInputFile(const std::string& path) + { + inputFile_ = path; + } + + const std::string& GetInputFile() const + { + return inputFile_; + } + + void SetJpegQuality(int quality); + + uint8_t GetJpegQuality() const + { + return jpegQuality_; + } + + void SetForceReencode(bool force) + { + forceReencode_ = force; + } + + bool IsForceReencode() const + { + return forceReencode_; + } + + void SetTargetFolder(const std::string& folder) + { + folder_ = folder; + } + + const std::string& GetTargetFolderPattern() const + { + return folderPattern_; + } + + void SetTargetFolderPattern(const std::string& pattern) + { + folderPattern_ = pattern; + } + + Orthanc::WebServiceParameters& GetOrthancParameters() + { + return orthanc_; + } + + const Orthanc::WebServiceParameters& GetOrthancParameters() const + { + return orthanc_; + } + + IFileTarget* CreateTarget() const; + + void SetDatasetPath(const std::string& path) + { + dataset_ = path; + } + + const std::string& GetDatasetPath() const + { + return dataset_; + } + + void SetOpticalPath(OpticalPath opticalPath) + { + opticalPath_ = opticalPath; + } + + OpticalPath GetOpticalPath() const + { + return opticalPath_; + } + + void SetIccProfilePath(const std::string& path) + { + iccProfile_ = path; + } + + const std::string& GetIccProfilePath() const + { + return iccProfile_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Enumerations.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,184 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "Enumerations.h" + +#include "Jpeg2000Reader.h" +#include "Orthanc/Core/OrthancException.h" +#include "Orthanc/Core/Toolbox.h" + +#include <string.h> +#include <boost/algorithm/string/predicate.hpp> + +#define HEADER(s) (const void*) (s), sizeof(s)-1 + +namespace OrthancWSI +{ + const char* EnumerationToString(ImageCompression compression) + { + switch (compression) + { + case ImageCompression_Unknown: + return "Unknown"; + + case ImageCompression_None: + return "Raw image"; + + case ImageCompression_Png: + return "PNG"; + + case ImageCompression_Jpeg: + return "JPEG"; + + case ImageCompression_Jpeg2000: + return "JPEG2000"; + + case ImageCompression_Tiff: + return "TIFF"; + + case ImageCompression_Dicom: + return "DICOM"; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + static bool MatchHeader(const void* actual, + size_t actualSize, + const void* expected, + size_t expectedSize) + { + if (actualSize < expectedSize) + { + return false; + } + else + { + return memcmp(actual, expected, expectedSize) == 0; + } + } + + + ImageCompression DetectFormatFromFile(const std::string& path) + { + std::string header; + Orthanc::Toolbox::ReadHeader(header, path, 256); + + ImageCompression tmp = DetectFormatFromMemory(header.c_str(), header.size()); + if (tmp != ImageCompression_Unknown) + { + return tmp; + } + + // Cannot detect the format using the header, fallback to the use + // of the filename extension + + std::string lower; + Orthanc::Toolbox::ToLowerCase(lower, path); + + if (boost::algorithm::ends_with(lower, ".jpeg") || + boost::algorithm::ends_with(lower, ".jpg")) + { + return ImageCompression_Jpeg; + } + + if (boost::algorithm::ends_with(lower, ".png")) + { + return ImageCompression_Png; + } + + if (boost::algorithm::ends_with(lower, ".tiff") || + boost::algorithm::ends_with(lower, ".tif")) + { + return ImageCompression_Tiff; + } + + if (boost::algorithm::ends_with(lower, ".jp2") || + boost::algorithm::ends_with(lower, ".j2k")) + { + return ImageCompression_Jpeg2000; + } + + if (boost::algorithm::ends_with(lower, ".dcm")) + { + return ImageCompression_Dicom; + } + + return ImageCompression_Unknown; + } + + + ImageCompression DetectFormatFromMemory(const void* buffer, + size_t size) + { + if (MatchHeader(buffer, size, HEADER("\377\330\377"))) + { + return ImageCompression_Jpeg; + } + + if (MatchHeader(buffer, size, HEADER("\xff\x4f\xff\x51")) || + MatchHeader(buffer, size, HEADER("\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a"))) + { + return ImageCompression_Jpeg2000; + } + + if (MatchHeader(buffer, size, HEADER("\211PNG\r\n\032\n"))) + { + return ImageCompression_Png; + } + + if (MatchHeader(buffer, size, HEADER("\115\115\000\052")) || + MatchHeader(buffer, size, HEADER("\111\111\052\000")) || + MatchHeader(buffer, size, HEADER("\115\115\000\053\000\010\000\000")) || + MatchHeader(buffer, size, HEADER("\111\111\053\000\010\000\000\000"))) + { + return ImageCompression_Tiff; + } + + if (size >= 128 + 4 && + MatchHeader(reinterpret_cast<const uint8_t*>(buffer) + 128, size - 128, HEADER("DICM"))) + { + bool ok = true; + for (size_t i = 0; ok && i < 128; i++) + { + if (reinterpret_cast<const uint8_t*>(buffer)[i] != 0) + { + ok = false; + } + } + + if (ok) + { + return ImageCompression_Dicom; + } + } + + Jpeg2000Format jpeg2000 = Jpeg2000Reader::DetectFormatFromMemory(buffer, size); + if (jpeg2000 == Jpeg2000Format_JP2 || + jpeg2000 == Jpeg2000Format_J2K) + { + return ImageCompression_Jpeg2000; + } + + return ImageCompression_Unknown; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Enumerations.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,66 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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/Enumerations.h" + +#include <stdint.h> +#include <string> + +namespace OrthancWSI +{ + enum ImageCompression + { + ImageCompression_Unknown, + ImageCompression_None, + ImageCompression_Dicom, + ImageCompression_Png, + ImageCompression_Jpeg, + ImageCompression_Jpeg2000, + ImageCompression_Tiff + }; + + enum OpticalPath + { + OpticalPath_None, + OpticalPath_Brightfield + }; + + const char* EnumerationToString(ImageCompression compression); + + ImageCompression DetectFormatFromFile(const std::string& path); + + ImageCompression DetectFormatFromMemory(const void* buffer, + size_t size); + + inline unsigned int CeilingDivision(unsigned int a, + unsigned int b) + { + if (a % b == 0) + { + return a / b; + } + else + { + return a / b + 1; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/ImageToolbox.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,367 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "ImageToolbox.h" + +#include "Jpeg2000Reader.h" +#include "Jpeg2000Writer.h" + +#include "Orthanc/Core/OrthancException.h" +#include "Orthanc/Core/Images/ImageProcessing.h" +#include "Orthanc/Core/Images/PngReader.h" +#include "Orthanc/Core/Images/PngWriter.h" +#include "Orthanc/Core/Images/JpegReader.h" +#include "Orthanc/Core/Images/JpegWriter.h" +#include "Orthanc/Core/Logging.h" + +#include <string.h> +#include <memory> + + +namespace OrthancWSI +{ + namespace ImageToolbox + { + Orthanc::ImageAccessor* Allocate(Orthanc::PixelFormat format, + unsigned int width, + unsigned int height) + { + return new Orthanc::Image(format, width, height, false); + } + + + void Embed(Orthanc::ImageAccessor& target, + const Orthanc::ImageAccessor& source, + unsigned int x, + unsigned int y) + { + if (target.GetFormat() != source.GetFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + if (x >= target.GetWidth() || + y >= target.GetHeight()) + { + return; + } + + unsigned int h = std::min(source.GetHeight(), target.GetHeight() - y); + unsigned int w = std::min(source.GetWidth(), target.GetWidth() - x); + + Orthanc::ImageAccessor targetRegion = target.GetRegion(x, y, w, h); + Orthanc::ImageAccessor sourceRegion = source.GetRegion(0, 0, w, h); + Orthanc::ImageProcessing::Copy(targetRegion, sourceRegion); + } + + + + void Set(Orthanc::ImageAccessor& image, + uint8_t r, + uint8_t g, + uint8_t b) + { + if (image.GetWidth() == 0 || + image.GetHeight() == 0) + { + return; + } + + uint8_t grayscale = (2126 * static_cast<uint16_t>(r) + + 7152 * static_cast<uint16_t>(g) + + 0722 * static_cast<uint16_t>(b)) / 10000; + + switch (image.GetFormat()) + { + case Orthanc::PixelFormat_Grayscale8: + { + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + memset(image.GetRow(y), grayscale, image.GetWidth()); + } + + break; + } + + case Orthanc::PixelFormat_RGB24: + { + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + uint8_t* p = reinterpret_cast<uint8_t*>(image.GetRow(y)); + for (unsigned int x = 0; x < image.GetWidth(); x++, p += 3) + { + p[0] = r; + p[1] = g; + p[2] = b; + } + } + + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + + Orthanc::ImageAccessor* DecodeTile(const std::string& source, + ImageCompression compression) + { + switch (compression) + { + case ImageCompression_Png: + { + std::auto_ptr<Orthanc::PngReader> reader(new Orthanc::PngReader); + reader->ReadFromMemory(source); + return reader.release(); + } + + case ImageCompression_Jpeg: + { + std::auto_ptr<Orthanc::JpegReader> reader(new Orthanc::JpegReader); + reader->ReadFromMemory(source); + return reader.release(); + } + + case ImageCompression_Jpeg2000: + { + std::auto_ptr<Jpeg2000Reader> reader(new Jpeg2000Reader); + reader->ReadFromMemory(source); + return reader.release(); + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + void EncodeTile(std::string& target, + const Orthanc::ImageAccessor& source, + ImageCompression compression, + uint8_t quality) + { + if (compression == ImageCompression_None) + { + unsigned int pitch = GetBytesPerPixel(source.GetFormat()) * source.GetWidth(); + target.resize(pitch * source.GetHeight()); + + for (unsigned int i = 0; i < source.GetHeight(); i++) + { + memcpy(&target[i * pitch], source.GetConstRow(i), pitch); + } + } + else + { + std::auto_ptr<Orthanc::IImageWriter> writer; + + switch (compression) + { + case ImageCompression_Png: + writer.reset(new Orthanc::PngWriter); + break; + + case ImageCompression_Jpeg: + writer.reset(new Orthanc::JpegWriter); + dynamic_cast<Orthanc::JpegWriter&>(*writer).SetQuality(quality); + break; + + case ImageCompression_Jpeg2000: + writer.reset(new Jpeg2000Writer); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + writer->WriteToMemory(target, source); + } + } + + + void ChangeTileCompression(std::string& target, + const std::string& source, + ImageCompression sourceCompression, + ImageCompression targetCompression, + uint8_t quality) + { + if (sourceCompression == targetCompression) + { + target = source; + } + else + { + std::auto_ptr<Orthanc::ImageAccessor> decoded(DecodeTile(source, sourceCompression)); + EncodeTile(target, *decoded, targetCompression, quality); + } + } + + + static uint8_t GetPixelValue(const Orthanc::ImageAccessor& source, + unsigned int x, + unsigned int y, + unsigned int channel, + int offsetX, + int offsetY) + { + assert(channel < source.GetBytesPerPixel()); + assert(source.GetFormat() == Orthanc::PixelFormat_Grayscale8 || + source.GetFormat() == Orthanc::PixelFormat_RGB24 || + source.GetFormat() == Orthanc::PixelFormat_RGBA32); // 16bpp is unsupported + + if (static_cast<int>(x) + offsetX < 0) + { + x = 0; + } + else + { + x += offsetX; + if (x >= source.GetWidth()) + { + x = source.GetWidth() - 1; + } + } + + if (static_cast<int>(y) + offsetY < 0) + { + y = 0; + } + else + { + y += offsetY; + if (y >= source.GetHeight()) + { + y = source.GetHeight() - 1; + } + } + + return *(reinterpret_cast<const uint8_t*>(source.GetConstBuffer()) + + y * source.GetPitch() + x * source.GetBytesPerPixel() + channel); + } + + + static uint8_t SmoothPixelValue(const Orthanc::ImageAccessor& source, + unsigned int x, + unsigned int y, + unsigned int channel) + { + static const uint32_t kernel[5] = { 1, 4, 6, 4, 1 }; + static const uint32_t normalization = 2 * (1 + 4 + 6 + 4 + 1); + + uint32_t accumulator = 0; + + // Horizontal smoothing + for (int offset = -2; offset <= 2; offset++) + { + accumulator += kernel[offset + 2] * GetPixelValue(source, x, y, channel, offset, 0); + } + + // Vertical smoothing + for (int offset = -2; offset <= 2; offset++) + { + accumulator += kernel[offset + 2] * GetPixelValue(source, x, y, channel, 0, offset); + } + + return static_cast<uint8_t>(accumulator / normalization); + } + + + Orthanc::ImageAccessor* Halve(const Orthanc::ImageAccessor& source, + bool smooth) + { + if (source.GetWidth() % 2 == 1 || + source.GetHeight() % 2 == 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + + if (source.GetFormat() != Orthanc::PixelFormat_Grayscale8 && + source.GetFormat() != Orthanc::PixelFormat_RGB24 && + source.GetFormat() != Orthanc::PixelFormat_RGBA32) // 16bpp is not supported (*) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + unsigned int channelsCount = source.GetBytesPerPixel(); // OK tx (*) + + std::auto_ptr<Orthanc::ImageAccessor> result(Allocate(source.GetFormat(), + source.GetWidth() / 2, + source.GetHeight() / 2)); + + for (unsigned int y = 0; y < source.GetHeight() / 2; y++) + { + uint8_t* q = reinterpret_cast<uint8_t*>(result->GetRow(y)); + + for (unsigned int x = 0; x < source.GetWidth() / 2; x++, q += 3) + { + for (unsigned int c = 0; c < channelsCount; c++) + { + if (smooth) + { + q[c] = SmoothPixelValue(source, 2 * x, 2 * y, c); + } + else + { + q[c] = GetPixelValue(source, 2 * x, 2 * y, c, 0, 0); + } + } + } + } + + return result.release(); + } + + + Orthanc::ImageAccessor* Clone(const Orthanc::ImageAccessor& accessor) + { + std::auto_ptr<Orthanc::ImageAccessor> result(Allocate(accessor.GetFormat(), + accessor.GetWidth(), + accessor.GetHeight())); + Embed(*result, accessor, 0, 0); + + return result.release(); + } + + + Orthanc::ImageAccessor* Render(ITiledPyramid& pyramid, + unsigned int level) + { + std::auto_ptr<Orthanc::ImageAccessor> result(Allocate(pyramid.GetPixelFormat(), + pyramid.GetLevelWidth(level), + pyramid.GetLevelHeight(level))); + + LOG(INFO) << "Rendering a tiled image of size " << result->GetWidth() << "x" << result->GetHeight(); + + for (unsigned int y = 0; y < result->GetHeight(); y += pyramid.GetTileHeight()) + { + for (unsigned int x = 0; x < result->GetWidth(); x += pyramid.GetTileWidth()) + { + std::auto_ptr<Orthanc::ImageAccessor> tile(pyramid.DecodeTile(level, + x / pyramid.GetTileWidth(), + y / pyramid.GetTileHeight())); + Embed(*result, *tile, x, y); + } + } + + return result.release(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/ImageToolbox.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,75 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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/Images/ImageAccessor.h" +#include "Enumerations.h" +#include "Inputs/ITiledPyramid.h" + +#include <stdint.h> + +namespace OrthancWSI +{ + namespace ImageToolbox + { + Orthanc::ImageAccessor* Allocate(Orthanc::PixelFormat format, + unsigned int width, + unsigned int height); + + void Embed(Orthanc::ImageAccessor& target, + const Orthanc::ImageAccessor& source, + unsigned int x, + unsigned int y); + + inline void Copy(Orthanc::ImageAccessor& target, + const Orthanc::ImageAccessor& source) + { + Embed(target, source, 0, 0); + } + + void Set(Orthanc::ImageAccessor& image, + uint8_t r, + uint8_t g, + uint8_t b); + + Orthanc::ImageAccessor* DecodeTile(const std::string& source, + ImageCompression compression); + + void EncodeTile(std::string& target, + const Orthanc::ImageAccessor& source, + ImageCompression compression, + uint8_t quality); // Only for JPEG compression + + void ChangeTileCompression(std::string& target, + const std::string& source, + ImageCompression sourceCompression, + ImageCompression targetCompression, + uint8_t quality); // Only for JPEG compression + + Orthanc::ImageAccessor* Halve(const Orthanc::ImageAccessor& source, + bool smooth); + + Orthanc::ImageAccessor* Clone(const Orthanc::ImageAccessor& accessor); + + Orthanc::ImageAccessor* Render(ITiledPyramid& pyramid, + unsigned int level); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/ImagedVolumeParameters.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,88 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "ImagedVolumeParameters.h" + +#include "Orthanc/Core/OrthancException.h" + +namespace OrthancWSI +{ + ImagedVolumeParameters::ImagedVolumeParameters() + { + // Typical parameters of a specimen millimeters + width_ = 15; + height_ = 15; + depth_ = 1; + offsetX_ = 20; + offsetY_ = 40; + } + + + void ImagedVolumeParameters::SetWidth(float width) + { + if (width <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + width_ = width; + } + + + void ImagedVolumeParameters::SetHeight(float height) + { + if (height <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + height_ = height; + } + + + void ImagedVolumeParameters::SetDepth(float depth) + { + if (depth <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + depth_ = depth; + } + + + void ImagedVolumeParameters::GetLocation(float& physicalX, + float& physicalY, + unsigned int imageX, + unsigned int imageY, + unsigned int totalWidth, + unsigned int totalHeight) const + { + if (imageX >= totalWidth || + imageY >= totalHeight) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + // WARNING: The physical X/Y axes are switched wrt. the image X/Y + physicalX = offsetX_ - GetHeight() * static_cast<float>(imageX) / static_cast<float>(totalWidth); + physicalY = offsetY_ - GetWidth() * static_cast<float>(imageY) / static_cast<float>(totalHeight); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/ImagedVolumeParameters.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,85 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 + +namespace OrthancWSI +{ + class ImagedVolumeParameters + { + private: + float width_; + float height_; + float depth_; + float offsetX_; + float offsetY_; + + public: + ImagedVolumeParameters(); + + float GetWidth() const + { + return width_; + } + + float GetHeight() const + { + return height_; + } + + float GetDepth() const + { + return depth_; + } + + float GetOffsetX() const + { + return offsetX_; + } + + float GetOffsetY() const + { + return offsetY_; + } + + void SetWidth(float width); + + void SetHeight(float height); + + void SetDepth(float depth); + + void SetOffsetX(float offset) + { + offsetX_ = offset; + } + + void SetOffsetY(float offset) + { + offsetY_ = offset; + } + + void GetLocation(float& physicalX, + float& physicalY, + unsigned int imageX, + unsigned int imageY, + unsigned int totalWidth, + unsigned int totalHeight) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/DecodedTiledPyramid.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,117 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "DecodedTiledPyramid.h" + +#include "../ImageToolbox.h" + +#include <memory> +#include <cassert> + +namespace OrthancWSI +{ + DecodedTiledPyramid::DecodedTiledPyramid() + { + SetBackgroundColor(255, 255, 255); + } + + + void DecodedTiledPyramid::SetBackgroundColor(uint8_t red, + uint8_t green, + uint8_t blue) + { + backgroundColor_[0] = red; + backgroundColor_[1] = green; + backgroundColor_[2] = blue; + } + + + void DecodedTiledPyramid::GetBackgroundColor(uint8_t& red, + uint8_t& green, + uint8_t& blue) const + { + red = backgroundColor_[0]; + green = backgroundColor_[1]; + blue = backgroundColor_[2]; + } + + + Orthanc::ImageAccessor* DecodedTiledPyramid::DecodeTile(unsigned int level, + unsigned int tileX, + unsigned int tileY) + { + unsigned int x = tileX * GetTileWidth(); + unsigned int y = tileY * GetTileHeight(); + + std::auto_ptr<Orthanc::ImageAccessor> tile + (ImageToolbox::Allocate(GetPixelFormat(), GetTileWidth(), GetTileHeight())); + + if (x >= GetLevelWidth(level) || + y >= GetLevelHeight(level)) // (*) + { + ImageToolbox::Set(*tile, backgroundColor_[0], backgroundColor_[1], backgroundColor_[2]); + return tile.release(); + } + + bool fit = true; + unsigned int regionWidth; + if (x + GetTileWidth() <= GetLevelWidth(level)) + { + regionWidth = GetTileWidth(); + } + else + { + assert(GetLevelWidth(level) >= x); // This results from (*) + regionWidth = GetLevelWidth(level) - x; + fit = false; + } + + unsigned int regionHeight; + if (y + GetTileHeight() <= GetLevelHeight(level)) + { + regionHeight = GetTileHeight(); + } + else + { + assert(GetLevelHeight(level) >= y); // This results from (*) + regionHeight = GetLevelHeight(level) - y; + fit = false; + } + + if (fit) + { + // The tile entirely lies inside the image + ReadRegion(*tile, level, x, y); + } + else + { + // The tile exceeds the size of image, decode it to a temporary buffer + std::auto_ptr<Orthanc::ImageAccessor> cropped + (ImageToolbox::Allocate(GetPixelFormat(), regionWidth, regionHeight)); + ReadRegion(*cropped, level, x, y); + + // Create a white tile, and fill it with the cropped content + ImageToolbox::Set(*tile, backgroundColor_[0], backgroundColor_[1], backgroundColor_[2]); + ImageToolbox::Embed(*tile, *cropped, 0, 0); + } + + return tile.release(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/DecodedTiledPyramid.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,75 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "ITiledPyramid.h" + + +namespace OrthancWSI +{ + /** + * This class acts as a wrapper for "cropped" tiled images, where + * the tiles at the right or at the bottom might not have the same + * dimensions of the other slides. + **/ + class DecodedTiledPyramid : public ITiledPyramid + { + private: + uint8_t backgroundColor_[3]; + + protected: + // Subclasses can assume that the requested region is fully inside + // the image, and that target has the proper size to store the + // region. Pay attention to implement mutual exclusion in subclasses. + virtual void ReadRegion(Orthanc::ImageAccessor& target, + unsigned int level, + unsigned int x, + unsigned int y) = 0; + + public: + DecodedTiledPyramid(); + + void SetBackgroundColor(uint8_t red, + uint8_t green, + uint8_t blue); + + void GetBackgroundColor(uint8_t& red, + uint8_t& green, + uint8_t& blue) const; + + virtual Orthanc::ImageAccessor* DecodeTile(unsigned int level, + unsigned int tileX, + unsigned int tileY); + + virtual ImageCompression GetImageCompression() const + { + return ImageCompression_None; + } + + virtual bool ReadRawTile(std::string& tile, + unsigned int level, + unsigned int tileX, + unsigned int tileY) + { + return false; // No access to the raw tiles + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/DicomPyramid.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,237 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "DicomPyramid.h" + +#include "../DicomToolbox.h" +#include "../Orthanc/Core/Logging.h" +#include "../Orthanc/Core/OrthancException.h" + +#include <algorithm> +#include <cassert> + +namespace OrthancWSI +{ + struct DicomPyramid::Comparator + { + bool operator() (DicomPyramidInstance* const& a, + DicomPyramidInstance* const& b) const + { + return a->GetTotalWidth() > b->GetTotalWidth(); + } + }; + + + void DicomPyramid::Clear() + { + for (size_t i = 0; i < levels_.size(); i++) + { + if (levels_[i] != NULL) + { + delete levels_[i]; + } + } + + for (size_t i = 0; i < instances_.size(); i++) + { + if (instances_[i] != NULL) + { + delete instances_[i]; + } + } + } + + + void DicomPyramid::RegisterInstances(const std::string& seriesId) + { + Json::Value series; + IOrthancConnection::RestApiGet(series, orthanc_, "/series/" + seriesId); + + if (series.type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + const Json::Value& instances = DicomToolbox::GetSequenceTag(series, "Instances"); + instances_.reserve(instances.size()); + + for (Json::Value::ArrayIndex i = 0; i < instances.size(); i++) + { + if (instances[i].type() != Json::stringValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + std::string instance = instances[i].asString(); + + try + { + instances_.push_back(new DicomPyramidInstance(orthanc_, instance)); + } + catch (Orthanc::OrthancException&) + { + LOG(ERROR) << "Skipping a DICOM instance that is not part of a whole-slide image: " << instance; + } + } + } + + + void DicomPyramid::Check(const std::string& seriesId) const + { + if (instances_.empty()) + { + LOG(ERROR) << "This series does not contain a whole-slide image: " << seriesId; + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + + const DicomPyramidInstance& a = *instances_[0]; + + for (size_t i = 1; i < instances_.size(); i++) + { + const DicomPyramidInstance& b = *instances_[i]; + + if (a.GetImageCompression() != b.GetImageCompression() || + a.GetPixelFormat() != b.GetPixelFormat() || + a.GetTileWidth() != b.GetTileWidth() || + a.GetTileHeight() != b.GetTileHeight() || + a.GetTotalWidth() < b.GetTotalWidth() || + a.GetTotalHeight() < b.GetTotalHeight()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + if (a.GetTotalWidth() == b.GetTotalWidth() && + a.GetTotalHeight() != b.GetTotalHeight()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + } + } + + + void DicomPyramid::CheckLevel(size_t level) const + { + if (level >= levels_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + DicomPyramid::DicomPyramid(IOrthancConnection& orthanc, + const std::string& seriesId) : + orthanc_(orthanc), + seriesId_(seriesId) + { + RegisterInstances(seriesId); + + // Sort the instances of the pyramid by decreasing total widths + std::sort(instances_.begin(), instances_.end(), Comparator()); + + try + { + Check(seriesId); + } + catch (Orthanc::OrthancException&) + { + Clear(); + throw; + } + + for (size_t i = 0; i < instances_.size(); i++) + { + if (i == 0 || + instances_[i - 1]->GetTotalWidth() != instances_[i]->GetTotalWidth()) + { + levels_.push_back(new DicomPyramidLevel(*instances_[i])); + } + else + { + assert(levels_.back() != NULL); + levels_.back()->AddInstance(*instances_[i]); + } + } + } + + + unsigned int DicomPyramid::GetLevelWidth(unsigned int level) const + { + CheckLevel(level); + return levels_[level]->GetTotalWidth(); + } + + + unsigned int DicomPyramid::GetLevelHeight(unsigned int level) const + { + CheckLevel(level); + return levels_[level]->GetTotalHeight(); + } + + + unsigned int DicomPyramid::GetTileWidth() const + { + assert(!levels_.empty() && levels_[0] != NULL); + return levels_[0]->GetTileWidth(); + } + + + unsigned int DicomPyramid::GetTileHeight() const + { + assert(!levels_.empty() && levels_[0] != NULL); + return levels_[0]->GetTileHeight(); + } + + + bool DicomPyramid::ReadRawTile(std::string& tile, + unsigned int level, + unsigned int tileX, + unsigned int tileY) + { + CheckLevel(level); + + ImageCompression compression; + Orthanc::PixelFormat format; + + if (levels_[level]->DownloadRawTile(compression, format, tile, orthanc_, tileX, tileY)) + { + assert(compression == GetImageCompression() && + format == GetPixelFormat()); + return true; + } + else + { + return false; + } + } + + + ImageCompression DicomPyramid::GetImageCompression() const + { + assert(!instances_.empty() && instances_[0] != NULL); + return instances_[0]->GetImageCompression(); + } + + + Orthanc::PixelFormat DicomPyramid::GetPixelFormat() const + { + assert(!instances_.empty() && instances_[0] != NULL); + return instances_[0]->GetPixelFormat(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/DicomPyramid.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,83 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "PyramidWithRawTiles.h" +#include "DicomPyramidInstance.h" +#include "DicomPyramidLevel.h" + +namespace OrthancWSI +{ + class DicomPyramid : public PyramidWithRawTiles + { + private: + struct Comparator; + + IOrthancConnection& orthanc_; + std::string seriesId_; + std::vector<DicomPyramidInstance*> instances_; + std::vector<DicomPyramidLevel*> levels_; + + void Clear(); + + void RegisterInstances(const std::string& seriesId); + + void Check(const std::string& seriesId) const; + + void CheckLevel(size_t level) const; + + public: + DicomPyramid(IOrthancConnection& orthanc, + const std::string& seriesId); + + virtual ~DicomPyramid() + { + Clear(); + } + + const std::string& GetSeriesId() const + { + return seriesId_; + } + + virtual unsigned int GetLevelCount() const + { + return levels_.size(); + } + + virtual unsigned int GetLevelWidth(unsigned int level) const; + + virtual unsigned int GetLevelHeight(unsigned int level) const; + + virtual unsigned int GetTileWidth() const; + + virtual unsigned int GetTileHeight() const; + + virtual bool ReadRawTile(std::string& tile, + unsigned int level, + unsigned int tileX, + unsigned int tileY); + + virtual ImageCompression GetImageCompression() const; + + virtual Orthanc::PixelFormat GetPixelFormat() const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/DicomPyramidInstance.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,170 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "DicomPyramidInstance.h" + +#include "../Orthanc/Core/Logging.h" +#include "../Orthanc/Core/OrthancException.h" +#include "../Orthanc/Core/Toolbox.h" +#include "../DicomToolbox.h" + +#include <cassert> + +namespace OrthancWSI +{ + static ImageCompression DetectImageCompression(const Json::Value& header) + { + std::string s = Orthanc::Toolbox::StripSpaces + (DicomToolbox::GetMandatoryStringTag(header, "TransferSyntaxUID")); + + if (s == "1.2.840.10008.1.2" || + s == "1.2.840.10008.1.2.1") + { + return ImageCompression_None; + } + else if (s == "1.2.840.10008.1.2.4.50") + { + return ImageCompression_Jpeg; + } + else if (s == "1.2.840.10008.1.2.4.90" || + s == "1.2.840.10008.1.2.4.91") + { + return ImageCompression_Jpeg2000; + } + else + { + LOG(ERROR) << "Unsupported transfer syntax: " << s; + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + + static Orthanc::PixelFormat DetectPixelFormat(const Json::Value& dicom) + { + std::string photometric = Orthanc::Toolbox::StripSpaces + (DicomToolbox::GetMandatoryStringTag(dicom, "PhotometricInterpretation")); + + if (photometric == "PALETTE") + { + LOG(ERROR) << "Unsupported photometric interpretation: " << photometric; + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + unsigned int bitsStored = DicomToolbox::GetUnsignedIntegerTag(dicom, "BitsStored"); + unsigned int samplesPerPixel = DicomToolbox::GetUnsignedIntegerTag(dicom, "SamplesPerPixel"); + bool isSigned = (DicomToolbox::GetUnsignedIntegerTag(dicom, "PixelRepresentation") != 0); + + if (bitsStored == 8 && + samplesPerPixel == 1 && + !isSigned) + { + return Orthanc::PixelFormat_Grayscale8; + } + else if (bitsStored == 8 && + samplesPerPixel == 3 && + !isSigned) + { + return Orthanc::PixelFormat_RGB24; + } + else + { + LOG(ERROR) << "Unsupported pixel format"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + + DicomPyramidInstance::DicomPyramidInstance(IOrthancConnection& orthanc, + const std::string& instanceId) : + instanceId_(instanceId) + { + Json::Value dicom, header; + IOrthancConnection::RestApiGet(dicom, orthanc, "/instances/" + instanceId + "/tags?simplify"); + IOrthancConnection::RestApiGet(header, orthanc, "/instances/" + instanceId + "/header?simplify"); + + if (DicomToolbox::GetMandatoryStringTag(dicom, "SOPClassUID") != "1.2.840.10008.5.1.4.1.1.77.1.6" || + DicomToolbox::GetMandatoryStringTag(dicom, "Modality") != "SM") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + compression_ = DetectImageCompression(header); + format_ = DetectPixelFormat(dicom); + tileWidth_ = DicomToolbox::GetUnsignedIntegerTag(dicom, "Columns"); + tileHeight_ = DicomToolbox::GetUnsignedIntegerTag(dicom, "Rows"); + totalWidth_ = DicomToolbox::GetUnsignedIntegerTag(dicom, "TotalPixelMatrixColumns"); + totalHeight_ = DicomToolbox::GetUnsignedIntegerTag(dicom, "TotalPixelMatrixRows"); + + const Json::Value& frames = DicomToolbox::GetSequenceTag(dicom, "PerFrameFunctionalGroupsSequence"); + + if (frames.size() != DicomToolbox::GetUnsignedIntegerTag(dicom, "NumberOfFrames")) + { + LOG(ERROR) << "Mismatch between the number of frames in instance: " << instanceId; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + frames_.resize(frames.size()); + + for (Json::Value::ArrayIndex i = 0; i < frames.size(); i++) + { + const Json::Value& frame = DicomToolbox::GetSequenceTag(frames[i], "PlanePositionSlideSequence"); + if (frame.size() != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + // "-1", because coordinates are shifted by 1 in DICOM + int xx = DicomToolbox::GetIntegerTag(frame[0], "ColumnPositionInTotalImagePixelMatrix") - 1; + int yy = DicomToolbox::GetIntegerTag(frame[0], "RowPositionInTotalImagePixelMatrix") - 1; + + unsigned int x = static_cast<unsigned int>(xx); + unsigned int y = static_cast<unsigned int>(yy); + + if (xx < 0 || + yy < 0 || + x % tileWidth_ != 0 || + y % tileHeight_ != 0 || + x >= totalWidth_ || + y >= totalHeight_) + { + LOG(ERROR) << "Frame " << i << " with unexpected tile location (" + << x << "," << y << ") in instance: " << instanceId; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + frames_[i].first = x / tileWidth_; + frames_[i].second = y / tileHeight_; + } + } + + + unsigned int DicomPyramidInstance::GetFrameLocationX(size_t frame) const + { + assert(frame < frames_.size()); + return frames_[frame].first; + } + + + unsigned int DicomPyramidInstance::GetFrameLocationY(size_t frame) const + { + assert(frame < frames_.size()); + return frames_[frame].second; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/DicomPyramidInstance.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,93 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "../Enumerations.h" +#include "../Messaging/IOrthancConnection.h" + +#include <boost/noncopyable.hpp> +#include <vector> + +namespace OrthancWSI +{ + class DicomPyramidInstance : public boost::noncopyable + { + private: + typedef std::pair<unsigned int, unsigned int> FrameLocation; + + std::string instanceId_; + ImageCompression compression_; + Orthanc::PixelFormat format_; + unsigned int tileWidth_; + unsigned int tileHeight_; + unsigned int totalWidth_; + unsigned int totalHeight_; + std::vector<FrameLocation> frames_; + + public: + DicomPyramidInstance(IOrthancConnection& orthanc, + const std::string& instanceId); + + const std::string& GetInstanceId() const + { + return instanceId_; + } + + ImageCompression GetImageCompression() const + { + return compression_; + } + + Orthanc::PixelFormat GetPixelFormat() const + { + return format_; + } + + unsigned int GetTotalWidth() const + { + return totalWidth_; + } + + unsigned int GetTotalHeight() const + { + return totalHeight_; + } + + unsigned int GetTileWidth() const + { + return tileWidth_; + } + + unsigned int GetTileHeight() const + { + return tileHeight_; + } + + size_t GetFrameCount() const + { + return frames_.size(); + } + + unsigned int GetFrameLocationX(size_t frame) const; + + unsigned int GetFrameLocationY(size_t frame) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/DicomPyramidLevel.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,128 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "DicomPyramidLevel.h" + +#include "../Orthanc/Core/Logging.h" +#include "../Orthanc/Core/OrthancException.h" + +#include <boost/lexical_cast.hpp> + +namespace OrthancWSI +{ + void DicomPyramidLevel::RegisterFrame(const DicomPyramidInstance& instance, + unsigned int frame) + { + TileLocation location(instance.GetFrameLocationX(frame), + instance.GetFrameLocationY(frame)); + + if (tiles_.find(location) != tiles_.end()) + { + LOG(ERROR) << "Tile with location (" << location.first << "," + << location.second << ") is indexed twice in level of size " + << totalWidth_ << "x" << totalHeight_; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + tiles_[location] = std::make_pair(&instance, frame); + } + + + bool DicomPyramidLevel::LookupTile(TileContent& tile, + unsigned int tileX, + unsigned int tileY) const + { + Tiles::const_iterator found = tiles_.find(std::make_pair(tileX, tileY)); + if (found == tiles_.end()) + { + return false; + } + else + { + tile = found->second; + return true; + } + } + + + DicomPyramidLevel::DicomPyramidLevel(const DicomPyramidInstance& instance) : + totalWidth_(instance.GetTotalWidth()), + totalHeight_(instance.GetTotalHeight()), + tileWidth_(instance.GetTileWidth()), + tileHeight_(instance.GetTileHeight()) + { + if (totalWidth_ == 0 || + totalHeight_ == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + AddInstance(instance); + } + + + void DicomPyramidLevel::AddInstance(const DicomPyramidInstance& instance) + { + if (instance.GetTotalWidth() != totalWidth_ || + instance.GetTotalHeight() != totalHeight_ || + instance.GetTileWidth() != tileWidth_ || + instance.GetTileHeight() != tileHeight_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + + instances_.push_back(&instance); + + for (size_t frame = 0; frame < instance.GetFrameCount(); frame++) + { + RegisterFrame(instance, frame); + } + } + + + bool DicomPyramidLevel::DownloadRawTile(ImageCompression& compression /* out */, + Orthanc::PixelFormat& format /* out */, + std::string& raw /* out */, + IOrthancConnection& orthanc, + unsigned int tileX, + unsigned int tileY) const + { + TileContent tile; + if (LookupTile(tile, tileX, tileY)) + { + assert(tile.first != NULL); + const DicomPyramidInstance& instance = *tile.first; + + std::string uri = ("/instances/" + instance.GetInstanceId() + + "/frames/" + boost::lexical_cast<std::string>(tile.second) + "/raw"); + + orthanc.RestApiGet(raw, uri); + + compression = instance.GetImageCompression(); + format = instance.GetPixelFormat(); + + return true; + } + else + { + return false; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/DicomPyramidLevel.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,83 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "DicomPyramidInstance.h" + +#include <list> + +namespace OrthancWSI +{ + class DicomPyramidLevel : public boost::noncopyable + { + private: + typedef std::pair<unsigned int, unsigned int> TileLocation; + typedef std::pair<const DicomPyramidInstance*, unsigned int> TileContent; + typedef std::map<TileLocation, TileContent> Tiles; + typedef std::list<const DicomPyramidInstance*> Instances; + + unsigned int totalWidth_; + unsigned int totalHeight_; + unsigned int tileWidth_; + unsigned int tileHeight_; + Instances instances_; + Tiles tiles_; + + void RegisterFrame(const DicomPyramidInstance& instance, + unsigned int frame); + + bool LookupTile(TileContent& tile, + unsigned int tileX, + unsigned int tileY) const; + + public: + DicomPyramidLevel(const DicomPyramidInstance& instance); + + void AddInstance(const DicomPyramidInstance& instance); + + unsigned int GetTotalWidth() const + { + return totalWidth_; + } + + unsigned int GetTotalHeight() const + { + return totalHeight_; + } + + unsigned int GetTileWidth() const + { + return tileWidth_; + } + + unsigned int GetTileHeight() const + { + return tileHeight_; + } + + bool DownloadRawTile(ImageCompression& compression /* out */, + Orthanc::PixelFormat& format /* out */, + std::string& raw /* out */, + IOrthancConnection& orthanc, + unsigned int tileX, + unsigned int tileY) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/HierarchicalTiff.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,307 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "HierarchicalTiff.h" + +#include "../Orthanc/Core/Logging.h" +#include "../Orthanc/Core/OrthancException.h" + +#include <iostream> +#include <algorithm> +#include <cassert> +#include <string.h> + +namespace OrthancWSI +{ + HierarchicalTiff::Level::Level(TIFF* tiff, + tdir_t directory, + unsigned int width, + unsigned int height) : + directory_(directory), + width_(width), + height_(height) + { + // Read the JPEG headers shared at that level, if any + uint8_t *tables = NULL; + uint32_t size; + if (TIFFGetField(tiff, TIFFTAG_JPEGTABLES, &size, &tables) && + size > 0 && + tables != NULL) + { + // Look for the EOI (end-of-image) tag == FF D9 + // https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format + + bool found = false; + + for (size_t i = 0; i + 1 < size; i++) + { + if (tables[i] == 0xff && + tables[i + 1] == 0xd9) + { + headers_.assign(reinterpret_cast<const char*>(tables), i); + found = true; + } + } + + if (!found) + { + headers_.assign(reinterpret_cast<const char*>(tables), size); + } + } + } + + struct HierarchicalTiff::Comparator + { + bool operator() (const HierarchicalTiff::Level& a, + const HierarchicalTiff::Level& b) const + { + return a.width_ > b.width_; + } + }; + + + void HierarchicalTiff::Finalize() + { + if (tiff_) + { + TIFFClose(tiff_); + tiff_ = NULL; + } + } + + + void HierarchicalTiff::CheckLevel(unsigned int level) const + { + if (level >= levels_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + bool HierarchicalTiff::GetCurrentCompression(ImageCompression& compression) + { + uint16_t c; + if (!TIFFGetField(tiff_, TIFFTAG_COMPRESSION, &c)) + { + return false; + } + + switch (c) + { + case COMPRESSION_NONE: + compression = ImageCompression_None; + return true; + + case COMPRESSION_JPEG: + compression = ImageCompression_Jpeg; + return true; + + default: + return false; + } + } + + + bool HierarchicalTiff::GetCurrentPixelFormat(Orthanc::PixelFormat& pixelFormat, + ImageCompression compression) + { + // http://www.awaresystems.be/imaging/tiff/tifftags/baseline.html + + uint16_t channels, photometric, bpp, planar; + if (!TIFFGetField(tiff_, TIFFTAG_SAMPLESPERPIXEL, &channels) || + channels == 0 || + !TIFFGetField(tiff_, TIFFTAG_PHOTOMETRIC, &photometric) || + !TIFFGetField(tiff_, TIFFTAG_BITSPERSAMPLE, &bpp) || + !TIFFGetField(tiff_, TIFFTAG_PLANARCONFIG, &planar)) + { + return false; + } + + if (compression == ImageCompression_Jpeg && + channels == 3 && // This is a color image + bpp == 8 && + photometric == PHOTOMETRIC_YCBCR && + planar == PLANARCONFIG_CONTIG) // This is interleaved RGB + { + pixelFormat = Orthanc::PixelFormat_RGB24; + } + else + { + return false; + } + + return true; + } + + + bool HierarchicalTiff::Initialize() + { + bool first = true; + tdir_t pos = 0; + + do + { + uint32_t w, h, tw, th; + ImageCompression compression; + Orthanc::PixelFormat pixelFormat; + + if (TIFFSetDirectory(tiff_, pos) && + TIFFGetField(tiff_, TIFFTAG_IMAGEWIDTH, &w) && + TIFFGetField(tiff_, TIFFTAG_IMAGELENGTH, &h) && + TIFFGetField(tiff_, TIFFTAG_TILEWIDTH, &tw) && + TIFFGetField(tiff_, TIFFTAG_TILELENGTH, &th) && + w > 0 && + h > 0 && + tw > 0 && + th > 0 && + GetCurrentCompression(compression) && + GetCurrentPixelFormat(pixelFormat, compression)) + { + if (first) + { + tileWidth_ = tw; + tileHeight_ = th; + compression_ = compression; + pixelFormat_ = pixelFormat; + first = false; + } + else if (tw != tileWidth_ || + th != tileHeight_ || + compression_ != compression || + pixelFormat_ != pixelFormat) + { + LOG(ERROR) << "The tile size or compression of the TIFF file varies along levels, this is not supported"; + return false; + } + + levels_.push_back(Level(tiff_, pos, w, h)); + } + + pos++; + } + while (TIFFReadDirectory(tiff_)); + + if (levels_.size() == 0) + { + LOG(ERROR) << "This is not a tiled TIFF image"; + return false; + } + + std::sort(levels_.begin(), levels_.end(), Comparator()); + return true; + } + + + HierarchicalTiff::HierarchicalTiff(const std::string& path) : + tileWidth_(0), + tileHeight_(0) + { + tiff_ = TIFFOpen(path.c_str(), "r"); + if (tiff_ == NULL) + { + LOG(ERROR) << "libtiff cannot open: " << path; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentFile); + } + + if (!Initialize()) + { + Finalize(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + + unsigned int HierarchicalTiff::GetLevelWidth(unsigned int level) const + { + CheckLevel(level); + return levels_[level].width_; + } + + + unsigned int HierarchicalTiff::GetLevelHeight(unsigned int level) const + { + CheckLevel(level); + return levels_[level].height_; + } + + + bool HierarchicalTiff::ReadRawTile(std::string& tile, + unsigned int level, + unsigned int tileX, + unsigned int tileY) + { + boost::mutex::scoped_lock lock(mutex_); + + CheckLevel(level); + + // Make the TIFF context point to the level of interest + if (!TIFFSetDirectory(tiff_, levels_[level].directory_)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile); + } + + // Get the index of the tile + ttile_t index = TIFFComputeTile(tiff_, tileX * tileWidth_, tileY * tileHeight_, 0 /*z*/, 0 /*sample*/); + + // Read the raw tile + toff_t *sizes; + if (!TIFFGetField(tiff_, TIFFTAG_TILEBYTECOUNTS, &sizes)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile); + } + + std::string raw; + raw.resize(sizes[index]); + + tsize_t read = TIFFReadRawTile(tiff_, index, &raw[0], raw.size()); + if (read != static_cast<tsize_t>(sizes[index])) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile); + } + + const std::string& headers = levels_[level].headers_; + + // Possibly prepend the raw tile with the shared JPEG headers + if (headers.empty() || + compression_ != ImageCompression_Jpeg) + { + tile.swap(raw); // Same as "tile.assign(raw)", but optimizes memory + } + else + { + assert(compression_ == ImageCompression_Jpeg); + + // Check that the raw JPEG tile starts with the SOI (start-of-image) tag == FF D8 + if (raw.size() < 2 || + static_cast<uint8_t>(raw[0]) != 0xff || + static_cast<uint8_t>(raw[1]) != 0xd8) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile); + } + + tile.resize(headers.size() + raw.size() - 2); + memcpy(&tile[0], &headers[0], headers.size()); + memcpy(&tile[0] + headers.size(), &raw[2], raw.size() - 2); + } + + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/HierarchicalTiff.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,111 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "PyramidWithRawTiles.h" + +#include <tiff.h> +#include <tiffio.h> +#include <vector> +#include <boost/thread.hpp> + +namespace OrthancWSI +{ + class HierarchicalTiff : public PyramidWithRawTiles + { + private: + struct Level + { + tdir_t directory_; + unsigned int width_; + unsigned int height_; + std::string headers_; + + Level(TIFF* tiff, + tdir_t directory, + unsigned int width, + unsigned int height); + }; + + struct Comparator; + + boost::mutex mutex_; + TIFF* tiff_; + Orthanc::PixelFormat pixelFormat_; + ImageCompression compression_; + unsigned int tileWidth_; + unsigned int tileHeight_; + std::vector<Level> levels_; + + void Finalize(); + + void CheckLevel(unsigned int level) const; + + bool GetCurrentCompression(ImageCompression& compression); + + bool GetCurrentPixelFormat(Orthanc::PixelFormat& pixelFormat, + ImageCompression compression); + + bool Initialize(); + + public: + HierarchicalTiff(const std::string& path); + + virtual ~HierarchicalTiff() + { + Finalize(); + } + + virtual unsigned int GetLevelCount() const + { + return levels_.size(); + } + + virtual unsigned int GetLevelWidth(unsigned int level) const; + + virtual unsigned int GetLevelHeight(unsigned int level) const; + + virtual unsigned int GetTileWidth() const + { + return tileWidth_; + } + + virtual unsigned int GetTileHeight() const + { + return tileHeight_; + } + + virtual bool ReadRawTile(std::string& tile, + unsigned int level, + unsigned int tileX, + unsigned int tileY); + + virtual ImageCompression GetImageCompression() const + { + return compression_; + } + + virtual Orthanc::PixelFormat GetPixelFormat() const + { + return pixelFormat_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/ITiledPyramid.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,68 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "../Enumerations.h" + +#include "../Orthanc/Core/Images/ImageAccessor.h" + +#include <boost/noncopyable.hpp> +#include <string> + + +namespace OrthancWSI +{ + /** + * Class that represents a whole-slide image. It is assumed to be + * thread-safe, which is the case of libtiff and openslide. + **/ + class ITiledPyramid : public boost::noncopyable + { + public: + virtual ~ITiledPyramid() + { + } + + virtual unsigned int GetLevelCount() const = 0; + + virtual unsigned int GetLevelWidth(unsigned int level) const = 0; + + virtual unsigned int GetLevelHeight(unsigned int level) const = 0; + + virtual unsigned int GetTileWidth() const = 0; + + virtual unsigned int GetTileHeight() const = 0; + + virtual bool ReadRawTile(std::string& tile, + unsigned int level, + unsigned int tileX, + unsigned int tileY) = 0; + + virtual Orthanc::ImageAccessor* DecodeTile(unsigned int level, + unsigned int tileX, + unsigned int tileY) = 0; + + // Only makes sense for images with raw access to tiles + virtual ImageCompression GetImageCompression() const = 0; + + virtual Orthanc::PixelFormat GetPixelFormat() const = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/OpenSlideLibrary.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,233 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "OpenSlideLibrary.h" + +#include "../Orthanc/Core/Logging.h" +#include "../Orthanc/Core/Images/Image.h" + +namespace OrthancWSI +{ + static std::auto_ptr<OpenSlideLibrary> globalLibrary_; + + + OpenSlideLibrary::OpenSlideLibrary(const std::string& path) : + library_(path) + { + close_ = (FunctionClose) library_.GetFunction("openslide_close"); + getLevelCount_ = (FunctionGetLevelCount) library_.GetFunction("openslide_get_level_count"); + getLevelDimensions_ = (FunctionGetLevelDimensions) library_.GetFunction("openslide_get_level_dimensions"); + getLevelDownsample_ = (FunctionGetLevelDownsample) library_.GetFunction("openslide_get_level_downsample"); + open_ = (FunctionOpen) library_.GetFunction("openslide_open"); + readRegion_ = (FunctionReadRegion) library_.GetFunction("openslide_read_region"); + } + + + OpenSlideLibrary::Image::Level::Level() : + width_(0), + height_(0), + downsample_(1) + { + } + + + OpenSlideLibrary::Image::Level::Level(int64_t width, + int64_t height, + double downsample) : + width_(static_cast<unsigned int>(width)), + height_(static_cast<unsigned int>(height)), + downsample_(downsample) + { + if (width < 0 || + height < 0 || + downsample <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + if (static_cast<int64_t>(width_) != width || + static_cast<int64_t>(height_) != height) + { + LOG(ERROR) << "The whole-slide image is too large"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + void OpenSlideLibrary::Image::Initialize(const std::string& path) + { + handle_ = that_.open_(path.c_str()); + if (handle_ == NULL) + { + LOG(ERROR) << "Cannot open an image with OpenSlide: " << path; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + try + { + LOG(INFO) << "Opening an image with OpenSlide: " << path; + + int32_t tmp = that_.getLevelCount_(handle_); + if (tmp <= 0) + { + LOG(ERROR) << "Image with no pyramid level"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + levels_.resize(tmp); + + for (int32_t level = 0; level < tmp; level++) + { + int64_t width, height; + that_.getLevelDimensions_(handle_, level, &width, &height); + + double downsample = that_.getLevelDownsample_(handle_, level); + + levels_[level] = Level(width, height, downsample); + } + + for (size_t i = 1; i < levels_.size(); i++) + { + if (levels_[i].width_ >= levels_[i - 1].width_ || + levels_[i].height_ >= levels_[i - 1].height_) + { + // This is not a pyramid with levels of decreasing sizes + // (level "0" must be the finest level) + LOG(ERROR) << "The pyramid does not have levels of strictly decreasing sizes"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + } + catch (Orthanc::OrthancException&) + { + Close(); + throw; + } + } + + + OpenSlideLibrary::Image::Image(OpenSlideLibrary& that, + const std::string& path) : + that_(that), + handle_(NULL) + { + Initialize(path); + } + + + OpenSlideLibrary::Image::Image(const std::string& path) : + that_(OpenSlideLibrary::GetInstance()), + handle_(NULL) + { + Initialize(path); + } + + + void OpenSlideLibrary::Image::Close() + { + if (handle_ != NULL) + { + that_.close_(handle_); + handle_ = NULL; + } + } + + + void OpenSlideLibrary::Image::CheckLevel(unsigned int level) const + { + if (level >= levels_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + double OpenSlideLibrary::Image::GetLevelDownsample(unsigned int level) const + { + CheckLevel(level); + return levels_[level].downsample_; + } + + + unsigned int OpenSlideLibrary::Image::GetLevelWidth(unsigned int level) const + { + CheckLevel(level); + return levels_[level].width_; + } + + + unsigned int OpenSlideLibrary::Image::GetLevelHeight(unsigned int level) const + { + CheckLevel(level); + return levels_[level].height_; + } + + + Orthanc::ImageAccessor* OpenSlideLibrary::Image::ReadRegion(unsigned int level, + uint64_t x, + uint64_t y, + unsigned int width, + unsigned int height) + { + CheckLevel(level); + + // Create a new image, with minimal pitch so as to be compatible with OpenSlide API + std::auto_ptr<Orthanc::ImageAccessor> region(new Orthanc::Image(Orthanc::PixelFormat_RGBA32, width, height, true)); + + if (region->GetWidth() != 0 && + region->GetHeight() != 0) + { + double zoom = levels_[level].downsample_; + x = static_cast<uint64_t>(zoom * static_cast<double>(x)); + y = static_cast<uint64_t>(zoom * static_cast<double>(y)); + + that_.readRegion_(handle_, reinterpret_cast<uint32_t*>(region->GetBuffer()), + x, y, level, region->GetWidth(), region->GetHeight()); + } + + return region.release(); + } + + + OpenSlideLibrary& OpenSlideLibrary::GetInstance() + { + if (globalLibrary_.get() == NULL) + { + LOG(ERROR) << "OpenSlide has not been initialized"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return *globalLibrary_; + } + } + + + void OpenSlideLibrary::Initialize(const std::string& path) + { + globalLibrary_.reset(new OpenSlideLibrary(path)); + } + + + void OpenSlideLibrary::Finalize() + { + globalLibrary_.reset(NULL); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/OpenSlideLibrary.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,112 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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/Plugins/Engine/SharedLibrary.h" +#include "../Orthanc/Core/Images/ImageAccessor.h" + +#include <vector> + +namespace OrthancWSI +{ + class OpenSlideLibrary : public boost::noncopyable + { + private: + typedef void* (*FunctionClose) (void*); + typedef int32_t (*FunctionGetLevelCount) (void*); + typedef void (*FunctionGetLevelDimensions) (void*, int32_t, int64_t*, int64_t*); + typedef double (*FunctionGetLevelDownsample) (void*, int32_t); + typedef void* (*FunctionOpen) (const char*); + typedef void (*FunctionReadRegion) (void*, uint32_t*, int64_t, int64_t, int32_t, int64_t, int64_t); + + Orthanc::SharedLibrary library_; + FunctionClose close_; + FunctionGetLevelCount getLevelCount_; + FunctionGetLevelDimensions getLevelDimensions_; + FunctionGetLevelDownsample getLevelDownsample_; + FunctionOpen open_; + FunctionReadRegion readRegion_; + + public: + OpenSlideLibrary(const std::string& path); + + static OpenSlideLibrary& GetInstance(); + + static void Initialize(const std::string& path); + + static void Finalize(); + + class Image : public boost::noncopyable + { + private: + struct Level + { + unsigned int width_; + unsigned int height_; + double downsample_; + + Level(); + + Level(int64_t width, + int64_t height, + double downsample); + }; + + OpenSlideLibrary& that_; + void* handle_; + std::vector<Level> levels_; + + void Initialize(const std::string& path); + + void Close(); + + void CheckLevel(unsigned int level) const; + + public: + Image(OpenSlideLibrary& that, + const std::string& path); + + Image(const std::string& path); + + ~Image() + { + Close(); + } + + unsigned int GetLevelCount() const + { + return levels_.size(); + } + + double GetLevelDownsample(unsigned int level) const; + + unsigned int GetLevelWidth(unsigned int level) const; + + unsigned int GetLevelHeight(unsigned int level) const; + + Orthanc::ImageAccessor* ReadRegion(unsigned int level, + uint64_t x, + uint64_t y, + unsigned int width, + unsigned int height); + }; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/OpenSlidePyramid.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,47 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "OpenSlidePyramid.h" + +#include "../Orthanc/Core/Images/ImageProcessing.h" +#include "../Orthanc/Core/OrthancException.h" +#include "../Orthanc/Core/Logging.h" + +namespace OrthancWSI +{ + void OpenSlidePyramid::ReadRegion(Orthanc::ImageAccessor& target, + unsigned int level, + unsigned int x, + unsigned int y) + { + std::auto_ptr<Orthanc::ImageAccessor> source(image_.ReadRegion(level, x, y, target.GetWidth(), target.GetHeight())); + Orthanc::ImageProcessing::Convert(target, *source); + } + + + OpenSlidePyramid::OpenSlidePyramid(const std::string& path, + unsigned int tileWidth, + unsigned int tileHeight) : + image_(path), + tileWidth_(tileWidth), + tileHeight_(tileHeight) + { + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/OpenSlidePyramid.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,76 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "DecodedTiledPyramid.h" +#include "OpenSlideLibrary.h" + +namespace OrthancWSI +{ + class OpenSlidePyramid : public DecodedTiledPyramid + { + private: + OpenSlideLibrary::Image image_; + unsigned int tileWidth_; + unsigned int tileHeight_; + + protected: + virtual void ReadRegion(Orthanc::ImageAccessor& target, + unsigned int level, + unsigned int x, + unsigned int y); + + public: + OpenSlidePyramid(const std::string& path, + unsigned int tileWidth, + unsigned int tileHeight); + + virtual unsigned int GetTileWidth() const + { + return tileWidth_; + } + + virtual unsigned int GetTileHeight() const + { + return tileHeight_; + } + + virtual unsigned int GetLevelCount() const + { + return image_.GetLevelCount(); + } + + virtual unsigned int GetLevelWidth(unsigned int level) const + { + return image_.GetLevelWidth(level); + } + + virtual unsigned int GetLevelHeight(unsigned int level) const + { + return image_.GetLevelHeight(level); + } + + virtual Orthanc::PixelFormat GetPixelFormat() const + { + return Orthanc::PixelFormat_RGB24; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/PyramidWithRawTiles.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,74 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "PyramidWithRawTiles.h" + +#include "../Orthanc/Core/Images/PngReader.h" +#include "../Orthanc/Core/Images/JpegReader.h" +#include "../Orthanc/Core/OrthancException.h" +#include "../Jpeg2000Reader.h" + +namespace OrthancWSI +{ + Orthanc::ImageAccessor* PyramidWithRawTiles::DecodeTile(unsigned int level, + unsigned int tileX, + unsigned int tileY) + { + std::string tile; + if (!ReadRawTile(tile, level, tileX, tileY)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + std::auto_ptr<Orthanc::ImageAccessor> result; + + switch (GetImageCompression()) + { + case ImageCompression_None: + result.reset(new Orthanc::ImageAccessor); + result->AssignReadOnly(GetPixelFormat(), + GetTileWidth(), + GetTileHeight(), + GetBytesPerPixel(GetPixelFormat()) * GetTileWidth(), + tile.c_str()); + break; + + case ImageCompression_Jpeg: + result.reset(new Orthanc::JpegReader); + dynamic_cast<Orthanc::JpegReader&>(*result).ReadFromMemory(tile); + break; + + case ImageCompression_Png: + result.reset(new Orthanc::PngReader); + dynamic_cast<Orthanc::PngReader&>(*result).ReadFromMemory(tile); + break; + + case ImageCompression_Jpeg2000: + result.reset(new Jpeg2000Reader); + dynamic_cast<Jpeg2000Reader&>(*result).ReadFromMemory(tile); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + return result.release(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/PyramidWithRawTiles.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,34 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "ITiledPyramid.h" + +namespace OrthancWSI +{ + class PyramidWithRawTiles : public ITiledPyramid + { + public: + virtual Orthanc::ImageAccessor* DecodeTile(unsigned int level, + unsigned int tileX, + unsigned int tileY); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/SingleLevelDecodedPyramid.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,58 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "SingleLevelDecodedPyramid.h" +#include "../ImageToolbox.h" + +#include "../Orthanc/Core/OrthancException.h" + +namespace OrthancWSI +{ + void SingleLevelDecodedPyramid::ReadRegion(Orthanc::ImageAccessor& target, + unsigned int level, + unsigned int x, + unsigned int y) + { + Orthanc::ImageAccessor region = image_.GetRegion(x, y, target.GetWidth(), target.GetHeight()); + ImageToolbox::Copy(target, region); + } + + + unsigned int SingleLevelDecodedPyramid::GetLevelWidth(unsigned int level) const + { + if (level != 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + return image_.GetWidth(); + } + + + unsigned int SingleLevelDecodedPyramid::GetLevelHeight(unsigned int level) const + { + if (level != 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + return image_.GetHeight(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/SingleLevelDecodedPyramid.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,77 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "DecodedTiledPyramid.h" + +namespace OrthancWSI +{ + class SingleLevelDecodedPyramid : public DecodedTiledPyramid + { + private: + Orthanc::ImageAccessor image_; + unsigned int tileWidth_; + unsigned int tileHeight_; + + protected: + void SetImage(const Orthanc::ImageAccessor& image) + { + image_ = image; + } + + virtual void ReadRegion(Orthanc::ImageAccessor& target, + unsigned int level, + unsigned int x, + unsigned int y); + + public: + SingleLevelDecodedPyramid(unsigned int tileWidth, + unsigned int tileHeight) : + tileWidth_(tileWidth), + tileHeight_(tileHeight) + { + } + + virtual unsigned int GetTileWidth() const + { + return tileWidth_; + } + + virtual unsigned int GetTileHeight() const + { + return tileHeight_; + } + + virtual unsigned int GetLevelCount() const + { + return 1; + } + + virtual unsigned int GetLevelWidth(unsigned int level) const; + + virtual unsigned int GetLevelHeight(unsigned int level) const; + + virtual Orthanc::PixelFormat GetPixelFormat() const + { + return image_.GetFormat(); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/TiledJpegImage.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,44 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "SingleLevelDecodedPyramid.h" + +#include "../Orthanc/Core/Images/JpegReader.h" + +namespace OrthancWSI +{ + class TiledJpegImage : public SingleLevelDecodedPyramid + { + private: + Orthanc::JpegReader reader_; + + public: + TiledJpegImage(const std::string& path, + unsigned int tileWidth, + unsigned int tileHeight) : + SingleLevelDecodedPyramid(tileWidth, tileHeight) + { + reader_.ReadFromFile(path); + SetImage(reader_); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/TiledPngImage.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,44 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "SingleLevelDecodedPyramid.h" + +#include "../Orthanc/Core/Images/PngReader.h" + +namespace OrthancWSI +{ + class TiledPngImage : public SingleLevelDecodedPyramid + { + private: + Orthanc::PngReader reader_; + + public: + TiledPngImage(const std::string& path, + unsigned int tileWidth, + unsigned int tileHeight) : + SingleLevelDecodedPyramid(tileWidth, tileHeight) + { + reader_.ReadFromFile(path); + SetImage(reader_); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/TiledPyramidStatistics.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,73 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "TiledPyramidStatistics.h" + +#include "../Orthanc/Core/Logging.h" + + +namespace OrthancWSI +{ + TiledPyramidStatistics::TiledPyramidStatistics(ITiledPyramid& source) : + source_(source), + countRawAccesses_(0), + countDecodedTiles_(0) + { + } + + + TiledPyramidStatistics::~TiledPyramidStatistics() + { + LOG(WARNING) << "Closing the input image (" + << countRawAccesses_ << " raw accesses to the tiles, " + << countDecodedTiles_ << " decoded tiles)"; + } + + + bool TiledPyramidStatistics::ReadRawTile(std::string& tile, + unsigned int level, + unsigned int tileX, + unsigned int tileY) + { + if (source_.ReadRawTile(tile, level, tileX, tileY)) + { + boost::mutex::scoped_lock lock(mutex_); + countRawAccesses_++; + return true; + } + else + { + return false; + } + } + + + Orthanc::ImageAccessor* TiledPyramidStatistics::DecodeTile(unsigned int level, + unsigned int tileX, + unsigned int tileY) + { + { + boost::mutex::scoped_lock lock(mutex_); + countDecodedTiles_++; + } + + return source_.DecodeTile(level, tileX, tileY); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/TiledPyramidStatistics.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,86 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "ITiledPyramid.h" + +#include <boost/thread/mutex.hpp> + +namespace OrthancWSI +{ + class TiledPyramidStatistics : public ITiledPyramid + { + private: + boost::mutex mutex_; + ITiledPyramid& source_; + unsigned int countRawAccesses_; + unsigned int countDecodedTiles_; + + public: + TiledPyramidStatistics(ITiledPyramid& source); // Takes ownership + + virtual ~TiledPyramidStatistics(); + + virtual unsigned int GetLevelCount() const + { + return source_.GetLevelCount(); + } + + virtual unsigned int GetLevelWidth(unsigned int level) const + { + return source_.GetLevelWidth(level); + } + + virtual unsigned int GetLevelHeight(unsigned int level) const + { + return source_.GetLevelHeight(level); + } + + virtual unsigned int GetTileWidth() const + { + return source_.GetTileWidth(); + } + + virtual unsigned int GetTileHeight() const + { + return source_.GetTileHeight(); + } + + virtual ImageCompression GetImageCompression() const + { + return source_.GetImageCompression(); + } + + virtual Orthanc::PixelFormat GetPixelFormat() const + { + return source_.GetPixelFormat(); + } + + virtual bool ReadRawTile(std::string& tile, + unsigned int level, + unsigned int tileX, + unsigned int tileY); + + virtual Orthanc::ImageAccessor* DecodeTile(unsigned int level, + unsigned int tileX, + unsigned int tileY); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Jpeg2000Reader.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,490 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "Jpeg2000Reader.h" + +#include "Orthanc/Core/OrthancException.h" +#include "Orthanc/Core/Toolbox.h" +#include "ImageToolbox.h" + +#include <cassert> +#include <string.h> +#include <openjpeg.h> + +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 +# define OPJ_CLRSPC_GRAY CLRSPC_GRAY +# define OPJ_CLRSPC_SRGB CLRSPC_SRGB +# define OPJ_CODEC_J2K CODEC_J2K +# define OPJ_CODEC_JP2 CODEC_JP2 +typedef opj_dinfo_t opj_codec_t; +typedef opj_cio_t opj_stream_t; +#elif ORTHANC_OPENJPEG_MAJOR_VERSION == 2 +#else +#error Unsupported version of OpenJpeg +#endif + + +namespace OrthancWSI +{ + namespace + { + // Check out opj_dparameters_t::decod_format + enum InputFormat + { + InputFormat_J2K = 0, + InputFormat_JP2 = 1, + InputFormat_JPT = 2 + }; + + enum OutputFormat + { +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 + OutputFormat_PGX = 1 +#else + OutputFormat_PGX = 11 +#endif + }; + + class OpenJpegDecoder : public boost::noncopyable + { + private: + opj_dparameters_t parameters_; + opj_codec_t* dinfo_; + + void SetupParameters(InputFormat format) + { + opj_set_default_decoder_parameters(¶meters_); + + parameters_.decod_format = format; + parameters_.cod_format = OutputFormat_PGX; + +#if OPENJPEG_MAJOR_VERSION == 1 + parameters_.cp_layer = 0; + parameters_.cp_reduce = 0; +#endif + } + + void Finalize() + { + if (dinfo_ != NULL) + { +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 + opj_destroy_decompress(dinfo_); +#else + opj_destroy_codec(dinfo_); +#endif + dinfo_ = NULL; + } + } + + public: + OpenJpegDecoder(Jpeg2000Format format) : dinfo_(NULL) + { + switch (format) + { + case Jpeg2000Format_J2K: + SetupParameters(InputFormat_J2K); + dinfo_ = opj_create_decompress(OPJ_CODEC_J2K); + break; + + case Jpeg2000Format_JP2: + SetupParameters(InputFormat_JP2); + dinfo_ = opj_create_decompress(OPJ_CODEC_JP2); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (!dinfo_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 + opj_setup_decoder(dinfo_, ¶meters_); +#else + if (!opj_setup_decoder(dinfo_, ¶meters_)) + { + Finalize(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } +#endif + } + + ~OpenJpegDecoder() + { + Finalize(); + } + + opj_codec_t* GetObject() + { + return dinfo_; + } + + const opj_dparameters_t& GetParameters() const + { + return parameters_; + } + }; + + + class OpenJpegInput : public boost::noncopyable + { + private: + opj_stream_t* cio_; + + const uint8_t* buffer_; + size_t size_; + size_t position_; + +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 2 + static void Free(void *userData) + { + } + + static OPJ_SIZE_T Read(void *target, + OPJ_SIZE_T size, + void *userData) + { + OpenJpegInput& that = *reinterpret_cast<OpenJpegInput*>(userData); + assert(that.position_ >= 0 && that.position_ <= that.size_); + assert(size >= 0); + + if (that.position_ == that.size_) + { + // End of file + return -1; + } + else + { + if (that.position_ + size > that.size_) + { + size = that.size_ - that.position_; + } + + if (size > 0) + { + memcpy(target, that.buffer_ + that.position_, size); + } + + that.position_ += size; + return size; + } + } + + static OPJ_OFF_T Skip(OPJ_OFF_T skip, + void *userData) + { + assert(skip >= 0); + OpenJpegInput& that = *reinterpret_cast<OpenJpegInput*>(userData); + + if (that.position_ == that.size_) + { + // End of file + return -1; + } + else if (that.position_ + skip > that.size_) + { + size_t offset = that.size_ - that.position_; + that.position_ = that.size_; + return offset; + } + else + { + that.position_ += skip; + return skip; + } + } + + static OPJ_BOOL Seek(OPJ_OFF_T position, + void *userData) + { + assert(position >= 0); + OpenJpegInput& that = *reinterpret_cast<OpenJpegInput*>(userData); + + if (static_cast<size_t>(position) > that.size_) + { + that.position_ = that.size_; + return false; + } + else + { + that.position_ = position; + return true; + } + } +#endif + + public: + OpenJpegInput(OpenJpegDecoder& decoder, + const void* buffer, + size_t size) : + buffer_(reinterpret_cast<const uint8_t*>(buffer)), + size_(size), + position_(0) + { +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 + cio_ = opj_cio_open(reinterpret_cast<opj_common_ptr>(decoder.GetObject()), + reinterpret_cast<unsigned char*>(const_cast<void*>(buffer)), + size); +#else + cio_ = opj_stream_create(size_, 1 /* input stream */); + if (!cio_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + // http://openjpeg.narkive.com/zHqG2fMe/opj-stream-set-user-data-length + // "I'd suggest to precise in the documentation that the skip + // and read callback functions should return -1 on end of + // stream, and the seek callback function should return false + // on end of stream." + + opj_stream_set_user_data(cio_, this, Free); + opj_stream_set_user_data_length(cio_, size); + opj_stream_set_read_function(cio_, Read); + opj_stream_set_skip_function(cio_, Skip); + opj_stream_set_seek_function(cio_, Seek); +#endif + } + + ~OpenJpegInput() + { + if (cio_) + { + #if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 + opj_cio_close(cio_); +#else + opj_stream_destroy(cio_); +#endif + cio_ = NULL; + } + } + + opj_stream_t* GetObject() + { + return cio_; + } + }; + + + class OpenJpegImage + { + private: + opj_image_t* image_; + + void CopyChannel(Orthanc::ImageAccessor& target, + unsigned int channel, + unsigned int targetIncrement) + { + int32_t* q = image_->comps[channel].data; + assert(q != NULL); + + for (unsigned int y = 0; y < target.GetHeight(); y++) + { + uint8_t *p = reinterpret_cast<uint8_t*>(target.GetRow(y)) + channel; + + for (unsigned int x = 0; x < target.GetWidth(); x++, p += targetIncrement) + { + *p = *q; + q++; + } + } + } + + public: + OpenJpegImage(OpenJpegDecoder& decoder, + OpenJpegInput& input) : + image_(NULL) + { +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 + image_ = opj_decode(decoder.GetObject(), input.GetObject()); + if (image_ == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } +#else + if (!opj_read_header(input.GetObject(), decoder.GetObject(), &image_) || + image_ == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (!opj_set_decode_area(decoder.GetObject(), image_, + static_cast<int32_t>(decoder.GetParameters().DA_x0), + static_cast<int32_t>(decoder.GetParameters().DA_y0), + static_cast<int32_t>(decoder.GetParameters().DA_x1), + static_cast<int32_t>(decoder.GetParameters().DA_y1)) || // Decode the whole image + !opj_decode(decoder.GetObject(), input.GetObject(), image_) || + !opj_end_decompress(decoder.GetObject(), input.GetObject())) + { + opj_image_destroy(image_); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } +#endif + } + + ~OpenJpegImage() + { + if (image_ != NULL) + { + opj_image_destroy(image_); + image_ = NULL; + } + } + + Orthanc::ImageAccessor* ProvideImage() + { + if (image_->x1 < 0 || + image_->y1 < 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (image_->x0 != 0 || + image_->y0 != 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + for (unsigned int c = 0; c < static_cast<unsigned int>(image_->numcomps); c++) + { + if (image_->comps[c].dx != 1 || + image_->comps[c].dy != 1 || + image_->comps[c].x0 != 0 || + image_->comps[c].y0 != 0 || + image_->comps[c].w != image_->x1 || + image_->comps[c].h != image_->y1 || + image_->comps[c].prec != 8 || + image_->comps[c].sgnd != 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + unsigned int width = static_cast<unsigned int>(image_->x1); + unsigned int height = static_cast<unsigned int>(image_->y1); + + Orthanc::PixelFormat format; + if (image_->numcomps == 1 && image_->color_space != OPJ_CLRSPC_GRAY) + { + format = Orthanc::PixelFormat_Grayscale8; + } + else if (image_->numcomps == 3 && image_->color_space != OPJ_CLRSPC_SRGB) + { + format = Orthanc::PixelFormat_RGB24; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + std::auto_ptr<Orthanc::ImageAccessor> image(ImageToolbox::Allocate(format, width, height)); + + switch (format) + { + case Orthanc::PixelFormat_Grayscale8: + { + CopyChannel(*image, 0, 1); + break; + } + + case Orthanc::PixelFormat_RGB24: + { + CopyChannel(*image, 0, 3); + CopyChannel(*image, 1, 3); + CopyChannel(*image, 2, 3); + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + return image.release(); + } + + }; + } + + + void Jpeg2000Reader::ReadFromMemory(const void* buffer, + size_t size) + { + OpenJpegDecoder decoder(DetectFormatFromMemory(buffer, size)); + OpenJpegInput input(decoder, buffer, size); + OpenJpegImage image(decoder, input); + + image_.reset(image.ProvideImage()); + AssignReadOnly(image_->GetFormat(), + image_->GetWidth(), + image_->GetHeight(), + image_->GetPitch(), + image_->GetConstBuffer()); + } + + + void Jpeg2000Reader::ReadFromMemory(const std::string& buffer) + { + if (buffer.empty()) + { + ReadFromMemory(NULL, 0); + } + else + { + ReadFromMemory(buffer.c_str(), buffer.size()); + } + } + + void Jpeg2000Reader::ReadFromFile(const std::string& filename) + { + // TODO Use opj_stream_create_file_stream() ? + + std::string content; + Orthanc::Toolbox::ReadFile(content, filename); + } + + + Jpeg2000Format Jpeg2000Reader::DetectFormatFromMemory(const void* buffer, + size_t size) + { + static const char JP2_RFC3745_HEADER[] = "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a"; + static const char JP2_HEADER[] = "\x0d\x0a\x87\x0a"; + static const char J2K_HEADER[] = "\xff\x4f\xff\x51"; + + if (size < sizeof(JP2_RFC3745_HEADER) - 1) + { + return Jpeg2000Format_Unknown; + } + + if (memcmp(buffer, JP2_RFC3745_HEADER, sizeof(JP2_RFC3745_HEADER) - 1) == 0 || + memcmp(buffer, JP2_HEADER, sizeof(JP2_HEADER) - 1) == 0) + { + return Jpeg2000Format_JP2; + } + else if (memcmp(buffer, J2K_HEADER, sizeof(J2K_HEADER) - 1) == 0) + { + return Jpeg2000Format_J2K; + } + + return Jpeg2000Format_Unknown; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Jpeg2000Reader.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,53 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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/Images/Image.h" +#include <memory> + +namespace OrthancWSI +{ + enum Jpeg2000Format + { + Jpeg2000Format_J2K, + Jpeg2000Format_JP2, + Jpeg2000Format_Unknown + }; + + class Jpeg2000Reader : + public Orthanc::ImageAccessor, + public boost::noncopyable + { + private: + std::auto_ptr<Orthanc::ImageAccessor> image_; + + public: + void ReadFromFile(const std::string& filename); + + void ReadFromMemory(const void* buffer, + size_t size); + + void ReadFromMemory(const std::string& buffer); + + static Jpeg2000Format DetectFormatFromMemory(const void* buffer, + size_t size); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Jpeg2000Writer.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,364 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "Jpeg2000Writer.h" + +#include "Orthanc/Core/ChunkedBuffer.h" +#include "Orthanc/Core/OrthancException.h" + +#include <openjpeg.h> +#include <string.h> +#include <vector> + +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 +# define OPJ_CLRSPC_GRAY CLRSPC_GRAY +# define OPJ_CLRSPC_SRGB CLRSPC_SRGB +# define OPJ_CODEC_J2K CODEC_J2K +typedef opj_cinfo_t opj_codec_t; +typedef opj_cio_t opj_stream_t; +#elif ORTHANC_OPENJPEG_MAJOR_VERSION == 2 +#else +#error Unsupported version of OpenJpeg +#endif + +namespace OrthancWSI +{ + namespace + { + class OpenJpegImage : public boost::noncopyable + { + private: + std::vector<opj_image_cmptparm_t> components_; + COLOR_SPACE colorspace_; + opj_image_t* image_; + + void SetupComponents(unsigned int width, + unsigned int height, + Orthanc::PixelFormat format) + { + switch (format) + { + case Orthanc::PixelFormat_Grayscale8: + colorspace_ = OPJ_CLRSPC_GRAY; + components_.resize(1); + break; + + case Orthanc::PixelFormat_RGB24: + colorspace_ = OPJ_CLRSPC_SRGB; + components_.resize(3); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + for (size_t i = 0; i < components_.size(); i++) + { + memset(&components_[i], 0, sizeof(opj_image_cmptparm_t)); + components_[i].dx = 1; + components_[i].dy = 1; + components_[i].x0 = 0; + components_[i].y0 = 0; + components_[i].w = width; + components_[i].h = height; + components_[i].prec = 8; + components_[i].bpp = 8; + components_[i].sgnd = 0; + } + } + + void CopyRGB24(unsigned int width, + unsigned int height, + unsigned int pitch, + const void* buffer) + { + int32_t* r = image_->comps[0].data; + int32_t* g = image_->comps[1].data; + int32_t* b = image_->comps[2].data; + + for (unsigned int y = 0; y < height; y++) + { + const uint8_t *p = reinterpret_cast<const uint8_t*>(buffer) + y * pitch; + + for (unsigned int x = 0; x < width; x++) + { + *r = p[0]; + *g = p[1]; + *b = p[2]; + p += 3; + r++; + g++; + b++; + } + } + } + + void CopyGrayscale8(unsigned int width, + unsigned int height, + unsigned int pitch, + const void* buffer) + { + int32_t* q = image_->comps[0].data; + + for (unsigned int y = 0; y < height; y++) + { + const uint8_t *p = reinterpret_cast<const uint8_t*>(buffer) + y * pitch; + + for (unsigned int x = 0; x < width; x++) + { + *q = *p; + p++; + q++; + } + } + } + + public: + OpenJpegImage(unsigned int width, + unsigned int height, + unsigned int pitch, + Orthanc::PixelFormat format, + const void* buffer) : image_(NULL) + { + SetupComponents(width, height, format); + + image_ = opj_image_create(components_.size(), &components_[0], colorspace_); + if (!image_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + image_->x0 = 0; + image_->y0 = 0; + image_->x1 = width; + image_->y1 = height; + + switch (format) + { + case Orthanc::PixelFormat_Grayscale8: + CopyGrayscale8(width, height, pitch, buffer); + break; + + case Orthanc::PixelFormat_RGB24: + CopyRGB24(width, height, pitch, buffer); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + ~OpenJpegImage() + { + if (image_) + { + opj_image_destroy(image_); + image_ = NULL; + } + } + + + opj_image_t* GetObject() + { + return image_; + } + }; + + + class OpenJpegEncoder : public boost::noncopyable + { + private: + opj_codec_t* cinfo_; + + public: + OpenJpegEncoder(opj_cparameters_t& parameters, + OpenJpegImage& image) : cinfo_(NULL) + { + cinfo_ = opj_create_compress(OPJ_CODEC_J2K); + if (!cinfo_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + opj_setup_encoder(cinfo_, ¶meters, image.GetObject()); + } + + ~OpenJpegEncoder() + { + if (cinfo_ != NULL) + { +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 + opj_destroy_compress(cinfo_); +#else + opj_destroy_codec(cinfo_); +#endif + cinfo_ = NULL; + } + } + + opj_codec_t* GetObject() + { + return cinfo_; + } + }; + + + class OpenJpegOutput : public boost::noncopyable + { + private: + opj_stream_t* cio_; + +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 2 + Orthanc::ChunkedBuffer buffer_; + + static void Free(void *userData) + { + } + + static OPJ_SIZE_T Write(void *buffer, + OPJ_SIZE_T size, + void *userData) + { + OpenJpegOutput* that = reinterpret_cast<OpenJpegOutput*>(userData); + that->buffer_.AddChunk(reinterpret_cast<const char*>(buffer), size); + return size; + } +#endif + + public: + OpenJpegOutput(OpenJpegEncoder& encoder) : cio_(NULL) + { +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 + cio_ = opj_cio_open(reinterpret_cast<opj_common_ptr>(encoder.GetObject()), NULL, 0); +#else + cio_ = opj_stream_default_create(0 /* output stream */); + if (!cio_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + opj_stream_set_user_data(cio_, this, Free); + opj_stream_set_write_function(cio_, Write); +#endif + } + + ~OpenJpegOutput() + { + if (cio_) + { +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 + opj_cio_close(cio_); +#else + opj_stream_destroy(cio_); +#endif + cio_ = NULL; + } + } + + opj_stream_t* GetObject() + { + return cio_; + } + + void Flatten(std::string& target) + { +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 + target.assign(reinterpret_cast<const char*>(cio_->buffer), cio_tell(cio_)); +#else + buffer_.Flatten(target); +#endif + } + }; + } + + + static void SetupParameters(opj_cparameters_t& parameters, + Orthanc::PixelFormat format, + bool isLossless) + { + opj_set_default_encoder_parameters(¶meters); + parameters.cp_disto_alloc = 1; + + if (isLossless) + { + parameters.tcp_numlayers = 1; + parameters.tcp_rates[0] = 0; + } + else + { + parameters.tcp_numlayers = 5; + parameters.tcp_rates[0] = 1920; + parameters.tcp_rates[1] = 480; + parameters.tcp_rates[2] = 120; + parameters.tcp_rates[3] = 30; + parameters.tcp_rates[4] = 10; + parameters.irreversible = 1; + + if (format == Orthanc::PixelFormat_RGB24 || + format == Orthanc::PixelFormat_RGBA32) + { + // This must be set to 1 if the number of color channels is >= 3 + parameters.tcp_mct = 1; + } + } + + parameters.cp_comment = const_cast<char*>(""); + } + + + void Jpeg2000Writer::WriteToMemoryInternal(std::string& compressed, + unsigned int width, + unsigned int height, + unsigned int pitch, + Orthanc::PixelFormat format, + const void* buffer) + { + if (format != Orthanc::PixelFormat_Grayscale8 && + format != Orthanc::PixelFormat_RGB24 && + format != Orthanc::PixelFormat_RGBA32) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + opj_cparameters_t parameters; + SetupParameters(parameters, format, isLossless_); + + OpenJpegImage image(width, height, pitch, format, buffer); + OpenJpegEncoder encoder(parameters, image); + OpenJpegOutput output(encoder); + +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 + if (!opj_encode(encoder.GetObject(), output.GetObject(), image.GetObject(), parameters.index)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } +#else + if (!opj_start_compress(encoder.GetObject(), image.GetObject(), output.GetObject()) || + !opj_encode(encoder.GetObject(), output.GetObject()) || + !opj_end_compress(encoder.GetObject(), output.GetObject())) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } +#endif + + output.Flatten(compressed); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Jpeg2000Writer.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,55 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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/Images/IImageWriter.h" + +namespace OrthancWSI +{ + class Jpeg2000Writer : public Orthanc::IImageWriter + { + protected: + virtual void WriteToMemoryInternal(std::string& compressed, + unsigned int width, + unsigned int height, + unsigned int pitch, + Orthanc::PixelFormat format, + const void* buffer); + + private: + bool isLossless_; + + public: + Jpeg2000Writer() : isLossless_(true) + { + } + + void SetLossless(bool isLossless) + { + isLossless_ = isLossless; + } + + bool IsLossless() const + { + return isLossless_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messaging/CurlOrthancConnection.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,58 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "CurlOrthancConnection.h" + +#include "../Orthanc/Core/OrthancException.h" + +namespace OrthancWSI +{ + void CurlOrthancConnection::ApplyGet(std::string& result, + const std::string& uri) + { + Orthanc::HttpClient client(parameters_, uri); + + // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc) + client.SetRedirectionFollowed(false); + + if (!client.Apply(result)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + } + + void CurlOrthancConnection::ApplyPost(std::string& result, + const std::string& uri, + const std::string& body) + { + Orthanc::HttpClient client(parameters_, uri); + + client.SetMethod(Orthanc::HttpMethod_Post); + client.SetBody(body); + + // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc) + client.SetRedirectionFollowed(false); + + if (!client.Apply(result)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messaging/CurlOrthancConnection.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,53 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "OrthancConnectionBase.h" + +#include "../Orthanc/Core/HttpClient.h" + +namespace OrthancWSI +{ + class CurlOrthancConnection : public OrthancConnectionBase + { + private: + Orthanc::WebServiceParameters parameters_; + + protected: + virtual void ApplyGet(std::string& result, + const std::string& uri); + + virtual void ApplyPost(std::string& result, + const std::string& uri, + const std::string& body); + + public: + CurlOrthancConnection(const Orthanc::WebServiceParameters& parameters) : + parameters_(parameters) + { + } + + const Orthanc::WebServiceParameters& GetParameters() const + { + return parameters_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messaging/FolderTarget.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,44 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "FolderTarget.h" + +#include "../Orthanc/Core/Toolbox.h" +#include "../Orthanc/Core/Logging.h" + +#include <stdio.h> + +namespace OrthancWSI +{ + void FolderTarget::Write(const std::string& file) + { + std::string path; + path.resize(pattern_.size() + 16); + + { + boost::mutex::scoped_lock lock(mutex_); + sprintf(&path[0], pattern_.c_str(), count_); + count_ += 1; + } + + LOG(INFO) << "Writing file " << path; + Orthanc::Toolbox::WriteFile(file, path); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messaging/FolderTarget.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,45 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "IFileTarget.h" + +#include <boost/thread.hpp> + +namespace OrthancWSI +{ + class FolderTarget : public IFileTarget + { + private: + boost::mutex mutex_; + unsigned int count_; + std::string pattern_; + + public: + FolderTarget(const std::string& pattern) : + count_(0), + pattern_(pattern) + { + } + + virtual void Write(const std::string& file); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messaging/IFileTarget.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,37 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 <boost/noncopyable.hpp> +#include <string> + +namespace OrthancWSI +{ + class IFileTarget : public boost::noncopyable + { + public: + virtual ~IFileTarget() + { + } + + virtual void Write(const std::string& file) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messaging/IOrthancConnection.cpp Sat Oct 22 21:48:33 2016 +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 + * + * 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 "IOrthancConnection.h" + +#include "../Orthanc/Core/Logging.h" +#include "../Orthanc/Core/OrthancException.h" + +#include <json/reader.h> + + +namespace OrthancWSI +{ + void IOrthancConnection::RestApiGet(Json::Value& result, + IOrthancConnection& orthanc, + const std::string& uri) + { + std::string content; + orthanc.RestApiGet(content, uri); + + Json::Reader reader; + if (!reader.parse(content, result)) + { + LOG(ERROR) << "Cannot parse a JSON file"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + + void IOrthancConnection::RestApiPost(Json::Value& result, + IOrthancConnection& orthanc, + const std::string& uri, + const std::string& body) + { + std::string content; + orthanc.RestApiPost(content, uri, body); + + Json::Reader reader; + if (!reader.parse(content, result)) + { + LOG(ERROR) << "Cannot parse a JSON file"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messaging/IOrthancConnection.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,52 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 <boost/noncopyable.hpp> +#include <json/value.h> + +namespace OrthancWSI +{ + // Derived classes must be thread-safe + class IOrthancConnection : public boost::noncopyable + { + public: + virtual ~IOrthancConnection() + { + } + + virtual void RestApiGet(std::string& result, + const std::string& uri) = 0; + + virtual void RestApiPost(std::string& result, + const std::string& uri, + const std::string& body) = 0; + + static void RestApiGet(Json::Value& result, + IOrthancConnection& orthanc, + const std::string& uri); + + static void RestApiPost(Json::Value& result, + IOrthancConnection& orthanc, + const std::string& uri, + const std::string& body); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messaging/OrthancConnectionBase.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,40 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "OrthancConnectionBase.h" + +namespace OrthancWSI +{ + void OrthancConnectionBase::RestApiGet(std::string& result, + const std::string& uri) + { + boost::mutex::scoped_lock lock_; + ApplyGet(result, uri); + } + + + void OrthancConnectionBase::RestApiPost(std::string& result, + const std::string& uri, + const std::string& body) + { + boost::mutex::scoped_lock lock_; + ApplyPost(result, uri, body); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messaging/OrthancConnectionBase.h Sat Oct 22 21:48:33 2016 +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 + * + * 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 "IOrthancConnection.h" + +#include <boost/thread/mutex.hpp> + +namespace OrthancWSI +{ + class OrthancConnectionBase : public IOrthancConnection + { + private: + boost::mutex mutex_; + + protected: + // Will be invoked in mutual exclusion + virtual void ApplyGet(std::string& result, + const std::string& uri) = 0; + + virtual void ApplyPost(std::string& result, + const std::string& uri, + const std::string& body) = 0; + + public: + virtual void RestApiGet(std::string& result, + const std::string& uri); + + virtual void RestApiPost(std::string& result, + const std::string& uri, + const std::string& body); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messaging/OrthancTarget.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,57 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "OrthancTarget.h" + +#include "CurlOrthancConnection.h" +#include "../DicomToolbox.h" +#include "../Orthanc/Core/OrthancException.h" +#include "../Orthanc/Core/Logging.h" + +namespace OrthancWSI +{ + OrthancTarget::OrthancTarget(const Orthanc::WebServiceParameters& parameters) : + orthanc_(new CurlOrthancConnection(parameters)), + first_(true) + { + } + + + void OrthancTarget::Write(const std::string& file) + { + Json::Value result; + IOrthancConnection::RestApiPost(result, *orthanc_, "/instances", file); + + std::string instanceId = DicomToolbox::GetMandatoryStringTag(result, "ID"); + + if (first_) + { + Json::Value instance; + IOrthancConnection::RestApiGet(instance, *orthanc_, "/instances/" + instanceId); + + std::string seriesId = DicomToolbox::GetMandatoryStringTag(instance, "ParentSeries"); + + LOG(WARNING) << "ID of the whole-slide image series in Orthanc: " << seriesId; + first_ = false; + } + + LOG(INFO) << "New instance was added to Orthanc: " << instanceId; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messaging/OrthancTarget.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,48 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "IFileTarget.h" +#include "IOrthancConnection.h" +#include "../Orthanc/Core/WebServiceParameters.h" + +#include <memory> + +namespace OrthancWSI +{ + class OrthancTarget : public IFileTarget + { + private: + std::auto_ptr<IOrthancConnection> orthanc_; + bool first_; + + public: + OrthancTarget(const Orthanc::WebServiceParameters& parameters); + + OrthancTarget(IOrthancConnection* orthanc) : // Takes ownership + orthanc_(orthanc), + first_(true) + { + } + + virtual void Write(const std::string& file); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messaging/PluginOrthancConnection.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,132 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "PluginOrthancConnection.h" + +#include "../Orthanc/Core/OrthancException.h" + +namespace OrthancWSI +{ + class PluginOrthancConnection::MemoryBuffer : public boost::noncopyable + { + private: + OrthancPluginContext* context_; + OrthancPluginMemoryBuffer buffer_; + + void Clear() + { + if (buffer_.data != NULL) + { + OrthancPluginFreeMemoryBuffer(context_, &buffer_); + buffer_.data = NULL; + buffer_.size = 0; + } + } + + + public: + MemoryBuffer(OrthancPluginContext* context) : + context_(context) + { + buffer_.data = NULL; + buffer_.size = 0; + } + + ~MemoryBuffer() + { + Clear(); + } + + void RestApiGet(const std::string& uri) + { + Clear(); + + OrthancPluginErrorCode error = OrthancPluginRestApiGet(context_, &buffer_, uri.c_str()); + + if (error == OrthancPluginErrorCode_Success) + { + // OK, success + } + else if (error == OrthancPluginErrorCode_UnknownResource || + error == OrthancPluginErrorCode_InexistentItem) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin); + } + } + + void RestApiPost(const std::string& uri, + const std::string& body) + { + Clear(); + + OrthancPluginErrorCode error = OrthancPluginRestApiPost(context_, &buffer_, uri.c_str(), body.c_str(), body.size()); + + if (error == OrthancPluginErrorCode_Success) + { + // OK, success + } + else if (error == OrthancPluginErrorCode_UnknownResource || + error == OrthancPluginErrorCode_InexistentItem) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin); + } + } + + void ToString(std::string& target) const + { + if (buffer_.size == 0) + { + target.clear(); + } + else + { + target.assign(reinterpret_cast<const char*>(buffer_.data), buffer_.size); + } + } + }; + + + + void PluginOrthancConnection::ApplyGet(std::string& result, + const std::string& uri) + { + MemoryBuffer buffer(context_); + buffer.RestApiGet(uri); + buffer.ToString(result); + } + + + void PluginOrthancConnection::ApplyPost(std::string& result, + const std::string& uri, + const std::string& body) + { + MemoryBuffer buffer(context_); + buffer.RestApiPost(uri, body); + buffer.ToString(result); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messaging/PluginOrthancConnection.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,50 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "OrthancConnectionBase.h" + +#include <orthanc/OrthancCPlugin.h> + +namespace OrthancWSI +{ + class PluginOrthancConnection : public OrthancConnectionBase + { + private: + class MemoryBuffer; + + OrthancPluginContext* context_; + + protected: + virtual void ApplyGet(std::string& result, + const std::string& uri); + + virtual void ApplyPost(std::string& result, + const std::string& uri, + const std::string& body); + + public: + PluginOrthancConnection(OrthancPluginContext* context) : + context_(context) + { + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Orthanc/README.txt Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,3 @@ +This folder contains an excerpt of the source code of Orthanc. It is +automatically generated using the "../../Resources/SyncOrthancFolder.py" +script.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Outputs/DicomPyramidWriter.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,198 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "DicomPyramidWriter.h" + +#include "../DicomToolbox.h" + +#include "../Orthanc/Core/Logging.h" +#include "../Orthanc/Core/OrthancException.h" +#include "../Orthanc/OrthancServer/FromDcmtkBridge.h" + +#include <dcmtk/dcmdata/dcdeftag.h> +#include <boost/lexical_cast.hpp> + +namespace OrthancWSI +{ + void DicomPyramidWriter::FlushInternal(MultiframeDicomWriter& writer, + bool force) + { + if (writer.GetFramesCount() > 0 && + writer.GetSize() > 0 && + (force || (maxSize_ != 0 && writer.GetSize() >= maxSize_))) + { + countInstances_ += 1; + + std::string dicom; + writer.Flush(dicom, countInstances_); + target_.Write(dicom); + } + } + + + DcmItem* DicomPyramidWriter::CreateFunctionalGroup(unsigned int frame, + unsigned int x, + unsigned int y, + unsigned int totalWidth, + unsigned int totalHeight, + float physicalZ) const + { + float physicalX, physicalY; + volume_.GetLocation(physicalX, physicalY, x, y, totalWidth, totalHeight); + + std::string tmpX = boost::lexical_cast<std::string>(physicalX); + std::string tmpY = boost::lexical_cast<std::string>(physicalY); + std::string tmpZ = boost::lexical_cast<std::string>(physicalZ); + + std::auto_ptr<DcmItem> dimension(new DcmItem); + if (!dimension->putAndInsertUint32(DCM_DimensionIndexValues, frame).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + // From Supp 145: The column position of the top left pixel of the + // Total Pixel Matrix is 1. The row position of the top left pixel + // of the Total Pixel Matrix is 1. + std::auto_ptr<DcmItem> position(new DcmItem); + if (!position->putAndInsertSint32(DCM_ColumnPositionInTotalImagePixelMatrix, x + 1).good() || + !position->putAndInsertSint32(DCM_RowPositionInTotalImagePixelMatrix, y + 1).good() || + !position->putAndInsertString(DCM_XOffsetInSlideCoordinateSystem, tmpX.c_str()).good() || + !position->putAndInsertString(DCM_YOffsetInSlideCoordinateSystem, tmpY.c_str()).good() || + !position->putAndInsertString(DCM_ZOffsetInSlideCoordinateSystem, tmpZ.c_str()).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + + std::auto_ptr<DcmSequenceOfItems> sequencePosition(new DcmSequenceOfItems(DCM_PlanePositionSlideSequence)); + if (!sequencePosition->insert(position.release(), false, false).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + std::auto_ptr<DcmSequenceOfItems> sequenceDimension(new DcmSequenceOfItems(DCM_FrameContentSequence)); + if (!sequenceDimension->insert(dimension.release(), false, false).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + std::auto_ptr<DcmItem> item(new DcmItem); + if (!item->insert(sequencePosition.release(), false, false).good() || + !item->insert(sequenceDimension.release(), false, false).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + return item.release(); + } + + + void DicomPyramidWriter::WriteRawTileInternal(const std::string& tile, + const Level& level, + unsigned int x, + unsigned int y) + { + if (x >= level.countTilesX_ || + y >= level.countTilesY_) + { + LOG(ERROR) << "Tile index out of range: " << x << "," << y + << " (max: " << level.countTilesX_ << "," << level.countTilesY_ << ")"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + const unsigned int z = level.z_; + + { + boost::mutex::scoped_lock lock(mutex_); + + if (z >= writers_.size()) + { + writers_.resize(z + 1); + } + + MultiframeDicomWriter* writer = writers_[z]; + + if (writer == NULL) + { + writer = new MultiframeDicomWriter + (dataset_, GetImageCompression(), GetPixelFormat(), level.width_, level.height_, + GetTileWidth(), GetTileHeight()); + writers_[z] = writer; + } + + std::auto_ptr<DcmItem> functionalGroup(CreateFunctionalGroup(writer->GetFramesCount() + 1, + x * GetTileWidth(), + y * GetTileHeight(), + writer->GetTotalWidth(), + writer->GetTotalHeight(), + 0.0f /* TODO Z-plane */)); + + writer->AddFrame(tile, functionalGroup.release()); + FlushInternal(*writer, false); + + countTiles_ ++; + } + } + + + DicomPyramidWriter::DicomPyramidWriter(IFileTarget& target, + const DcmDataset& dataset, + Orthanc::PixelFormat pixelFormat, + ImageCompression compression, + unsigned int tileWidth, + unsigned int tileHeight, + size_t maxSize, // If "0", no automatic flushing + const ImagedVolumeParameters& volume) : + PyramidWriterBase(pixelFormat, compression, tileWidth, tileHeight), + target_(target), + dataset_(dataset), + maxSize_(maxSize), + countTiles_(0), + countInstances_(0), + volume_(volume) + { + } + + + DicomPyramidWriter::~DicomPyramidWriter() + { + LOG(WARNING) << "Closing the DICOM pyramid (" << countTiles_ << " tiles were written)"; + + for (size_t i = 0; i < writers_.size(); i++) + { + if (writers_[i] != NULL) + { + FlushInternal(*writers_[i], true); + delete writers_[i]; + } + } + } + + + void DicomPyramidWriter::Flush() + { + boost::mutex::scoped_lock lock(mutex_); + + for (size_t i = 0; i < writers_.size(); i++) + { + FlushInternal(*writers_[i], true); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Outputs/DicomPyramidWriter.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,78 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "PyramidWriterBase.h" +#include "MultiframeDicomWriter.h" +#include "../Messaging/IFileTarget.h" +#include "../ImagedVolumeParameters.h" + +namespace OrthancWSI +{ + class DicomPyramidWriter : public PyramidWriterBase + { + private: + std::vector<MultiframeDicomWriter*> writers_; + + boost::mutex mutex_; // Protects the access to "writers_" + IFileTarget& target_; + const DcmDataset& dataset_; + size_t maxSize_; + size_t countTiles_; + unsigned int countInstances_; + + const ImagedVolumeParameters& volume_; + + void FlushInternal(MultiframeDicomWriter& writer, + bool force); + + DcmItem* CreateFunctionalGroup(unsigned int frame, + unsigned int x, + unsigned int y, + unsigned int totalWidth, + unsigned int totalHeight, + float physicalZ) const; + + protected: + virtual void WriteRawTileInternal(const std::string& tile, + const Level& level, + unsigned int x, + unsigned int y); + + virtual void AddLevelInternal(const Level& level) + { + } + + public: + DicomPyramidWriter(IFileTarget& target, + const DcmDataset& dataset, + Orthanc::PixelFormat pixelFormat, + ImageCompression compression, + unsigned int tileWidth, + unsigned int tileHeight, + size_t maxSize, // If "0", no automatic flushing + const ImagedVolumeParameters& volume); + + virtual ~DicomPyramidWriter(); + + virtual void Flush(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Outputs/HierarchicalTiffWriter.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,421 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "HierarchicalTiffWriter.h" + +#include "../Orthanc/Core/Logging.h" +#include "../Orthanc/Core/OrthancException.h" +#include "../Orthanc/Core/Uuid.h" + +namespace OrthancWSI +{ + class HierarchicalTiffWriter::PendingTile + { + private: + HierarchicalTiffWriter& that_; + unsigned int level_; + unsigned int tileX_; + unsigned int tileY_; + Orthanc::Toolbox::TemporaryFile file_; + + public: + PendingTile(HierarchicalTiffWriter& that, + unsigned int level, + unsigned int tileX, + unsigned int tileY, + const std::string& tile) : + that_(that), + level_(level), + tileX_(tileX), + tileY_(tileY) + { + file_.Write(tile); + } + + unsigned int GetLevel() const + { + return level_; + } + + unsigned int GetTileX() const + { + return tileX_; + } + + unsigned int GetTileY() const + { + return tileY_; + } + + void Store(TIFF* tiff) + { + std::string tile; + file_.Read(tile); + that_.StoreTile(tile, tileX_, tileY_); + } + }; + + + struct HierarchicalTiffWriter::Comparator + { + inline bool operator() (PendingTile* const& a, + PendingTile* const& b) + { + if (a->GetLevel() < b->GetLevel()) + { + return true; + } + + if (a->GetLevel() > b->GetLevel()) + { + return false; + } + + if (a->GetTileY() < b->GetTileY()) + { + return true; + } + + if (a->GetTileY() > b->GetTileY()) + { + return false; + } + + return a->GetTileX() < b->GetTileX(); + } + }; + + + static uint8_t GetUint8(const std::string& tile, + size_t index) + { + if (index >= tile.size()) + { + return 0; + } + else + { + return static_cast<uint8_t>(tile[index]); + } + } + + +#if 0 + static uint16_t GetUint16(const std::string& tile, + size_t index) + { + if (index + 1 >= tile.size()) + { + return 0; + } + else + { + return static_cast<uint16_t>(tile[index]) * 256 + static_cast<uint16_t>(tile[index + 1]); + } + } +#endif + + + static void CheckJpegTile(const std::string& tile, + Orthanc::PixelFormat pixelFormat) + { + // Check the sampling by accessing the "Start of Frame" header of JPEG + + if (tile.size() < 3 || + static_cast<uint8_t>(tile[0]) != 0xff || + static_cast<uint8_t>(tile[1]) != 0xd8 || + static_cast<uint8_t>(tile[2]) != 0xff) + { + LOG(WARNING) << "The source image does not contain JPEG tiles"; + return; + } + + // Look for the "Start of Frame" header (FF C0) + for (size_t i = 2; i + 1 < tile.size(); i++) + { + if (static_cast<uint8_t>(tile[i]) == 0xff && + static_cast<uint8_t>(tile[i + 1]) == 0xc0) + { + uint8_t numberOfComponents = GetUint8(tile, i + 9); + + switch (pixelFormat) + { + case Orthanc::PixelFormat_Grayscale8: + if (numberOfComponents != 3) + { + LOG(WARNING) << "The source image does not contain a grayscale image as expected"; + } + break; + + case Orthanc::PixelFormat_RGB24: + { + if (numberOfComponents != 3) + { + LOG(WARNING) << "The source image does not contain a RGB24 color image as expected"; + } + + // Read the header corresponding to the first component + const size_t component = 0; + const size_t offset = i + 10 + component * 3; + const uint8_t sampling = GetUint8(tile, offset + 1); + const int samplingH = sampling / 16; + const int samplingV = sampling % 16; + + LOG(WARNING) << "The source image uses chroma sampling " << samplingH << ":" << samplingV; + + if (samplingH != 2 || + samplingV != 2) + { + LOG(WARNING) << "The source image has not a chroma sampling of 2:2, " + << "you should consider using option \"--reencode\""; + } + + break; + } + + default: + break; + } + } + } + } + + + + void HierarchicalTiffWriter::StoreTile(const std::string& tile, + unsigned int tileX, + unsigned int tileY) + { + // Get the index of the tile + ttile_t index = TIFFComputeTile(tiff_, tileX * GetTileWidth(), tileY * GetTileHeight(), 0 /*z*/, 0 /*sample*/); + + if (TIFFWriteRawTile(tiff_, index, tile.size() ? const_cast<char*>(&tile[0]) : NULL, tile.size()) != + static_cast<tsize_t>(tile.size())) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile); + } + + if (isFirst_ && + GetImageCompression() == ImageCompression_Jpeg) + { + CheckJpegTile(tile, GetPixelFormat()); + } + + isFirst_ = false; + } + + + void HierarchicalTiffWriter::ConfigureLevel(const Level& level, + bool createLevel) + { + if (createLevel && TIFFWriteDirectory(tiff_) != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile); + } + + if (TIFFFlush(tiff_) != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile); + } + + currentLevel_ = level.z_; + nextX_ = 0; + nextY_ = 0; + + switch (GetImageCompression()) + { + case ImageCompression_Jpeg: + { + uint16_t c = COMPRESSION_JPEG; + + if (TIFFSetField(tiff_, TIFFTAG_COMPRESSION, c) != 1) + { + Close(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile); + } + + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + + switch (GetPixelFormat()) + { + case Orthanc::PixelFormat_RGB24: + { + uint16_t samplesPerPixel = 3; + uint16_t photometric = PHOTOMETRIC_YCBCR; + uint16_t planar = PLANARCONFIG_CONTIG; // Interleaved RGB + uint16_t bpp = 8; + uint16_t subsampleHorizontal = 2; + uint16_t subsampleVertical = 2; + + if (TIFFSetField(tiff_, TIFFTAG_SAMPLESPERPIXEL, samplesPerPixel) != 1 || + TIFFSetField(tiff_, TIFFTAG_PHOTOMETRIC, photometric) != 1 || + TIFFSetField(tiff_, TIFFTAG_BITSPERSAMPLE, bpp) != 1 || + TIFFSetField(tiff_, TIFFTAG_PLANARCONFIG, planar) != 1 || + TIFFSetField(tiff_, TIFFTAG_YCBCRSUBSAMPLING, subsampleHorizontal, subsampleVertical) != 1) + { + Close(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile); + } + + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + uint32_t w = level.width_; + uint32_t h = level.height_; + uint32_t tw = GetTileWidth(); + uint32_t th = GetTileHeight(); + + if (TIFFSetField(tiff_, TIFFTAG_IMAGEWIDTH, w) != 1 || + TIFFSetField(tiff_, TIFFTAG_IMAGELENGTH, h) != 1 || + TIFFSetField(tiff_, TIFFTAG_TILEWIDTH, tw) != 1 || + TIFFSetField(tiff_, TIFFTAG_TILELENGTH, th) != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile); + } + } + + + void HierarchicalTiffWriter::AdvanceToNextTile() + { + assert(currentLevel_ < levels_.size()); + + nextX_ += 1; + if (nextX_ >= levels_[currentLevel_].countTilesX_) + { + nextX_ = 0; + nextY_ += 1; + + if (nextY_ >= levels_[currentLevel_].countTilesY_) + { + currentLevel_ += 1; + + if (currentLevel_ < levels_.size()) + { + ConfigureLevel(levels_[currentLevel_], true); + } + } + } + } + + + void HierarchicalTiffWriter::ScanPending() + { + std::sort(pending_.begin(), pending_.end(), Comparator()); + + while (currentLevel_ < levels_.size() && + !pending_.empty() && + pending_.front()->GetLevel() == currentLevel_ && + pending_.front()->GetTileX() == nextX_ && + pending_.front()->GetTileY() == nextY_) + { + pending_.front()->Store(tiff_); + delete pending_.front(); + pending_.pop_front(); + AdvanceToNextTile(); + } + } + + + void HierarchicalTiffWriter::WriteRawTileInternal(const std::string& tile, + const Level& level, + unsigned int tileX, + unsigned int tileY) + { + boost::mutex::scoped_lock lock(mutex_); + + if (level.z_ == currentLevel_ && + tileX == nextX_ && + tileY == nextY_) + { + StoreTile(tile, tileX, tileY); + AdvanceToNextTile(); + ScanPending(); + } + else + { + pending_.push_back(new PendingTile(*this, level.z_, tileX, tileY, tile)); + } + } + + + void HierarchicalTiffWriter::AddLevelInternal(const Level& level) + { + if (level.z_ == 0) + { + // Configure the finest level on initialization + ConfigureLevel(level, false); + } + + levels_.push_back(level); + } + + + HierarchicalTiffWriter::HierarchicalTiffWriter(const std::string& path, + Orthanc::PixelFormat pixelFormat, + ImageCompression compression, + unsigned int tileWidth, + unsigned int tileHeight) : + PyramidWriterBase(pixelFormat, compression, tileWidth, tileHeight), + currentLevel_(0), + isFirst_(true) + { + tiff_ = TIFFOpen(path.c_str(), "w"); + if (tiff_ == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile); + } + } + + + HierarchicalTiffWriter::~HierarchicalTiffWriter() + { + if (pending_.size()) + { + LOG(ERROR) << "Some tiles (" << pending_.size() << ") were not written to the TIFF file"; + } + + for (size_t i = 0; i < pending_.size(); i++) + { + if (pending_[i]) + { + delete pending_[i]; + } + } + + Close(); + } + + + void HierarchicalTiffWriter::Flush() + { + boost::mutex::scoped_lock lock(mutex_); + ScanPending(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Outputs/HierarchicalTiffWriter.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,86 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "PyramidWriterBase.h" + +#include <tiff.h> +#include <tiffio.h> +#include <deque> +#include <vector> +#include <boost/thread.hpp> + +namespace OrthancWSI +{ + class HierarchicalTiffWriter : public PyramidWriterBase + { + private: + class PendingTile; + struct Comparator; + + TIFF* tiff_; + + boost::mutex mutex_; + std::deque<PendingTile*> pending_; + std::vector<Level> levels_; + unsigned int currentLevel_; + unsigned int nextX_; + unsigned int nextY_; + bool isFirst_; + + void Close() + { + TIFFClose(tiff_); + } + + void StoreTile(const std::string& tile, + unsigned int tileX, + unsigned int tileY); + + void ConfigureLevel(const Level& level, + bool createLevel); + + void AdvanceToNextTile(); + + void ScanPending(); + + + protected: + virtual void WriteRawTileInternal(const std::string& tile, + const Level& level, + unsigned int tileX, + unsigned int tileY); + + virtual void AddLevelInternal(const Level& level); + + + public: + HierarchicalTiffWriter(const std::string& path, + Orthanc::PixelFormat pixelFormat, + ImageCompression compression, + unsigned int tileWidth, + unsigned int tileHeight); + + virtual ~HierarchicalTiffWriter(); + + virtual void Flush(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Outputs/IPyramidWriter.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,60 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "../Enumerations.h" +#include "../Orthanc/Core/Images/ImageAccessor.h" + +#include <boost/noncopyable.hpp> + +namespace OrthancWSI +{ + class IPyramidWriter : public boost::noncopyable + { + public: + virtual ~IPyramidWriter() + { + } + + virtual unsigned int GetLevelCount() const = 0; + + virtual Orthanc::PixelFormat GetPixelFormat() const = 0; + + virtual unsigned int GetTileWidth() const = 0; + + virtual unsigned int GetTileHeight() const = 0; + + virtual unsigned int GetCountTilesX(unsigned int level) const = 0; + + virtual unsigned int GetCountTilesY(unsigned int level) const = 0; + + virtual void WriteRawTile(const std::string& tile, + ImageCompression compression, + unsigned int level, + unsigned int tileX, + unsigned int tileY) = 0; + + virtual void EncodeTile(const Orthanc::ImageAccessor& tile, + unsigned int level, + unsigned int tileX, + unsigned int tileY) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Outputs/InMemoryTiledImage.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,171 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "InMemoryTiledImage.h" + +#include "../ImageToolbox.h" +#include "../Orthanc/Core/Logging.h" +#include "../Orthanc/Core/OrthancException.h" + +namespace OrthancWSI +{ + static void CheckLevel(unsigned int level) + { + if (level != 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + InMemoryTiledImage::InMemoryTiledImage(Orthanc::PixelFormat format, + unsigned int countTilesX, + unsigned int countTilesY, + unsigned int tileWidth, + unsigned int tileHeight) : + format_(format), + countTilesX_(countTilesX), + countTilesY_(countTilesY), + tileWidth_(tileWidth), + tileHeight_(tileHeight) + { + } + + + InMemoryTiledImage::~InMemoryTiledImage() + { + for (Tiles::iterator it = tiles_.begin(); it != tiles_.end(); ++it) + { + delete it->second; + } + } + + + unsigned int InMemoryTiledImage::GetCountTilesX(unsigned int level) const + { + CheckLevel(level); + return countTilesX_; + } + + + unsigned int InMemoryTiledImage::GetCountTilesY(unsigned int level) const + { + CheckLevel(level); + return countTilesY_; + } + + + unsigned int InMemoryTiledImage::GetLevelWidth(unsigned int level) const + { + CheckLevel(level); + return tileWidth_ * countTilesX_; + } + + + unsigned int InMemoryTiledImage::GetLevelHeight(unsigned int level) const + { + CheckLevel(level); + return tileHeight_ * countTilesY_; + } + + + bool InMemoryTiledImage::ReadRawTile(std::string& tile, + unsigned int level, + unsigned int tileX, + unsigned int tileY) + { + CheckLevel(level); + return false; // Unavailable + } + + + Orthanc::ImageAccessor* InMemoryTiledImage::DecodeTile(unsigned int level, + unsigned int tileX, + unsigned int tileY) + { + CheckLevel(level); + + if (tileX >= countTilesX_ || + tileY >= countTilesY_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + { + boost::mutex::scoped_lock lock(mutex_); + + Tiles::const_iterator it = tiles_.find(std::make_pair(tileX, tileY)); + if (it != tiles_.end()) + { + return new Orthanc::ImageAccessor(*it->second); + } + else + { + LOG(ERROR) << "The following tile has not been set: " << tileX << "," << tileY; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + } + + + void InMemoryTiledImage::WriteRawTile(const std::string& raw, + ImageCompression compression, + unsigned int level, + unsigned int tileX, + unsigned int tileY) + { + std::auto_ptr<Orthanc::ImageAccessor> decoded(ImageToolbox::DecodeTile(raw, compression)); + EncodeTile(*decoded, level, tileX, tileY); + } + + + void InMemoryTiledImage::EncodeTile(const Orthanc::ImageAccessor& tile, + unsigned int level, + unsigned int tileX, + unsigned int tileY) + { + CheckLevel(level); + + if (tileX >= countTilesX_ || + tileY >= countTilesY_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + { + boost::mutex::scoped_lock lock(mutex_); + + Tiles::iterator it = tiles_.find(std::make_pair(tileX, tileY)); + if (it == tiles_.end()) + { + tiles_[std::make_pair(tileX, tileY)] = ImageToolbox::Clone(tile); + } + else + { + if (it->second) + { + delete it->second; + } + + it->second = ImageToolbox::Clone(tile); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Outputs/InMemoryTiledImage.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,109 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "../Inputs/ITiledPyramid.h" +#include "../Outputs/IPyramidWriter.h" + +#include <map> +#include <boost/thread.hpp> + +namespace OrthancWSI +{ + class InMemoryTiledImage : + public ITiledPyramid, + public IPyramidWriter + { + private: + typedef std::pair<unsigned int, unsigned int> Location; + typedef std::map<Location, Orthanc::ImageAccessor*> Tiles; + + boost::mutex mutex_; + Orthanc::PixelFormat format_; + unsigned int countTilesX_; + unsigned int countTilesY_; + unsigned int tileWidth_; + unsigned int tileHeight_; + Tiles tiles_; + + public: + InMemoryTiledImage(Orthanc::PixelFormat format, + unsigned int countTilesX, + unsigned int countTilesY, + unsigned int tileWidth, + unsigned int tileHeight); + + virtual ~InMemoryTiledImage(); + + virtual unsigned int GetLevelCount() const + { + return 1; + } + + virtual unsigned int GetCountTilesX(unsigned int level) const; + + virtual unsigned int GetCountTilesY(unsigned int level) const; + + virtual unsigned int GetLevelWidth(unsigned int level) const; + + virtual unsigned int GetLevelHeight(unsigned int level) const; + + virtual unsigned int GetTileWidth() const + { + return tileWidth_; + } + + virtual unsigned int GetTileHeight() const + { + return tileHeight_; + } + + virtual bool ReadRawTile(std::string& tile, + unsigned int level, + unsigned int tileX, + unsigned int tileY); + + virtual Orthanc::ImageAccessor* DecodeTile(unsigned int level, + unsigned int tileX, + unsigned int tileY); + + virtual ImageCompression GetImageCompression() const + { + return ImageCompression_None; + } + + virtual Orthanc::PixelFormat GetPixelFormat() const + { + return format_; + } + + virtual void WriteRawTile(const std::string& raw, + ImageCompression compression, + unsigned int level, + unsigned int tileX, + unsigned int tileY); + + virtual void EncodeTile(const Orthanc::ImageAccessor& tile, + unsigned int level, + unsigned int tileX, + unsigned int tileY); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Outputs/MultiframeDicomWriter.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,299 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "MultiframeDicomWriter.h" + +#include "../Orthanc/Core/OrthancException.h" +#include "../Orthanc/Core/Logging.h" +#include "../DicomToolbox.h" + +#include <dcmtk/dcmdata/dcuid.h> +#include <dcmtk/dcmdata/dcdeftag.h> +#include <dcmtk/dcmdata/dcostrmb.h> +#include <dcmtk/dcmdata/dcpxitem.h> +#include <dcmtk/dcmdata/dcpixel.h> +#include <dcmtk/dcmdata/dcvrat.h> + +#include <boost/lexical_cast.hpp> + +namespace OrthancWSI +{ + static void SaveDicomToMemory(std::string& target, + DcmFileFormat& dicom, + E_TransferSyntax transferSyntax) + { + dicom.validateMetaInfo(transferSyntax); + dicom.removeInvalidGroups(); + + E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength; + + // Create a memory buffer with the proper size + { + const uint32_t estimatedSize = dicom.calcElementLength(transferSyntax, encodingType); // (*) + target.resize(estimatedSize); + } + + DcmOutputBufferStream ob(&target[0], target.size()); + + // Fill the memory buffer with the meta-header and the dataset + dicom.transferInit(); + OFCondition c = dicom.write(ob, transferSyntax, encodingType, NULL, + /*opt_groupLength*/ EGL_recalcGL, + /*opt_paddingType*/ EPD_withoutPadding); + dicom.transferEnd(); + + if (!c.good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + // The DICOM file is successfully written, truncate the target + // buffer if its size was overestimated by (*) + ob.flush(); + + size_t effectiveSize = static_cast<size_t>(ob.tell()); + if (effectiveSize < target.size()) + { + target.resize(effectiveSize); + } + } + + + void MultiframeDicomWriter::ResetImage() + { + perFrameFunctionalGroups_.reset(new DcmSequenceOfItems(DCM_PerFrameFunctionalGroupsSequence)); + + if (compression_ != ImageCompression_None) + { + compressedPixelSequence_.reset(new DcmPixelSequence(DcmTag(DCM_PixelData, EVR_OB))); + + offsetTable_ = new DcmPixelItem(DCM_Item, EVR_OB); + compressedPixelSequence_->insert(offsetTable_); + offsetList_.reset(new DcmOffsetList); + } + + writtenSize_ = 0; + framesCount_ = 0; + } + + + void MultiframeDicomWriter::InjectUncompressedPixelData(DcmFileFormat& dicom) + { + static const size_t GIGABYTE = 1024 * 1024 * 1024; + + std::string pixelData; + uncompressedPixelData_.Flatten(pixelData); + + // Prevent the creation of too large DICOM files + // (uncompressed DICOM files are limited to 2GB) + if (pixelData.size() > GIGABYTE) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory); + } + + std::auto_ptr<DcmPixelData> pixels(new DcmPixelData(DCM_PixelData)); + + uint8_t* target = NULL; + if (!pixels->createUint8Array(pixelData.size(), target).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (pixelData.size() > 0) + { + if (target == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + // TODO Avoid this memcpy() + memcpy(target, pixelData.c_str(), pixelData.size()); + } + + if (!dicom.getDataset()->insert(pixels.release(), false, false).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + MultiframeDicomWriter::MultiframeDicomWriter(const DcmDataset& dataset, + ImageCompression compression, + Orthanc::PixelFormat pixelFormat, + unsigned int width, + unsigned int height, + unsigned int tileWidth, + unsigned int tileHeight) : + compression_(compression), + width_(width), + height_(height) + { + switch (compression) + { + case ImageCompression_None: + transferSyntax_ = EXS_LittleEndianImplicit; + break; + + case ImageCompression_Jpeg: + // Default transfer syntax for lossy JPEG 8bit compression + transferSyntax_ = EXS_JPEGProcess1TransferSyntax; + break; + + case ImageCompression_Jpeg2000: + // JPEG2000 compression (lossless only) + transferSyntax_ = EXS_JPEG2000LosslessOnly; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + if (!sharedTags_.copyFrom(dataset).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + // We only take care of grayscale or RGB images in 8bpp + DicomToolbox::SetUint32Tag(sharedTags_, DCM_TotalPixelMatrixColumns, width); + DicomToolbox::SetUint32Tag(sharedTags_, DCM_TotalPixelMatrixRows, height); + DicomToolbox::SetUint16Tag(sharedTags_, DCM_PlanarConfiguration, 0); // Interleaved RGB values + DicomToolbox::SetUint16Tag(sharedTags_, DCM_Columns, tileWidth); + DicomToolbox::SetUint16Tag(sharedTags_, DCM_Rows, tileHeight); + DicomToolbox::SetUint16Tag(sharedTags_, DCM_BitsAllocated, 8); + DicomToolbox::SetUint16Tag(sharedTags_, DCM_BitsStored, 8); + DicomToolbox::SetUint16Tag(sharedTags_, DCM_HighBit, 7); + DicomToolbox::SetUint16Tag(sharedTags_, DCM_PixelRepresentation, 0); // Unsigned values + + switch (pixelFormat) + { + case Orthanc::PixelFormat_RGB24: + uncompressedFrameSize_ = 3 * tileWidth * tileHeight; + DicomToolbox::SetUint16Tag(sharedTags_, DCM_SamplesPerPixel, 3); + + if (compression_ == ImageCompression_Jpeg) + { + DicomToolbox::SetStringTag(sharedTags_, DCM_PhotometricInterpretation, "YBR_FULL_422"); + } + else + { + DicomToolbox::SetStringTag(sharedTags_, DCM_PhotometricInterpretation, "RGB"); + } + + break; + + case Orthanc::PixelFormat_Grayscale8: + uncompressedFrameSize_ = tileWidth * tileHeight; + DicomToolbox::SetUint16Tag(sharedTags_, DCM_SamplesPerPixel, 1); + DicomToolbox::SetStringTag(sharedTags_, DCM_PhotometricInterpretation, "MONOCHROME2"); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + ResetImage(); + } + + + void MultiframeDicomWriter::AddFrame(const std::string& frame, + DcmItem* functionalGroup) // This takes the ownership + { + // Free the functional group on error + std::auto_ptr<DcmItem> functionalGroupRaii(functionalGroup); + + if (compression_ == ImageCompression_None) + { + if (frame.size() != uncompressedFrameSize_) + { + LOG(ERROR) << "An uncompressed frame has not the proper size: " + << frame.size() << " instead of " << uncompressedFrameSize_; + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + + uncompressedPixelData_.AddChunk(frame); + } + else + { + uint8_t* bytes = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(frame.c_str())); + + compressedPixelSequence_->storeCompressedFrame(*offsetList_, bytes, frame.size(), + 0 /* unlimited fragment size */); + } + + if (functionalGroup != NULL) + { + perFrameFunctionalGroups_->insert(functionalGroupRaii.release()); + } + else + { + perFrameFunctionalGroups_->insert(new DcmItem); + } + + writtenSize_ += frame.size(); + framesCount_ += 1; + } + + + void MultiframeDicomWriter::Flush(std::string& target, + unsigned int instanceNumber) + { + if (instanceNumber <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + std::string tmp = boost::lexical_cast<std::string>(instanceNumber); + + std::auto_ptr<DcmFileFormat> dicom(new DcmFileFormat); + + char uid[100]; + dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT); + + if (!dicom->getDataset()->copyFrom(sharedTags_).good() || + !dicom->getDataset()->insert(perFrameFunctionalGroups_.release(), false, false).good() || + !dicom->getDataset()->putAndInsertString(DCM_SOPInstanceUID, uid).good() || + !dicom->getDataset()->putAndInsertString(DCM_InstanceNumber, tmp.c_str()).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + DicomToolbox::SetStringTag(*dicom->getDataset(), DCM_NumberOfFrames, boost::lexical_cast<std::string>(framesCount_)); + + switch (compression_) + { + case ImageCompression_None: + InjectUncompressedPixelData(*dicom); + break; + + case ImageCompression_Jpeg: + case ImageCompression_Jpeg2000: + offsetTable_->createOffsetTable(*offsetList_); + dicom->getDataset()->insert(compressedPixelSequence_.release()); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + ResetImage(); + + SaveDicomToMemory(target, *dicom, transferSyntax_); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Outputs/MultiframeDicomWriter.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,93 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "../Enumerations.h" +#include "../Orthanc/Core/ChunkedBuffer.h" + +#include <boost/noncopyable.hpp> +#include <memory> +#include <stdint.h> + +#include <dcmtk/dcmdata/dcpixseq.h> +#include <dcmtk/dcmdata/dcdatset.h> +#include <dcmtk/dcmdata/dcfilefo.h> + +namespace OrthancWSI +{ + class MultiframeDicomWriter : public boost::noncopyable + { + private: + ImageCompression compression_; + E_TransferSyntax transferSyntax_; + DcmDataset sharedTags_; + size_t writtenSize_; + size_t framesCount_; + size_t uncompressedFrameSize_; + unsigned int width_; + unsigned int height_; + + Orthanc::ChunkedBuffer uncompressedPixelData_; + std::auto_ptr<DcmSequenceOfItems> perFrameFunctionalGroups_; + std::auto_ptr<DcmPixelSequence> compressedPixelSequence_; + DcmPixelItem* offsetTable_; + std::auto_ptr<DcmOffsetList> offsetList_; + + void ResetImage(); + + void InjectUncompressedPixelData(DcmFileFormat& dicom); + + public: + MultiframeDicomWriter(const DcmDataset& dataset, + ImageCompression compression, + Orthanc::PixelFormat pixelFormat, + unsigned int width, + unsigned int height, + unsigned int tileWidth, + unsigned int tileHeight); + + void AddFrame(const std::string& frame, + DcmItem* functionalGroup); // This takes the ownership + + void Flush(std::string& target, + unsigned int instanceNumber); + + unsigned int GetFramesCount() const + { + return framesCount_; + } + + size_t GetSize() const + { + return writtenSize_; + } + + unsigned int GetTotalWidth() const + { + return width_; + } + + unsigned int GetTotalHeight() const + { + return height_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Outputs/PyramidWriterBase.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,150 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "PyramidWriterBase.h" + +#include "../ImageToolbox.h" +#include "../Orthanc/Core/OrthancException.h" +#include "../Orthanc/Core/Logging.h" + +namespace OrthancWSI +{ + PyramidWriterBase::Level PyramidWriterBase::GetLevel(unsigned int level) const + { + boost::mutex::scoped_lock lock(const_cast<PyramidWriterBase&>(*this).mutex_); + + if (level >= levels_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + return levels_[level]; + } + } + + + PyramidWriterBase::PyramidWriterBase(Orthanc::PixelFormat pixelFormat, + ImageCompression compression, + unsigned int tileWidth, + unsigned int tileHeight) : + pixelFormat_(pixelFormat), + compression_(compression), + tileWidth_(tileWidth), + tileHeight_(tileHeight), + jpegQuality_(90), // Default JPEG quality + first_(true) + { + } + + + void PyramidWriterBase::SetJpegQuality(int quality) + { + if (quality <= 0 || quality > 100) + { + LOG(ERROR) << "The JPEG quality must be in range [1;100], but " << quality << " is provided"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + jpegQuality_ = quality; + } + + + void PyramidWriterBase::AddLevel(unsigned int width, + unsigned int height) + { + boost::mutex::scoped_lock lock(mutex_); + + if (!first_) + { + LOG(ERROR) << "Cannot add pyramid levels after some tile has already been written"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + if (!levels_.empty()) + { + const Level& previous = levels_[levels_.size() - 1]; + + if (width >= previous.width_ || + height >= previous.height_ || + width == 0 || + height == 0) + { + LOG(ERROR) << "Levels must have strictly decreasing sizes"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + Level level; + level.z_ = levels_.size(); + level.width_ = width; + level.height_ = height; + level.countTilesX_ = CeilingDivision(width, tileWidth_); + level.countTilesY_ = CeilingDivision(height, tileHeight_); + levels_.push_back(level); + + AddLevelInternal(level); + } + + + unsigned int PyramidWriterBase::GetLevelCount() const + { + boost::mutex::scoped_lock lock(const_cast<PyramidWriterBase&>(*this).mutex_); + return levels_.size(); + } + + + void PyramidWriterBase::WriteRawTile(const std::string& tile, + ImageCompression compression, + unsigned int z, + unsigned int x, + unsigned int y) + { + first_ = false; + + const Level level = GetLevel(z); + + if (compression != compression_) + { + std::string recoded; + ImageToolbox::ChangeTileCompression(recoded, tile, compression, compression_, jpegQuality_); + WriteRawTileInternal(recoded, level, x, y); + } + else + { + WriteRawTileInternal(tile, level, x, y); + } + } + + + void PyramidWriterBase::EncodeTile(const Orthanc::ImageAccessor& tile, + unsigned int z, + unsigned int x, + unsigned int y) + { + first_ = false; + + const Level level = GetLevel(z); + + std::string raw; + ImageToolbox::EncodeTile(raw, tile, compression_, jpegQuality_); + WriteRawTileInternal(raw, level, x, y); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Outputs/PyramidWriterBase.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,124 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "IPyramidWriter.h" + +#include <boost/thread.hpp> + +namespace OrthancWSI +{ + class PyramidWriterBase : public IPyramidWriter + { + protected: + struct Level + { + unsigned int z_; + unsigned int width_; + unsigned int height_; + unsigned int countTilesX_; + unsigned int countTilesY_; + }; + + // WARNING: The following method can be called from multiple + // threads, locking must be implemented in derived classes. + virtual void WriteRawTileInternal(const std::string& tile, + const Level& level, + unsigned int tileX, + unsigned int tileY) = 0; + + // This function is invoked before any call to WriteRawTileInternal() + virtual void AddLevelInternal(const Level& level) = 0; + + private: + boost::mutex mutex_; // This mutex protects access to the levels + Orthanc::PixelFormat pixelFormat_; + ImageCompression compression_; + unsigned int tileWidth_; + unsigned int tileHeight_; + uint8_t jpegQuality_; + std::vector<Level> levels_; + bool first_; + + Level GetLevel(unsigned int level) const; + + public: + PyramidWriterBase(Orthanc::PixelFormat pixelFormat, + ImageCompression compression, + unsigned int tileWidth, + unsigned int tileHeight); + + uint8_t GetJpegQuality() const + { + return jpegQuality_; + } + + void SetJpegQuality(int quality); + + virtual void AddLevel(unsigned int width, + unsigned int height); + + virtual unsigned int GetTileWidth() const + { + return tileWidth_; + } + + virtual unsigned int GetTileHeight() const + { + return tileHeight_; + } + + virtual unsigned int GetCountTilesX(unsigned int level) const + { + return GetLevel(level).countTilesX_; + } + + virtual unsigned int GetCountTilesY(unsigned int level) const + { + return GetLevel(level).countTilesY_; + } + + virtual unsigned int GetLevelCount() const; + + ImageCompression GetImageCompression() const + { + return compression_; + } + + virtual void WriteRawTile(const std::string& tile, + ImageCompression compression, + unsigned int z, + unsigned int tileX, + unsigned int tileY); + + virtual void EncodeTile(const Orthanc::ImageAccessor& tile, + unsigned int z, + unsigned int tileX, + unsigned int tileY); + + virtual Orthanc::PixelFormat GetPixelFormat() const + { + return pixelFormat_; + } + + virtual void Flush() = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Outputs/TruncatedPyramidWriter.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,117 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "TruncatedPyramidWriter.h" + +#include "../Orthanc/Core/OrthancException.h" + +namespace OrthancWSI +{ + TruncatedPyramidWriter::TruncatedPyramidWriter(IPyramidWriter& lower, + unsigned int upperLevelIndex) : + lowerLevels_(lower), + upperLevel_(lower.GetPixelFormat(), + lower.GetCountTilesX(upperLevelIndex), + lower.GetCountTilesY(upperLevelIndex), + lower.GetTileWidth(), + lower.GetTileHeight()), + upperLevelIndex_(upperLevelIndex) + { + if (upperLevelIndex > lower.GetLevelCount()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + unsigned int TruncatedPyramidWriter::GetCountTilesX(unsigned int level) const + { + if (level < upperLevelIndex_) + { + return lowerLevels_.GetCountTilesX(level); + } + else if (level == upperLevelIndex_) + { + return upperLevel_.GetCountTilesX(0); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + unsigned int TruncatedPyramidWriter::GetCountTilesY(unsigned int level) const + { + if (level < upperLevelIndex_) + { + return lowerLevels_.GetCountTilesY(level); + } + else if (level == upperLevelIndex_) + { + return upperLevel_.GetCountTilesY(0); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + void TruncatedPyramidWriter::WriteRawTile(const std::string& tile, + ImageCompression compression, + unsigned int level, + unsigned int x, + unsigned int y) + { + if (level < upperLevelIndex_) + { + lowerLevels_.WriteRawTile(tile, compression, level, x, y); + } + else if (level == upperLevelIndex_) + { + upperLevel_.WriteRawTile(tile, compression, 0, x, y); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + void TruncatedPyramidWriter::EncodeTile(const Orthanc::ImageAccessor& tile, + unsigned int level, + unsigned int x, + unsigned int y) + { + if (level < upperLevelIndex_) + { + lowerLevels_.EncodeTile(tile, level, x, y); + } + else if (level == upperLevelIndex_) + { + upperLevel_.EncodeTile(tile, 0, x, y); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Outputs/TruncatedPyramidWriter.h Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,78 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "InMemoryTiledImage.h" + +namespace OrthancWSI +{ + class TruncatedPyramidWriter : public IPyramidWriter + { + private: + IPyramidWriter& lowerLevels_; + InMemoryTiledImage upperLevel_; + unsigned int upperLevelIndex_; + + public: + TruncatedPyramidWriter(IPyramidWriter& lower, + unsigned int upperLevelIndex); + + virtual unsigned int GetLevelCount() const + { + return upperLevelIndex_ + 1; + } + + virtual Orthanc::PixelFormat GetPixelFormat() const + { + return lowerLevels_.GetPixelFormat(); + } + + virtual unsigned int GetTileWidth() const + { + return lowerLevels_.GetTileWidth(); + } + + virtual unsigned int GetTileHeight() const + { + return lowerLevels_.GetTileHeight(); + } + + virtual unsigned int GetCountTilesX(unsigned int level) const; + + virtual unsigned int GetCountTilesY(unsigned int level) const; + + virtual void WriteRawTile(const std::string& tile, + ImageCompression compression, + unsigned int level, + unsigned int x, + unsigned int y); + + virtual void EncodeTile(const Orthanc::ImageAccessor& tile, + unsigned int level, + unsigned int x, + unsigned int y); + + InMemoryTiledImage& GetUpperLevel() + { + return upperLevel_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/NEWS Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,4 @@ +2016-10-22 +========== + +* Initial release
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,85 @@ +Orthanc for Whole-Slide Imaging +=============================== + + +General Information +------------------- + +This repository contains the source code of the official tools to +introduce support of whole-slide microscopic imaging (WSI) in +Orthanc. It is made of three separate components: + +(a) A command-line tool called "Dicomizer" that converts whole-slide + images to DICOM, according to Supplement 145. + + The input images of the Dicomizer can be either formatted as + standard hierarchical TIFF images, or as proprietary formats for + digital pathology. In the latter case, OpenSlide is used to decode + the input images. + +(b) An Orthanc plugin that extends Orthanc Explorer with a Web viewer + of whole-slide images. + +(c) A command-line tool called "DicomToTiff" that converts some + whole-slide image stored in an Orthanc server, into a standard + hierarchical TIFF file. This tool can be used to export DICOM + images to any post-processing framework. + + + +Dependencies +------------ + +The whole-slide imaging framework is notably based upon the following +projects: + +* Orthanc, a lightweight Vendor Neutral Archive (DICOM server): + http://www.orthanc-server.com/ + +* OpenSlide, a simple interface to read whole-slide images encoded + using proprietary file formats: + http://openslide.org/ + +* OpenLayers, a framework to put a dynamic map in any web page: + https://openlayers.org/ + +* OpenJpeg, an open-source JPEG 2000 codec: + http://www.openjpeg.org/ + + + +Installation and usage +---------------------- + +Build instructions are similar to that of Orthanc: +https://orthanc.chu.ulg.ac.be/book/faq/compiling.html + +The two command-line tools can be found in folder "Applications". +They come with an extensive "--help" option. + +The Web viewer plugin can be found in folder "ViewerPlugin". + + + +Licensing +--------- + +The WSI toolbox for Orthanc is licensed under the AGPL license. + +We also kindly ask scientific works and clinical studies that make +use of Orthanc to cite Orthanc in their associated publications. +Similarly, we ask open-source and closed-source products that make +use of Orthanc to warn us about this use. You can cite our work +using the following BibTeX entry: + +@inproceedings{Jodogne:ISBI2013, + author = {Jodogne, S. and Bernard, C. and Devillers, M. and Lenaerts, E. and Coucke, P.}, + title = {Orthanc -- {A} Lightweight, {REST}ful {DICOM} Server for Healthcare and Medical Research}, + booktitle={Biomedical Imaging ({ISBI}), {IEEE} 10th International Symposium on}, + year={2013}, + pages={190-193}, + ISSN={1945-7928}, + month=apr, + url={http://ieeexplore.ieee.org/xpl/articleDetails.jsp?tp=&arnumber=6556444}, + address={San Francisco, {CA}, {USA}} +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/BrightfieldOpticalPath.json Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,20 @@ +[ + { + "OpticalPathIdentifier" : "1", + "OpticalPathDescription" : "Brightfield", + "IlluminationTypeCodeSequence" : [ + { + "CodeValue" : "111744", + "CodingSchemeDesignator" : "DCM", + "CodeMeaning" : "Brightfield illumination" + } + ], + "IlluminationColorCodeSequence" : [ + { + "CodeValue" : "R-102C0", + "CodingSchemeDesignator" : "SRT", + "CodeMeaning" : "Full Spectrum" + } + ] + } +]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/BoostExtendedConfiguration.cmake Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,21 @@ +SET(ORTHANC_BOOST_COMPONENTS program_options) + +include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) + +if (BOOST_STATIC) + list(APPEND BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/program_options/src/cmdline.cpp + ${BOOST_SOURCES_DIR}/libs/program_options/src/config_file.cpp + ${BOOST_SOURCES_DIR}/libs/program_options/src/convert.cpp + ${BOOST_SOURCES_DIR}/libs/program_options/src/options_description.cpp + ${BOOST_SOURCES_DIR}/libs/program_options/src/parsers.cpp + ${BOOST_SOURCES_DIR}/libs/program_options/src/positional_options.cpp + ${BOOST_SOURCES_DIR}/libs/program_options/src/split.cpp + ${BOOST_SOURCES_DIR}/libs/program_options/src/utf8_codecvt_facet.cpp + ${BOOST_SOURCES_DIR}/libs/program_options/src/value_semantic.cpp + ${BOOST_SOURCES_DIR}/libs/program_options/src/variables_map.cpp + #${BOOST_SOURCES_DIR}/libs/program_options/src/winmain.cpp + ) + add_definitions(-DBOOST_PROGRAM_OPTIONS_NO_LIB) +endif() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/LibTiffConfiguration.cmake Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,115 @@ +if (STATIC_BUILD OR NOT USE_SYSTEM_LIBTIFF) + SET(LIBTIFF_SOURCES_DIR ${CMAKE_BINARY_DIR}/tiff-4.0.6) + SET(LIBTIFF_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/WSI/tiff-4.0.6.tar.gz") + SET(LIBTIFF_MD5 "d1d2e940dea0b5ad435f21f03d96dd72") + + DownloadPackage(${LIBTIFF_MD5} ${LIBTIFF_URL} "${LIBTIFF_SOURCES_DIR}") + + if (NOT EXISTS ${LIBTIFF_SOURCES_DIR}/libtiff/tif_config.h) + file(WRITE ${LIBTIFF_SOURCES_DIR}/libtiff/tif_config.h " +#include <stdint.h> +#include <stddef.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +") + file(WRITE ${LIBTIFF_SOURCES_DIR}/libtiff/tiffconf.h " +#include <stdint.h> +#include <sys/types.h> +") + endif() + + set(TIFF_FILLORDER FILLORDER_MSB2LSB) + if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "i.*86.*" OR + CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "amd64.*" OR + CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "x86_64.*") + set(TIFF_FILLORDER FILLORDER_LSB2MSB) + endif() + + add_definitions( + -DTIFF_INT8_T=int8_t + -DTIFF_INT16_T=int16_t + -DTIFF_INT32_T=int32_t + -DTIFF_INT64_T=int64_t + -DTIFF_UINT8_T=uint8_t + -DTIFF_UINT16_T=uint16_t + -DTIFF_UINT32_T=uint32_t + -DTIFF_UINT64_T=uint64_t + -DTIFF_SSIZE_T=ssize_t + -DHAVE_IEEEFP=1 + -DHOST_FILLORDER=${TIFF_FILLORDER} + -DHAVE_SNPRINTF=1 + -DJPEG_SUPPORT=1 + -DLZW_SUPPORT=1 + + -DTIFF_INT64_FORMAT="%lld" + -DTIFF_UINT64_FORMAT="%llu" + -DTIFF_SSIZE_FORMAT="%d" + ) + + set(LIBTIFF_SOURCES + #${LIBTIFF_SOURCES_DIR}/libtiff/mkg3states.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_aux.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_close.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_codec.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_color.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_compress.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_dir.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_dirinfo.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_dirread.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_dirwrite.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_dumpmode.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_error.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_extension.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_fax3.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_fax3sm.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_flush.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_getimage.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_jbig.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_jpeg_12.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_jpeg.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_luv.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_lzma.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_lzw.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_next.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_ojpeg.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_open.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_packbits.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_pixarlog.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_predict.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_print.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_read.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_strip.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_swab.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_thunder.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_tile.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_unix.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_version.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_warning.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_write.c + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_zip.c + ) + + include_directories(${LIBTIFF_SOURCES_DIR}/libtiff) + + if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "Windows") + list(APPEND LIBTIFF_SOURCES + ${LIBTIFF_SOURCES_DIR}/libtiff/tif_win32.c + ) + endif() + + source_group(ThirdParty\\LibTiff REGULAR_EXPRESSION ${LIBTIFF_SOURCES_DIR}/.*) + +else() + CHECK_INCLUDE_FILE_CXX(tiff.h HAVE_LIBTIFF_H) + if (NOT HAVE_LIBTIFF_H) + message(FATAL_ERROR "Please install the libtiff-dev package") + endif() + + CHECK_LIBRARY_EXISTS(tiff TIFFGetField "" HAVE_LIBTIFF_LIB) + if (NOT HAVE_LIBTIFF_LIB) + message(FATAL_ERROR "Please install the libtiff-dev package") + endif() + + link_libraries(tiff) +endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/OpenJpegConfiguration.cmake Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,144 @@ +if (STATIC_BUILD OR NOT USE_SYSTEM_OPENJPEG) + SET(OPENJPEG_SOURCES_DIR ${CMAKE_BINARY_DIR}/openjpeg-version.2.1) + SET(OPENJPEG_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/openjpeg-2.1.tar.gz") + SET(OPENJPEG_MD5 "3e1c451c087f8462955426da38aa3b3d") + + if (IS_DIRECTORY "${OPENJPEG_SOURCES_DIR}") + set(FirstRun OFF) + else() + set(FirstRun ON) + endif() + + DownloadPackage(${OPENJPEG_MD5} ${OPENJPEG_URL} "${OPENJPEG_SOURCES_DIR}") + + execute_process( + COMMAND ${PATCH_EXECUTABLE} -p0 -N -i ${CMAKE_CURRENT_LIST_DIR}/OpenJpegConfiguration.patch + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + + if (Failure AND FirstRun) + message(FATAL_ERROR "Error while patching a file") + endif() + + if (USE_OPENJPEG_JP2) + set(OPENJPEG_SOURCES + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/bio.c + #${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/cidx_manager.c + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/cio.c + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/dwt.c + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/event.c + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/function_list.c + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/image.c + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/invert.c + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/j2k.c + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/jp2.c + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/mct.c + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/mqc.c + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/openjpeg.c + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/opj_clock.c + #${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/phix_manager.c + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/pi.c + #${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/ppix_manager.c + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/raw.c + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/t1.c + #${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/t1_generate_luts.c + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/t2.c + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/tcd.c + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/tgt.c + #${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/thix_manager.c + #${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/tpix_manager.c + ) + + configure_file( + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/opj_config.h.cmake.in + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/opj_config.h + @ONLY + ) + + configure_file( + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/opj_config_private.h.cmake.in + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/opj_config_private.h + @ONLY + ) + + include_directories( + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2 + ) + + # The following definition disables explicit inlining. This is + # necessary to bypass the "undefined reference to + # `opj_t1_dec_sigpass_step_mqc'" error. + add_definitions( + #-DINLINE= + ) + + else() + AUX_SOURCE_DIRECTORY(${OPENJPEG_SOURCES_DIR}/src/lib/openmj2 OPENJPEG_SOURCES) + + configure_file( + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/opj_config.h.cmake.in + ${OPENJPEG_SOURCES_DIR}/src/lib/openmj2/opj_config.h + @ONLY + ) + + configure_file( + ${OPENJPEG_SOURCES_DIR}/src/lib/openjp2/opj_config_private.h.cmake.in + ${OPENJPEG_SOURCES_DIR}/src/lib/openmj2/opj_config_private.h + @ONLY + ) + + include_directories( + ${OPENJPEG_SOURCES_DIR}/src/lib/openmj2 + ) + endif() + + + add_definitions( + -DOPJ_STATIC + -DORTHANC_OPENJPEG_MAJOR_VERSION=2 + ) + + if (NOT WIN32) + add_definitions( + -DOPJ_HAVE_STDINT_H=1 + -DOPJ_HAVE_INTTYPES_H=1 + ) + endif() + + source_group(ThirdParty\\OpenJpeg REGULAR_EXPRESSION ${OPENJPEG_SOURCES_DIR}/.*) + +else() + CHECK_INCLUDE_FILE_CXX(openjpeg.h HAVE_OPENJPEG_H) + if (NOT HAVE_OPENJPEG_H) + message(FATAL_ERROR "Please install the openjpeg-devel package") + endif() + + find_path(OPENJPEG_INCLUDE_DIR openjpeg.h) + + CHECK_LIBRARY_EXISTS(openjpeg opj_image_create "" HAVE_OPENJPEG_LIB) + if (NOT HAVE_OPENJPEG_LIB) + message(FATAL_ERROR "Please install the openjpeg-devel package") + endif() + + # Autodetection of the version of OpenJpeg + file(STRINGS + "${OPENJPEG_INCLUDE_DIR}/openjpeg.h" + OPENJPEG_VERSION REGEX + "#define OPENJPEG_VERSION") + + string(REGEX REPLACE + ".*OPENJPEG_VERSION.*\"([0-9]*)\\.([0-9]*)\\.([0-9]*)\"$" + "\\1" + OPENJPEG_MAJOR_VERSION ${OPENJPEG_VERSION}) + + if (OPENJPEG_MAJOR_VERSION EQUAL 1) + add_definitions(-DORTHANC_OPENJPEG_MAJOR_VERSION=1) + elseif (OPENJPEG_MAJOR_VERSION EQUAL 2) + add_definitions(-DORTHANC_OPENJPEG_MAJOR_VERSION=2) + else() + message(FATAL_ERROR "Cannot parse the version of OpenJpeg") + endif() + + link_libraries(openjpeg) +endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/OpenJpegConfiguration.patch Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,44 @@ +diff -urEb openjpeg-version.2.1.orig/src/lib/openjp2/t1.c openjpeg-version.2.1/src/lib/openjp2/t1.c +--- openjpeg-version.2.1.orig/src/lib/openjp2/t1.c 2016-07-15 10:36:18.575913348 +0200 ++++ openjpeg-version.2.1/src/lib/openjp2/t1.c 2016-07-15 10:36:45.571914446 +0200 +@@ -85,13 +85,13 @@ + OPJ_INT32 orient, + OPJ_INT32 oneplushalf, + OPJ_INT32 vsc); +-static INLINE void opj_t1_dec_sigpass_step_mqc( ++static void opj_t1_dec_sigpass_step_mqc( + opj_t1_t *t1, + opj_flag_t *flagsp, + OPJ_INT32 *datap, + OPJ_INT32 orient, + OPJ_INT32 oneplushalf); +-static INLINE void opj_t1_dec_sigpass_step_mqc_vsc( ++static void opj_t1_dec_sigpass_step_mqc_vsc( + opj_t1_t *t1, + opj_flag_t *flagsp, + OPJ_INT32 *datap, +@@ -179,20 +179,20 @@ + OPJ_UINT32 vsc); + #endif + +-static INLINE void opj_t1_dec_refpass_step_raw( ++static void opj_t1_dec_refpass_step_raw( + opj_t1_t *t1, + opj_flag_t *flagsp, + OPJ_INT32 *datap, + OPJ_INT32 poshalf, + OPJ_INT32 neghalf, + OPJ_INT32 vsc); +-static INLINE void opj_t1_dec_refpass_step_mqc( ++static void opj_t1_dec_refpass_step_mqc( + opj_t1_t *t1, + opj_flag_t *flagsp, + OPJ_INT32 *datap, + OPJ_INT32 poshalf, + OPJ_INT32 neghalf); +-static INLINE void opj_t1_dec_refpass_step_mqc_vsc( ++static void opj_t1_dec_refpass_step_mqc_vsc( + opj_t1_t *t1, + opj_flag_t *flagsp, + OPJ_INT32 *datap, +Only in openjpeg-version.2.1/src/lib/openjp2: t1.c~
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/OrthancWSI.doxygen Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,1794 @@ +# Doxyfile 1.8.1.2 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" "). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = OrthancWSI + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "Orthanc for Whole-Slide Imaging API" + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = @ORTHANC_WSI_DIR@/Resources/OrthancLogoDocumentation.png + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = OrthancWSIDocumentation + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all +# comments according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you +# can mix doxygen, HTML, and XML commands with Markdown formatting. +# Disable only in case of backward compatibilities issues. + +MARKDOWN_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = YES + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +SYMBOL_CACHE_SIZE = 0 + +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command <command> <input-file>, where <command> is the value of +# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = @ORTHANC_WSI_DIR@/Framework + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = @ORTHANC_WSI_DIR@/Framework/Orthanc/Resources/ \ + @ORTHANC_WSI_DIR@/Framework/Orthanc/Sdk-1.0.0/ + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = Orthanc::Internals \ + OrthancPlugins::Internals + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command <filter> <input-file>, where <filter> +# is the value of the INPUT_FILTER tag, and <input-file> is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C, C++ and Fortran comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = doc + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# style sheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of +# entries shown in the various tree structured indices initially; the user +# can expand and collapse entries dynamically later on. Doxygen will expand +# the tree to such a level that at most the specified number of entries are +# visible (unless a fully collapsed tree already exceeds this amount). +# So setting the number of entries 1 will produce a full collapsed tree by +# default. 0 is a special value representing an infinite number of entries +# and will result in a full expanded tree by default. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters"> +# Qt Help Project / Custom Filters</a>. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes"> +# Qt Help Project / Filter Attributes</a>. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 1 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you may also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to +# the MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. +# However, it is strongly recommended to install a local +# copy of MathJax from http://www.mathjax.org before deployment. + +MATHJAX_RELPATH = http://www.mathjax.org/mathjax + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load style sheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = ORTHANC_ENABLE_OPENSLIDE=1 + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. For each +# tag file the location of the external documentation should be added. The +# format of a tag file without this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths +# or URLs. Note that each tag file must have a unique name (where the name does +# NOT include the path). If a tag file is not located in the directory in which +# doxygen is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = NO + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside +# the class node. If there are many fields or methods and many nodes the +# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS +# threshold limits the number of items for each type to make the size more +# managable. Set this to 0 for no limit. Note that the threshold may be +# exceeded by 50% before the limit is enforced. + +UML_LIMIT_NUM_FIELDS = 10 + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/SampleDataset.json Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,43 @@ +{ + "PatientID" : "C123456789", + "PatientName" : "SOME^PATIENT", + "PatientBirthDate" : "19700101", + "PatientSex" : "M", + "StudyID" : "NONE", + "SeriesNumber" : "1", + "ReferringPhysicianName" : "SOME^PHYSICIAN", + "AccessionNumber" : "123456789", + "Manufacturer" : "MyManufacturer", + "ManufacturerModelName" : "MyModel", + "DeviceSerialNumber" : "MySerialNumber", + "SoftwareVersions" : "MyVersion", + + "ImageType" : "DERIVED\\PRIMARY\\VOLUME\\NONE", + "FocusMethod" : "AUTO", + "ExtendedDepthOfField" : "NO", + + "AcquisitionContextSequence" : [ + ], + "AcquisitionDuration" : "100", + + "ContainerIdentifier" : "CI_12345", + "IssuerOfTheContainerIdentifierSequence" : [ + ], + "ContainerTypeCodeSequence" : [ + ], + "IssuerOfTheContainerIdentifierSequence" : [ + ], + + "SpecimenDescriptionSequence" : [ + { + "SpecimenIdentifier" : "Specimen^Identifier", + "SpecimenUID" : "1.2.276.0.7230010.3.1.4.3252829876.4112.1426166133.871", + "IssuerOfTheSpecimenIdentifierSequence" : [ + ], + "SpecimenPreparationSequence" : [ + ] + } + ], + "SpecimenLabelInImage" : "NO", + "BurnedInAnnotation" : "NO" +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/SyncOrthancFolder.py Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,164 @@ +#!/usr/bin/python + +# +# This maintenance script updates the content of the "Orthanc" folder +# to match the latest version of the Orthanc source code. +# + +import multiprocessing +import os +import stat +import urllib2 + +TARGET = os.path.join(os.path.dirname(__file__), '..', 'Framework', 'Orthanc') +PLUGIN_SDK_VERSION = '1.0.0' +REPOSITORY = 'http://bitbucket.org/sjodogne/orthanc/raw' + +FILES = [ + 'Core/ChunkedBuffer.cpp', + 'Core/ChunkedBuffer.h', + 'Core/DicomFormat/DicomArray.cpp', + 'Core/DicomFormat/DicomArray.h', + 'Core/DicomFormat/DicomElement.h', + 'Core/DicomFormat/DicomMap.cpp', + 'Core/DicomFormat/DicomMap.h', + 'Core/DicomFormat/DicomTag.cpp', + 'Core/DicomFormat/DicomTag.h', + 'Core/DicomFormat/DicomValue.cpp', + 'Core/DicomFormat/DicomValue.h', + 'Core/Endianness.h', + 'Core/EnumerationDictionary.h', + 'Core/Enumerations.cpp', + 'Core/Enumerations.h', + 'Core/HttpClient.cpp', + 'Core/HttpClient.h', + 'Core/ICommand.h', + 'Core/IDynamicObject.h', + 'Core/Images/IImageWriter.cpp', + 'Core/Images/IImageWriter.h', + 'Core/Images/Image.cpp', + 'Core/Images/Image.h', + 'Core/Images/ImageAccessor.cpp', + 'Core/Images/ImageAccessor.h', + 'Core/Images/ImageBuffer.cpp', + 'Core/Images/ImageBuffer.h', + 'Core/Images/ImageProcessing.cpp', + 'Core/Images/ImageProcessing.h', + 'Core/Images/JpegErrorManager.cpp', + 'Core/Images/JpegErrorManager.h', + 'Core/Images/JpegReader.cpp', + 'Core/Images/JpegReader.h', + 'Core/Images/JpegWriter.cpp', + 'Core/Images/JpegWriter.h', + 'Core/Images/PngReader.cpp', + 'Core/Images/PngReader.h', + 'Core/Images/PngWriter.cpp', + 'Core/Images/PngWriter.h', + 'Core/Logging.cpp', + 'Core/Logging.h', + 'Core/MultiThreading/BagOfTasks.h', + 'Core/MultiThreading/BagOfTasksProcessor.cpp', + 'Core/MultiThreading/BagOfTasksProcessor.h', + 'Core/MultiThreading/SharedMessageQueue.cpp', + 'Core/MultiThreading/SharedMessageQueue.h', + 'Core/MultiThreading/Semaphore.cpp', + 'Core/MultiThreading/Semaphore.h', + 'Core/OrthancException.h', + 'Core/PrecompiledHeaders.h', + 'Core/Toolbox.cpp', + 'Core/Toolbox.h', + 'Core/Uuid.cpp', + 'Core/Uuid.h', + 'Core/WebServiceParameters.cpp', + 'Core/WebServiceParameters.h', + 'OrthancServer/FromDcmtkBridge.cpp', + 'OrthancServer/FromDcmtkBridge.h', + 'OrthancServer/PrecompiledHeadersServer.h', + 'OrthancServer/ServerEnumerations.cpp', + 'OrthancServer/ServerEnumerations.h', + 'OrthancServer/ToDcmtkBridge.cpp', + 'OrthancServer/ToDcmtkBridge.h', + 'Plugins/Engine/SharedLibrary.cpp', + 'Plugins/Engine/SharedLibrary.h', + 'Plugins/Samples/Common/ExportedSymbols.list', + 'Plugins/Samples/Common/OrthancPluginCppWrapper.cpp', + 'Plugins/Samples/Common/OrthancPluginCppWrapper.h', + 'Plugins/Samples/Common/VersionScript.map', + 'Resources/CMake/AutoGeneratedCode.cmake', + 'Resources/CMake/BoostConfiguration.cmake', + 'Resources/CMake/Compiler.cmake', + 'Resources/CMake/DcmtkConfiguration.cmake', + 'Resources/CMake/DownloadPackage.cmake', + 'Resources/CMake/JsonCppConfiguration.cmake', + 'Resources/CMake/LibCurlConfiguration.cmake', + 'Resources/CMake/LibJpegConfiguration.cmake', + 'Resources/CMake/LibPngConfiguration.cmake', + 'Resources/CMake/OpenSslConfiguration.cmake', + 'Resources/CMake/ZlibConfiguration.cmake', + 'Resources/EmbedResources.py', + 'Resources/MinGW-W64-Toolchain32.cmake', + 'Resources/MinGW-W64-Toolchain64.cmake', + 'Resources/MinGWToolchain.cmake', + 'Resources/Patches/dcmtk-3.6.0-mingw64.patch', + 'Resources/Patches/dcmtk-3.6.0-speed.patch', + 'Resources/Patches/dcmtk-3.6.1-speed.patch', + 'Resources/ThirdParty/VisualStudio/stdint.h', + 'Resources/ThirdParty/base64/base64.cpp', + 'Resources/ThirdParty/base64/base64.h', + 'Resources/WindowsResources.py', + 'Resources/WindowsResources.rc', +] + +SDK = [ + 'orthanc/OrthancCPlugin.h', +] + +EXE = [ + 'Resources/EmbedResources.py', + 'Resources/WindowsResources.py', +] + + +def Download(x): + branch = x[0] + source = x[1] + target = os.path.join(TARGET, x[2]) + print target + + try: + os.makedirs(os.path.dirname(target)) + except: + pass + + url = '%s/%s/%s' % (REPOSITORY, branch, source) + + try: + with open(target, 'w') as f: + f.write(urllib2.urlopen(url).read()) + except: + print('Cannot download file %s' % url) + raise + + +commands = [] + +for f in FILES: + commands.append([ 'default', f, f ]) + +for f in SDK: + commands.append([ + 'Orthanc-%s' % PLUGIN_SDK_VERSION, + 'Plugins/Include/%s' % f, + 'Sdk-%s/%s' % (PLUGIN_SDK_VERSION, f) + ]) + + +pool = multiprocessing.Pool(10) # simultaneous downloads +pool.map(Download, commands) + + +for exe in EXE: + path = os.path.join(TARGET, exe) + st = os.stat(path) + os.chmod(path, st.st_mode | stat.S_IEXEC) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/sRGB.txt Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,69 @@ +Notes about the sRGB.icc file +============================= + +The file "sRGB.icc" contains a default sRGB ICC color profile. It is +taken from a Debian distribution, from the following path: +/usr/share/color/icc/sRGB.icc + +Its MD5 hash is: 7fb30d688bf82d32a0e748daf3dba95d + +This profile comes from the "icc-profiles-free" package from Debian: +https://packages.debian.org/sid/icc-profiles-free + +The license terms of the profile are reproduced below, and are taken +from the source of the "icc-profiles-free" package: +http://anonscm.debian.org/cgit/collab-maint/icc-profiles.git/tree/debian/copyright + + +License +------- + +Files: LCMS* compat* Gray* Cine* ITUL* sRGB.icc +Copyright: Kai-Uwe Behrmann <www.behrmann.name> + Marti Maria <www.littlecms.com> + Photogamut <www.photogamut.org> + Graeme Gill <www.argyllcms.com> + ColorSolutions <www.basICColor.com> +License: Zlib + The zlib/libpng License + . + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + . + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + . + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + . + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + . + 3. This notice may not be removed or altered from any source + distribution. + . + NO WARRANTY + . + BECAUSE THE DATA IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY + FOR THE DATA, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN + OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES + PROVIDE THE DATA "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED + OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS + TO THE QUALITY AND PERFORMANCE OF THE DATA IS WITH YOU. SHOULD THE + DATA PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, + REPAIR OR CORRECTION. + . + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING + WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR + REDISTRIBUTE THE DATA AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, + INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING + OUT OF THE USE OR INABILITY TO USE THE DATA (INCLUDING BUT NOT LIMITED + TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY + YOU OR THIRD PARTIES OR A FAILURE OF THE DATA TO OPERATE WITH ANY OTHER + PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGES.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TODO Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,17 @@ +Orthanc for Whole-Slide Imaging +=============================== + + +------- +General +------- + +* Doxygen Documentation +* Support sparse tiling (both in encoder and decoder) + + +----------- +Performance +----------- + +* Larger cache with LRU recycling to improve viewer performance
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ViewerPlugin/CMakeLists.txt Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,203 @@ +cmake_minimum_required(VERSION 2.8) +project(OrthancWSIPlugin) + +set(ORTHANC_WSI_VERSION "mainline") + + +##################################################################### +## Parameters of the build +##################################################################### + +# Generic parameters +SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") +SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") + +# Advanced parameters to fine-tune linking against system libraries +SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost") +SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") +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_OPENJPEG ON CACHE BOOL "Use the system version of OpenJpeg") +SET(USE_SYSTEM_ZLIB ON CACHE BOOL "Use the system version of ZLib") +SET(USE_SYSTEM_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK") + +# Parameters related to OpenLayers +SET(USE_SYSTEM_OPENLAYERS OFF CACHE BOOL "Use the system version of OpenLayers") +SET(OPENLAYERS_CSS "" CACHE FILEPATH "Path to the system version of OpenLayers CSS") +SET(OPENLAYERS_JS "" CACHE FILEPATH "Path to the system version of OpenLayers JavaScript") + + +##################################################################### +## Configure mandatory third-party components +##################################################################### + +SET(ORTHANC_WSI_DIR ${CMAKE_CURRENT_LIST_DIR}/..) +SET(ORTHANC_ROOT ${ORTHANC_WSI_DIR}/Framework/Orthanc) + +SET(USE_OPENJPEG_JP2 ON) + +include(CheckIncludeFiles) +include(CheckIncludeFileCXX) +include(CheckLibraryExists) +include(FindPythonInterp) +include(FindPkgConfig) + +include(${ORTHANC_ROOT}/Resources/CMake/Compiler.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/AutoGeneratedCode.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) + +# Third-party components shipped with Orthanc +include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/LibJpegConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/LibPngConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/ZlibConfiguration.cmake) + +include(${ORTHANC_WSI_DIR}/Resources/CMake/OpenJpegConfiguration.cmake) + +add_definitions( + -DORTHANC_ENABLE_BASE64=0 + -DORTHANC_ENABLE_CURL=0 + -DORTHANC_ENABLE_DCMTK=0 + -DORTHANC_ENABLE_LOGGING=0 + -DORTHANC_ENABLE_MD5=0 + -DHAS_ORTHANC_EXCEPTION=1 + ) + + +##################################################################### +## Find the Orthanc SDK +##################################################################### + +if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK) + include_directories(${ORTHANC_ROOT}/Sdk-1.0.0) +else () + CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCPlugin.h HAVE_ORTHANC_H) + if (NOT HAVE_ORTHANC_H) + message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK") + endif() +endif() + + +##################################################################### +## Platform-specific configuration +##################################################################### + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") + link_libraries(rt) + +elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + SET(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lws2_32") + + execute_process( + COMMAND + ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py + ${ORTHANC_WSI_VERSION} "OrthancWSI" OrthancWSI.dll "Whole-slide imaging plugin for Orthanc" + ERROR_VARIABLE Failure + OUTPUT_FILE ${AUTOGENERATED_DIR}/Version.rc + ) + + if (Failure) + message(FATAL_ERROR "Error while computing the version information: ${Failure}") + endif() + + list(APPEND AUTOGENERATED_SOURCES ${AUTOGENERATED_DIR}/Version.rc) +endif() + +if (APPLE) + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework CoreFoundation") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -framework CoreFoundation") +endif() + + +##################################################################### +## Prepare OpenLayers +##################################################################### + +if (STATIC_BUILD OR NOT USE_SYSTEM_OPENLAYERS) + DownloadPackage( + "77e57aad873c2d4deea8bb1446e9b87a" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/WSI/openlayers-3.19.0-dist.zip" + "v3.19.0-dist") + + set(OPENLAYERS_RESOURCES + OPENLAYERS_CSS ${CMAKE_CURRENT_BINARY_DIR}/v3.19.0-dist/ol.css + OPENLAYERS_JS ${CMAKE_CURRENT_BINARY_DIR}/v3.19.0-dist/ol.js + ) + +else() + set(OPENLAYERS_RESOURCES + OPENLAYERS_CSS ${OPENLAYERS_CSS} + OPENLAYERS_JS ${OPENLAYERS_JS} + ) +endif() + +EmbedResources( + ${OPENLAYERS_RESOURCES} + ORTHANC_EXPLORER ${CMAKE_SOURCE_DIR}/OrthancExplorer.js + VIEWER_HTML ${CMAKE_SOURCE_DIR}/viewer.html + VIEWER_JS ${CMAKE_SOURCE_DIR}/viewer.js + ) + + +##################################################################### +## Create the plugin +##################################################################### + +add_library(OrthancWSI SHARED + Plugin.cpp + + ${ORTHANC_WSI_DIR}/Framework/DicomToolbox.cpp + ${ORTHANC_WSI_DIR}/Framework/Enumerations.cpp + ${ORTHANC_WSI_DIR}/Framework/ImageToolbox.cpp + ${ORTHANC_WSI_DIR}/Framework/Inputs/DicomPyramid.cpp + ${ORTHANC_WSI_DIR}/Framework/Inputs/DicomPyramidInstance.cpp + ${ORTHANC_WSI_DIR}/Framework/Inputs/DicomPyramidLevel.cpp + ${ORTHANC_WSI_DIR}/Framework/Inputs/PyramidWithRawTiles.cpp + ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Reader.cpp + ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Writer.cpp + ${ORTHANC_WSI_DIR}/Framework/Messaging/IOrthancConnection.cpp + ${ORTHANC_WSI_DIR}/Framework/Messaging/OrthancConnectionBase.cpp + ${ORTHANC_WSI_DIR}/Framework/Messaging/PluginOrthancConnection.cpp + + ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp + ${ORTHANC_ROOT}/Core/Enumerations.cpp + ${ORTHANC_ROOT}/Core/Images/IImageWriter.cpp + ${ORTHANC_ROOT}/Core/Images/Image.cpp + ${ORTHANC_ROOT}/Core/Images/ImageAccessor.cpp + ${ORTHANC_ROOT}/Core/Images/ImageBuffer.cpp + ${ORTHANC_ROOT}/Core/Images/ImageProcessing.cpp + ${ORTHANC_ROOT}/Core/Images/JpegErrorManager.cpp + ${ORTHANC_ROOT}/Core/Images/JpegReader.cpp + ${ORTHANC_ROOT}/Core/Images/JpegWriter.cpp + ${ORTHANC_ROOT}/Core/Images/PngReader.cpp + ${ORTHANC_ROOT}/Core/Images/PngWriter.cpp + ${ORTHANC_ROOT}/Core/Logging.cpp + ${ORTHANC_ROOT}/Core/MultiThreading/Semaphore.cpp + ${ORTHANC_ROOT}/Core/Toolbox.cpp + + ${AUTOGENERATED_SOURCES} + + # Mandatory components + ${BOOST_SOURCES} + ${JSONCPP_SOURCES} + ${ZLIB_SOURCES} + ${LIBPNG_SOURCES} + ${LIBJPEG_SOURCES} + ${OPENJPEG_SOURCES} + ) + +message("Setting the version of the library to ${ORTHANC_WSI_VERSION}") +add_definitions(-DORTHANC_WSI_VERSION="${ORTHANC_WSI_VERSION}") + +set_target_properties(OrthancWSI PROPERTIES + VERSION ${ORTHANC_WSI_VERSION} + SOVERSION ${ORTHANC_WSI_VERSION}) + +install( + TARGETS OrthancWSI + RUNTIME DESTINATION lib # Destination for Windows + LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux + )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ViewerPlugin/OrthancExplorer.js Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,34 @@ +$('#series').live('pagebeforeshow', function() { + var seriesId = $.mobile.pageData.uuid; + + $('#wsi-button').remove(); + + // Test whether this is a whole-slide image by check the SOP Class + // UID of one instance of the series + GetResource('/series/' + seriesId, function(series) { + GetResource('/instances/' + series['Instances'][0] + '/tags?simplify', function(instance) { + console.log(instance['SOPClassUID']); + + if (instance['SOPClassUID'] == '1.2.840.10008.5.1.4.1.1.77.1.6') { + + // This is a whole-slide image, register the button + var b = $('<a>') + .attr('id', 'wsi-button') + .attr('data-role', 'button') + .attr('href', '#') + .attr('data-icon', 'search') + .attr('data-theme', 'e') + .text('Whole-Slide Imaging Viewer') + .button(); + + b.insertAfter($('#series-info')); + b.click(function() { + if ($.mobile.pageData) { + window.open('../wsi/app/viewer.html?series=' + seriesId); + } + }); + + } + }); + }); +});
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ViewerPlugin/Plugin.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,390 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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 "../Framework/Inputs/DicomPyramid.h" +#include "../Framework/Jpeg2000Reader.h" +#include "../Framework/Messaging/PluginOrthancConnection.h" +#include "../Framework/Orthanc/Core/Images/PngWriter.h" +#include "../Framework/Orthanc/Core/MultiThreading/Semaphore.h" +#include "../Framework/Orthanc/Core/OrthancException.h" +#include "../Framework/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h" + +#include <EmbeddedResources.h> + +#include <cassert> + + + +namespace OrthancWSI +{ + // TODO Add LRU recycling policy + class DicomPyramidCache : public boost::noncopyable + { + private: + boost::mutex mutex_; + IOrthancConnection& orthanc_; + std::auto_ptr<DicomPyramid> pyramid_; + + DicomPyramid& GetPyramid(const std::string& seriesId) + { + // Mutex is assumed to be locked + + if (pyramid_.get() == NULL || + pyramid_->GetSeriesId() != seriesId) + { + pyramid_.reset(new DicomPyramid(orthanc_, seriesId)); + } + + if (pyramid_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + return *pyramid_; + } + + public: + DicomPyramidCache(IOrthancConnection& orthanc) : + orthanc_(orthanc) + { + } + + void Invalidate(const std::string& seriesId) + { + boost::mutex::scoped_lock lock(mutex_); + + if (pyramid_.get() != NULL && + pyramid_->GetSeriesId() == seriesId) + { + pyramid_.reset(NULL); + } + } + + class Locker : public boost::noncopyable + { + private: + boost::mutex::scoped_lock lock_; + DicomPyramid& pyramid_; + + public: + Locker(DicomPyramidCache& cache, + const std::string& seriesId) : + lock_(cache.mutex_), + pyramid_(cache.GetPyramid(seriesId)) + { + } + + DicomPyramid& GetPyramid() const + { + return pyramid_; + } + }; + }; +} + + +OrthancPluginContext* context_ = NULL; + +std::auto_ptr<OrthancWSI::PluginOrthancConnection> orthanc_; +std::auto_ptr<OrthancWSI::DicomPyramidCache> cache_; +std::auto_ptr<Orthanc::Semaphore> transcoderSemaphore_; + + +static bool DisplayPerformanceWarning() +{ + (void) DisplayPerformanceWarning; // Disable warning about unused function + OrthancPluginLogWarning(context_, "Performance warning in whole-slide imaging: " + "Non-release build, runtime debug assertions are turned on"); + return true; +} + + +void ServePyramid(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + std::string seriesId(request->groups[0]); + + char tmp[1024]; + sprintf(tmp, "Accessing whole-slide pyramid of series %s", seriesId.c_str()); + OrthancPluginLogInfo(context_, tmp); + + + OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId); + + unsigned int totalWidth = locker.GetPyramid().GetLevelWidth(0); + unsigned int totalHeight = locker.GetPyramid().GetLevelHeight(0); + + Json::Value resolutions = Json::arrayValue; + for (unsigned int i = 0; i < locker.GetPyramid().GetLevelCount(); i++) + { + resolutions.append(static_cast<float>(totalWidth) / + static_cast<float>(locker.GetPyramid().GetLevelWidth(i))); + } + + Json::Value result; + result["ID"] = seriesId; + result["TotalWidth"] = totalWidth; + result["TotalHeight"] = totalHeight; + result["TileWidth"] = locker.GetPyramid().GetTileWidth(); + result["TileHeight"] = locker.GetPyramid().GetTileHeight(); + result["Resolutions"] = resolutions; + + std::string s = result.toStyledString(); + OrthancPluginAnswerBuffer(context_, output, s.c_str(), s.size(), "application/json"); +} + + +void ServeTile(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + std::string seriesId(request->groups[0]); + int level = boost::lexical_cast<int>(request->groups[1]); + int tileY = boost::lexical_cast<int>(request->groups[3]); + int tileX = boost::lexical_cast<int>(request->groups[2]); + + char tmp[1024]; + sprintf(tmp, "Accessing tile in series %s: (%d,%d) at level %d", seriesId.c_str(), tileX, tileY, level); + OrthancPluginLogInfo(context_, tmp); + + if (level < 0 || + tileX < 0 || + tileY < 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + // Retrieve the raw tile from the WSI pyramid + OrthancWSI::ImageCompression compression; + Orthanc::PixelFormat format; + std::string tile; + unsigned int tileWidth, tileHeight; + + { + OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId); + + compression = locker.GetPyramid().GetImageCompression(); + format = locker.GetPyramid().GetPixelFormat(); + tileWidth = locker.GetPyramid().GetTileWidth(); + tileHeight = locker.GetPyramid().GetTileHeight(); + + if (!locker.GetPyramid().ReadRawTile(tile, + static_cast<unsigned int>(level), + static_cast<unsigned int>(tileX), + static_cast<unsigned int>(tileY))) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + } + + + // Test whether the tile is a JPEG image. In such a case, we can + // serve it as such, because any Web browser can handle JPEG + + if (compression == OrthancWSI::ImageCompression_Jpeg) + { + OrthancPluginAnswerBuffer(context_, output, tile.c_str(), tile.size(), "image/jpeg"); + return; // We're done + } + + + // The tile does not come from a DICOM-JPEG instance, we need to + // decompress the raw tile + std::auto_ptr<Orthanc::ImageAccessor> decoded; + + Orthanc::Semaphore::Locker locker(*transcoderSemaphore_); + + switch (compression) + { + case OrthancWSI::ImageCompression_Jpeg2000: + decoded.reset(new OrthancWSI::Jpeg2000Reader); + dynamic_cast<OrthancWSI::Jpeg2000Reader&>(*decoded).ReadFromMemory(tile); + break; + + case OrthancWSI::ImageCompression_None: + { + unsigned int bpp = Orthanc::GetBytesPerPixel(format); + if (bpp * tileWidth * tileHeight != tile.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + decoded.reset(new Orthanc::ImageAccessor); + decoded->AssignReadOnly(format, tileWidth, tileHeight, bpp * tileWidth, tile.c_str()); + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + + // This is a lossless frame (coming from a JPEG2000 or uncompressed + // DICOM instance), serve it as a PNG image so as to prevent lossy + // compression + + std::string png; + Orthanc::PngWriter writer; + writer.WriteToMemory(png, *decoded); + + OrthancPluginAnswerBuffer(context_, output, png.c_str(), png.size(), "image/png"); +} + + +OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char *resourceId) +{ + if (resourceType == OrthancPluginResourceType_Series && + changeType == OrthancPluginChangeType_NewChildInstance) + { + char tmp[1024]; + sprintf(tmp, "New instance has been added to series %s, invalidating it", resourceId); + OrthancPluginLogInfo(context_, tmp); + + cache_->Invalidate(resourceId); + } + + return OrthancPluginErrorCode_Success; +} + + + +void ServeFile(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + Orthanc::EmbeddedResources::FileResourceId resource; + + std::string f(request->groups[0]); + std::string mime; + + if (f == "viewer.html") + { + resource = Orthanc::EmbeddedResources::VIEWER_HTML; + mime = "text/html"; + } + else if (f == "viewer.js") + { + resource = Orthanc::EmbeddedResources::VIEWER_JS; + mime = "application/javascript"; + } + else if (f == "ol.js") + { + resource = Orthanc::EmbeddedResources::OPENLAYERS_JS; + mime = "application/javascript"; + } + else if (f == "ol.css") + { + resource = Orthanc::EmbeddedResources::OPENLAYERS_CSS; + mime = "text/css"; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + + std::string content; + Orthanc::EmbeddedResources::GetFileResource(content, resource); + + OrthancPluginAnswerBuffer(context_, output, content.c_str(), content.size(), mime.c_str()); +} + + + +extern "C" +{ + ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) + { + context_ = context; + assert(DisplayPerformanceWarning()); + + /* Check the version of the Orthanc core */ + if (OrthancPluginCheckVersion(context_) == 0) + { + char info[1024]; + sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", + context_->orthancVersion, + ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); + OrthancPluginLogError(context_, info); + return -1; + } + + // Limit the number of PNG transcoders to the number of available + // hardware threads (e.g. number of CPUs or cores or + // hyperthreading units) + unsigned int threads = boost::thread::hardware_concurrency(); + + if (threads <= 0) + { + threads = 1; + } + + transcoderSemaphore_.reset(new Orthanc::Semaphore(threads)); + + char info[1024]; + sprintf(info, "The whole-slide imaging plugin will use at most %d threads to transcode the tiles", threads); + OrthancPluginLogWarning(context_, info); + + OrthancPluginSetDescription(context, "Plugin to serve whole-slide microscopic images from Orthanc."); + + orthanc_.reset(new OrthancWSI::PluginOrthancConnection(context)); + cache_.reset(new OrthancWSI::DicomPyramidCache(*orthanc_)); + + OrthancPluginRegisterOnChangeCallback(context_, OnChangeCallback); + + OrthancPlugins::RegisterRestCallback<ServeFile>(context, "/wsi/app/(ol.css)", true); + OrthancPlugins::RegisterRestCallback<ServeFile>(context, "/wsi/app/(ol.js)", true); + OrthancPlugins::RegisterRestCallback<ServeFile>(context, "/wsi/app/(viewer.html)", true); + OrthancPlugins::RegisterRestCallback<ServeFile>(context, "/wsi/app/(viewer.js)", true); + OrthancPlugins::RegisterRestCallback<ServePyramid>(context, "/wsi/pyramids/([0-9a-f-]+)", true); + OrthancPlugins::RegisterRestCallback<ServeTile>(context, "/wsi/tiles/([0-9a-f-]+)/([0-9-]+)/([0-9-]+)/([0-9-]+)", true); + + // Extend the default Orthanc Explorer with custom JavaScript for WSI + std::string explorer; + Orthanc::EmbeddedResources::GetFileResource(explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER); + OrthancPluginExtendOrthancExplorer(context_, explorer.c_str()); + + return 0; + } + + + ORTHANC_PLUGINS_API void OrthancPluginFinalize() + { + cache_.reset(NULL); + orthanc_.reset(NULL); + transcoderSemaphore_.reset(NULL); + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetName() + { + return "wsi"; + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() + { + return ORTHANC_WSI_VERSION; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ViewerPlugin/viewer.html Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,32 @@ +<!DOCTYPE html> + +<html> + <head> + <title>Orthanc for Whole-Slide Imaging</title> + + <link rel="stylesheet" href="ol.css" type="text/css"> + + <!-- This is the version of jQuery that is used by Orthanc Explorer --> + <script src="../../app/libs/jquery.min.js"></script> + + <script src="ol.js"></script> + + <style> + #map { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: 1px solid #ccc; + margin-bottom: 10px; + } + </style> + + </head> + <body> + <div id="map" class="map"></div> + + <script src="viewer.js"></script> + </body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ViewerPlugin/viewer.js Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,125 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 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/>. + **/ + + +// For IE compatibility +if (!window.console) window.console = {}; +if (!window.console.log) window.console.log = function () { }; + + +// http://stackoverflow.com/a/21903119/881731 +function GetUrlParameter(sParam) +{ + var sPageURL = decodeURIComponent(window.location.search.substring(1)); + var sURLVariables = sPageURL.split('&'); + var sParameterName; + var i; + + for (i = 0; i < sURLVariables.length; i++) + { + sParameterName = sURLVariables[i].split('='); + + if (sParameterName[0] === sParam) + { + return sParameterName[1] === undefined ? '' : sParameterName[1]; + } + } + + return ''; +}; + + + +$(document).ready(function() { + var seriesId = GetUrlParameter('series'); + if (seriesId.length == 0) + { + alert('Error - No series ID specified!'); + } + else + { + $.ajax({ + url : '../pyramids/' + seriesId, + error: function() { + alert('Error - Cannot get the pyramid structure of series: ' + seriesId); + }, + success : function(series) { + var width = series['TotalWidth']; + var height = series['TotalHeight']; + var tileWidth = series['TileWidth']; + var tileHeight = series['TileHeight']; + var countLevels = series['Resolutions'].length; + + // Maps always need a projection, but Zoomify layers are not geo-referenced, and + // are only measured in pixels. So, we create a fake projection that the map + // can use to properly display the layer. + var proj = new ol.proj.Projection({ + code: 'pixel', + units: 'pixels', + extent: [0, 0, width, height] + }); + + var extent = [0, -height, width, 0]; + + // Disable the rotation of the map, and inertia while panning + // http://stackoverflow.com/a/25682186 + var interactions = ol.interaction.defaults({ + altShiftDragRotate : false, + pinchRotate : false, + dragPan: false + }).extend([ + new ol.interaction.DragPan({kinetic: false}) + ]); + + var layer = new ol.layer.Tile({ + extent: extent, + source: new ol.source.TileImage({ + projection: proj, + tileUrlFunction: function(tileCoord, pixelRatio, projection) { + return ('../tiles/' + seriesId + '/' + + (countLevels - 1 - tileCoord[0]) + '/' + tileCoord[1] + '/' + (-tileCoord[2] - 1)); + }, + tileGrid: new ol.tilegrid.TileGrid({ + extent: extent, + resolutions: series['Resolutions'].reverse(), + tileSize: [tileWidth, tileHeight] + }) + }), + wrapX: false, + projection: proj + }); + + + var map = new ol.Map({ + target: 'map', + layers: [ layer ], + view: new ol.View({ + projection: proj, + center: [width / 2, -height / 2], + zoom: 0, + minResolution: 1 // Do not interpelate over pixels + }), + interactions: interactions + }); + + map.getView().fit(extent, map.getSize()); + } + }); + } +});