changeset 104:4274441e21d4

rename Wado to WadoUri
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 25 Apr 2016 13:29:40 +0200 (2016-04-25)
parents cf5002bc63a8
children e1e2b6b2139d 7cb164a4b5f0
files CMakeLists.txt Orthanc/Core/ChunkedBuffer.cpp Orthanc/Core/Enumerations.cpp Orthanc/Core/Enumerations.h Orthanc/Core/Toolbox.cpp Orthanc/Core/Toolbox.h Orthanc/Resources/CMake/BoostConfiguration.cmake Orthanc/Resources/CMake/Compiler.cmake Orthanc/Resources/CMake/JsonCppConfiguration.cmake Plugin/Plugin.cpp Plugin/Wado.cpp Plugin/Wado.h Plugin/WadoUri.cpp Plugin/WadoUri.h
diffstat 14 files changed, 450 insertions(+), 332 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Thu Jan 28 17:09:48 2016 +0100
+++ b/CMakeLists.txt	Mon Apr 25 13:29:40 2016 +0200
@@ -124,9 +124,9 @@
   ${CMAKE_SOURCE_DIR}/Plugin/Plugin.cpp
   ${CMAKE_SOURCE_DIR}/Plugin/QidoRs.cpp
   ${CMAKE_SOURCE_DIR}/Plugin/StowRs.cpp
-  ${CMAKE_SOURCE_DIR}/Plugin/Wado.cpp
   ${CMAKE_SOURCE_DIR}/Plugin/WadoRs.cpp
   ${CMAKE_SOURCE_DIR}/Plugin/WadoRsRetrieveFrames.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/WadoUri.cpp
   ${AUTOGENERATED_SOURCES}
   )
 
--- a/Orthanc/Core/ChunkedBuffer.cpp	Thu Jan 28 17:09:48 2016 +0100
+++ b/Orthanc/Core/ChunkedBuffer.cpp	Mon Apr 25 13:29:40 2016 +0200
@@ -95,5 +95,6 @@
     }
 
     chunks_.clear();
+    numBytes_ = 0;
   }
 }
--- a/Orthanc/Core/Enumerations.cpp	Thu Jan 28 17:09:48 2016 +0100
+++ b/Orthanc/Core/Enumerations.cpp	Mon Apr 25 13:29:40 2016 +0200
@@ -212,10 +212,10 @@
         return "The specified path does not point to a directory";
 
       case ErrorCode_HttpPortInUse:
-        return "The TCP port of the HTTP server is already in use";
+        return "The TCP port of the HTTP server is privileged or already in use";
 
       case ErrorCode_DicomPortInUse:
-        return "The TCP port of the DICOM server is already in use";
+        return "The TCP port of the DICOM server is privileged or already in use";
 
       case ErrorCode_BadHttpStatusInRest:
         return "This HTTP status is not allowed in a REST API";
@@ -328,6 +328,9 @@
       case ErrorCode_NoWorklistHandler:
         return "No request handler factory for DICOM C-Find Modality SCP";
 
+      case ErrorCode_AlreadyExistingTag:
+        return "Cannot override the value of a tag that already exists";
+
       default:
         if (error >= ErrorCode_START_PLUGINS)
         {
@@ -718,6 +721,31 @@
   }
 
 
+  const char* EnumerationToString(PixelFormat format)
+  {
+    switch (format)
+    {
+      case PixelFormat_RGB24:
+        return "RGB24";
+
+      case PixelFormat_RGBA32:
+        return "RGBA32";
+
+      case PixelFormat_Grayscale8:
+        return "Grayscale (unsigned 8bpp)";
+
+      case PixelFormat_Grayscale16:
+        return "Grayscale (unsigned 16bpp)";
+
+      case PixelFormat_SignedGrayscale16:
+        return "Grayscale (signed 16bpp)";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
   Encoding StringToEncoding(const char* encoding)
   {
     std::string s(encoding);
--- a/Orthanc/Core/Enumerations.h	Thu Jan 28 17:09:48 2016 +0100
+++ b/Orthanc/Core/Enumerations.h	Mon Apr 25 13:29:40 2016 +0200
@@ -100,8 +100,8 @@
     ErrorCode_DirectoryOverFile = 2000    /*!< The directory to be created is already occupied by a regular file */,
     ErrorCode_FileStorageCannotWrite = 2001    /*!< Unable to create a subdirectory or a file in the file storage */,
     ErrorCode_DirectoryExpected = 2002    /*!< The specified path does not point to a directory */,
-    ErrorCode_HttpPortInUse = 2003    /*!< The TCP port of the HTTP server is already in use */,
-    ErrorCode_DicomPortInUse = 2004    /*!< The TCP port of the DICOM server is already in use */,
+    ErrorCode_HttpPortInUse = 2003    /*!< The TCP port of the HTTP server is privileged or already in use */,
+    ErrorCode_DicomPortInUse = 2004    /*!< The TCP port of the DICOM server is privileged or already in use */,
     ErrorCode_BadHttpStatusInRest = 2005    /*!< This HTTP status is not allowed in a REST API */,
     ErrorCode_RegularFileExpected = 2006    /*!< The specified path does not point to a regular file */,
     ErrorCode_PathToExecutable = 2007    /*!< Unable to get the path to the executable */,
@@ -139,6 +139,7 @@
     ErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
     ErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
     ErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
+    ErrorCode_AlreadyExistingTag = 2042    /*!< Cannot override the value of a tag that already exists */,
     ErrorCode_START_PLUGINS = 1000000
   };
 
@@ -444,6 +445,8 @@
 
   const char* EnumerationToString(RequestOrigin origin);
 
+  const char* EnumerationToString(PixelFormat format);
+
   Encoding StringToEncoding(const char* encoding);
 
   ResourceType StringToResourceType(const char* type);
--- a/Orthanc/Core/Toolbox.cpp	Thu Jan 28 17:09:48 2016 +0100
+++ b/Orthanc/Core/Toolbox.cpp	Mon Apr 25 13:29:40 2016 +0200
@@ -67,7 +67,7 @@
 #include <limits.h>      /* PATH_MAX */
 #endif
 
-#if defined(__linux) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
+#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
 #include <limits.h>      /* PATH_MAX */
 #include <signal.h>
 #include <unistd.h>
@@ -132,7 +132,7 @@
   {
 #if defined(_WIN32)
     ::Sleep(static_cast<DWORD>(microSeconds / static_cast<uint64_t>(1000)));
-#elif defined(__linux) || defined(__APPLE__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
+#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
     usleep(microSeconds);
 #else
 #error Support your platform here
@@ -206,6 +206,17 @@
   }
 
 
+  static std::streamsize GetStreamSize(std::istream& f)
+  {
+    // http://www.cplusplus.com/reference/iostream/istream/tellg/
+    f.seekg(0, std::ios::end);
+    std::streamsize size = f.tellg();
+    f.seekg(0, std::ios::beg);
+
+    return size;
+  }
+
+
   void Toolbox::ReadFile(std::string& content,
                          const std::string& path) 
   {
@@ -222,11 +233,7 @@
       throw OrthancException(ErrorCode_InexistentFile);
     }
 
-    // http://www.cplusplus.com/reference/iostream/istream/tellg/
-    f.seekg(0, std::ios::end);
-    std::streamsize size = f.tellg();
-    f.seekg(0, std::ios::beg);
-
+    std::streamsize size = GetStreamSize(f);
     content.resize(size);
     if (size != 0)
     {
@@ -237,6 +244,51 @@
   }
 
 
+  bool Toolbox::ReadHeader(std::string& header,
+                           const std::string& path,
+                           size_t headerSize)
+  {
+    if (!IsRegularFile(path))
+    {
+      LOG(ERROR) << std::string("The path does not point to a regular file: ") << path;
+      throw OrthancException(ErrorCode_RegularFileExpected);
+    }
+
+    boost::filesystem::ifstream f;
+    f.open(path, std::ifstream::in | std::ifstream::binary);
+    if (!f.good())
+    {
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+
+    bool full = true;
+
+    {
+      std::streamsize size = GetStreamSize(f);
+      if (size <= 0)
+      {
+        headerSize = 0;
+        full = false;
+      }
+      else if (static_cast<size_t>(size) < headerSize)
+      {
+        headerSize = size;  // Truncate to the size of the file
+        full = false;
+      }
+    }
+
+    header.resize(headerSize);
+    if (headerSize != 0)
+    {
+      f.read(reinterpret_cast<char*>(&header[0]), headerSize);
+    }
+
+    f.close();
+
+    return full;
+  }
+
+
   void Toolbox::WriteFile(const void* content,
                           size_t size,
                           const std::string& path)
@@ -535,7 +587,7 @@
 
 
 #  if BOOST_HAS_REGEX == 1
-  void Toolbox::DecodeDataUriScheme(std::string& mime,
+  bool Toolbox::DecodeDataUriScheme(std::string& mime,
                                     std::string& content,
                                     const std::string& source)
   {
@@ -547,10 +599,11 @@
     {
       mime = what[1];
       DecodeBase64(content, what[2]);
+      return true;
     }
     else
     {
-      throw OrthancException(ErrorCode_BadFileFormat);
+      return false;
     }
   }
 #  endif
@@ -577,7 +630,7 @@
     return std::string(&buffer[0]);
   }
 
-#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
+#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
   static std::string GetPathToExecutableInternal()
   {
     std::vector<char> buffer(PATH_MAX + 1);
--- a/Orthanc/Core/Toolbox.h	Thu Jan 28 17:09:48 2016 +0100
+++ b/Orthanc/Core/Toolbox.h	Mon Apr 25 13:29:40 2016 +0200
@@ -66,6 +66,10 @@
     void ReadFile(std::string& content,
                   const std::string& path);
 
+    bool ReadHeader(std::string& header,
+                    const std::string& path,
+                    size_t headerSize);
+
     void WriteFile(const std::string& content,
                    const std::string& path);
 
@@ -123,7 +127,7 @@
                       const std::string& data);
 
 #  if BOOST_HAS_REGEX == 1
-    void DecodeDataUriScheme(std::string& mime,
+    bool DecodeDataUriScheme(std::string& mime,
                              std::string& content,
                              const std::string& source);
 #  endif
--- a/Orthanc/Resources/CMake/BoostConfiguration.cmake	Thu Jan 28 17:09:48 2016 +0100
+++ b/Orthanc/Resources/CMake/BoostConfiguration.cmake	Mon Apr 25 13:29:40 2016 +0200
@@ -8,7 +8,7 @@
   #set(Boost_USE_STATIC_LIBS ON)
 
   find_package(Boost
-    COMPONENTS filesystem thread system date_time regex locale)
+    COMPONENTS filesystem thread system date_time regex locale ${ORTHANC_BOOST_COMPONENTS})
 
   if (NOT Boost_FOUND)
     message(FATAL_ERROR "Unable to locate Boost on this system")
@@ -39,10 +39,10 @@
 
 
 if (BOOST_STATIC)
-  # Parameters for Boost 1.59.0
-  set(BOOST_NAME boost_1_59_0)
-  set(BOOST_BCP_SUFFIX bcpdigest-0.9.5)
-  set(BOOST_MD5 "08abb7cdbea0b380f9ab0d5cce476f12")
+  # Parameters for Boost 1.60.0
+  set(BOOST_NAME boost_1_60_0)
+  set(BOOST_BCP_SUFFIX bcpdigest-1.0.1)
+  set(BOOST_MD5 "0646971514a1e012fbe382c5662a8605")
   set(BOOST_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz")
   set(BOOST_FILESYSTEM_SOURCES_DIR "${BOOST_NAME}/libs/filesystem/src") 
   set(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME})
--- a/Orthanc/Resources/CMake/Compiler.cmake	Thu Jan 28 17:09:48 2016 +0100
+++ b/Orthanc/Resources/CMake/Compiler.cmake	Mon Apr 25 13:29:40 2016 +0200
@@ -10,7 +10,7 @@
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wno-long-long -Wno-implicit-function-declaration")  
   # --std=c99 makes libcurl not to compile
   # -pedantic gives a lot of warnings on OpenSSL 
-  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wno-long-long -Wno-variadic-macros")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -Wno-variadic-macros")
 
   if (CMAKE_CROSSCOMPILING)
     # http://stackoverflow.com/a/3543845/881731
@@ -73,6 +73,11 @@
     link_libraries(dl)
   endif()
 
+  CHECK_INCLUDE_FILES(uuid/uuid.h HAVE_UUID_H)
+  if (NOT HAVE_UUID_H)
+    message(FATAL_ERROR "Please install the uuid-dev package")
+  endif()
+
 elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
   if (MSVC)
     message("MSVC compiler version = " ${MSVC_VERSION} "\n")
@@ -97,6 +102,11 @@
   link_libraries(rpcrt4 ws2_32)
 
   if (CMAKE_COMPILER_IS_GNUCXX)
+    # Some additional C/C++ compiler flags for MinGW
+    SET(MINGW_NO_WARNINGS "-Wno-unused-function -Wno-unused-variable")
+    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${MINGW_NO_WARNINGS} -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast")
+    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MINGW_NO_WARNINGS}")
+
     # This is a patch for MinGW64
     SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++")
     SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++")
@@ -120,6 +130,11 @@
     )
   link_libraries(iconv)
 
+  CHECK_INCLUDE_FILES(uuid/uuid.h HAVE_UUID_H)
+  if (NOT HAVE_UUID_H)
+    message(FATAL_ERROR "Please install the uuid-dev package")
+  endif()
+
 endif()
 
 
@@ -139,17 +154,6 @@
 endif()
 
 
-if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
-  CHECK_INCLUDE_FILES(rpc.h HAVE_UUID_H)
-else()
-  CHECK_INCLUDE_FILES(uuid/uuid.h HAVE_UUID_H)
-endif()
-
-if (NOT HAVE_UUID_H)
-  message(FATAL_ERROR "Please install the uuid-dev package")
-endif()
-
-
 if (STATIC_BUILD)
   add_definitions(-DORTHANC_STATIC=1)
 else()
--- a/Orthanc/Resources/CMake/JsonCppConfiguration.cmake	Thu Jan 28 17:09:48 2016 +0100
+++ b/Orthanc/Resources/CMake/JsonCppConfiguration.cmake	Mon Apr 25 13:29:40 2016 +0200
@@ -32,4 +32,29 @@
     message(FATAL_ERROR "Please install the libjsoncpp-dev package")
   endif()
 
+  # Switch to the C++11 standard if the version of JsonCpp is 1.y.z
+  if (EXISTS ${JSONCPP_INCLUDE_DIR}/json/version.h)
+    file(STRINGS
+      "${JSONCPP_INCLUDE_DIR}/json/version.h" 
+      JSONCPP_VERSION_MAJOR1 REGEX
+      ".*define JSONCPP_VERSION_MAJOR.*")
+
+    if (NOT JSONCPP_VERSION_MAJOR1)
+      message(FATAL_ERROR "Unable to extract the major version of JsonCpp")
+    endif()
+    
+    string(REGEX REPLACE
+      ".*JSONCPP_VERSION_MAJOR.*([0-9]+)$" "\\1" 
+      JSONCPP_VERSION_MAJOR ${JSONCPP_VERSION_MAJOR1})
+    message("JsonCpp major version: ${JSONCPP_VERSION_MAJOR}")
+
+    if (CMAKE_COMPILER_IS_GNUCXX AND 
+        JSONCPP_VERSION_MAJOR GREATER 0)
+      message("Switching to C++11 standard, as version of JsonCpp is >= 1.0.0")
+      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wno-deprecated-declarations")
+    endif()
+  else()
+    message("Unable to detect the major version of JsonCpp, assuming < 1.0.0")
+  endif()
+
 endif()
--- a/Plugin/Plugin.cpp	Thu Jan 28 17:09:48 2016 +0100
+++ b/Plugin/Plugin.cpp	Mon Apr 25 13:29:40 2016 +0200
@@ -23,7 +23,7 @@
 #include "QidoRs.h"
 #include "StowRs.h"
 #include "WadoRs.h"
-#include "Wado.h"
+#include "WadoUri.h"
 #include "Configuration.h"
 
 
@@ -222,7 +222,7 @@
       std::string message = "URI to the WADO API: " + wado;
       OrthancPluginLogWarning(context_, message.c_str());
 
-      OrthancPluginRegisterRestCallback(context_, wado.c_str(), Protect<WadoCallback>);
+      OrthancPluginRegisterRestCallback(context_, wado.c_str(), Protect<WadoUriCallback>);
     }
     else
     {
--- a/Plugin/Wado.cpp	Thu Jan 28 17:09:48 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,270 +0,0 @@
-/**
- * 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 "Wado.h"
-#include "Plugin.h"
-
-#include "../Orthanc/Core/OrthancException.h"
-#include "Configuration.h"
-
-#include <string>
-
-static bool MapWadoToOrthancIdentifier(std::string& orthanc,
-                                       char* (*func) (OrthancPluginContext*, const char*),
-                                       const std::string& dicom)
-{
-  char* tmp = func(context_, dicom.c_str());
-
-  if (tmp)
-  {
-    orthanc = tmp;
-    OrthancPluginFreeString(context_, tmp);
-    return true;
-  }
-  else
-  {
-    return false;
-  }
-}
-
-
-static bool LocateInstance(std::string& instance,
-                           std::string& contentType,
-                           const OrthancPluginHttpRequest* request)
-{
-  std::string requestType, studyUid, seriesUid, objectUid;
-
-  for (uint32_t i = 0; i < request->getCount; i++)
-  {
-    std::string key(request->getKeys[i]);
-    std::string value(request->getValues[i]);
-
-    if (key == "studyUID")
-    {
-      studyUid = value;
-    }
-    else if (key == "seriesUID")
-    {
-      seriesUid = value;
-    }
-    else if (key == "objectUID")  // In WADO, "objectUID" corresponds to "SOPInstanceUID"
-    {
-      objectUid = value;
-    }
-    else if (key == "requestType")
-    {
-      requestType = value;
-    }
-    else if (key == "contentType")
-    {
-      contentType = value;
-    }
-  }
-
-  if (requestType != "WADO")
-  {
-    std::string msg = "WADO: Invalid requestType: \"" + requestType + "\"";
-    OrthancPluginLogError(context_, msg.c_str());
-    return false;
-  }
-
-  if (objectUid.empty())
-  {
-    OrthancPluginLogError(context_, "WADO: No SOPInstanceUID provided");
-    return false;
-  }
-
-  if (!MapWadoToOrthancIdentifier(instance, OrthancPluginLookupInstance, objectUid))
-  {
-    std::string msg = "WADO: No such SOPInstanceUID in Orthanc: \"" + objectUid + "\"";
-    OrthancPluginLogError(context_, msg.c_str());
-    return false;
-  }
-
-  /**
-   * Below are only sanity checks to ensure that the possibly provided
-   * "seriesUID" and "studyUID" match that of the provided instance.
-   **/
-
-  if (!seriesUid.empty())
-  {
-    std::string series;
-    if (!MapWadoToOrthancIdentifier(series, OrthancPluginLookupSeries, seriesUid))
-    {
-      std::string msg = "WADO: No such SeriesInstanceUID in Orthanc: \"" + seriesUid + "\"";
-      OrthancPluginLogError(context_, msg.c_str());
-      return false;
-    }
-    else
-    {
-      Json::Value info;
-      if (!OrthancPlugins::RestApiGetJson(info, context_, "/instances/" + instance + "/series") ||
-          info["MainDicomTags"]["SeriesInstanceUID"] != seriesUid)
-      {
-        std::string msg = "WADO: Instance " + objectUid + " does not belong to series " + seriesUid;
-        OrthancPluginLogError(context_, msg.c_str());
-        return false;
-      }
-    }
-  }
-  
-  if (!studyUid.empty())
-  {
-    std::string study;
-    if (!MapWadoToOrthancIdentifier(study, OrthancPluginLookupStudy, studyUid))
-    {
-      std::string msg = "WADO: No such StudyInstanceUID in Orthanc: \"" + studyUid + "\"";
-      OrthancPluginLogError(context_, msg.c_str());
-      return false;
-    }
-    else
-    {
-      Json::Value info;
-      if (!OrthancPlugins::RestApiGetJson(info, context_, "/instances/" + instance + "/study") ||
-          info["MainDicomTags"]["StudyInstanceUID"] != studyUid)
-      {
-        std::string msg = "WADO: Instance " + objectUid + " does not belong to study " + studyUid;
-        OrthancPluginLogError(context_, msg.c_str());
-        return false;
-      }
-    }
-  }
-  
-  return true;
-}
-
-
-static void AnswerDicom(OrthancPluginRestOutput* output,
-                        const std::string& instance)
-{
-  std::string uri = "/instances/" + instance + "/file";
-
-  std::string dicom;
-  if (OrthancPlugins::RestApiGetString(dicom, context_, uri))
-  {
-    OrthancPluginAnswerBuffer(context_, output, dicom.c_str(), dicom.size(), "application/dicom");
-  }
-  else
-  {
-    std::string msg = "WADO: Unable to retrieve DICOM file from " + uri;
-    OrthancPluginLogError(context_, msg.c_str());
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
-  }
-}
-
-
-static bool RetrievePngPreview(std::string& png,
-                               const std::string& instance)
-{
-  std::string uri = "/instances/" + instance + "/preview";
-
-  if (OrthancPlugins::RestApiGetString(png, context_, uri, true))
-  {
-    return true;
-  }
-  else
-  {
-    std::string msg = "WADO: Unable to generate a preview image for " + uri;
-    OrthancPluginLogError(context_, msg.c_str());
-    return false;
-  }
-}
-
-
-static void AnswerPngPreview(OrthancPluginRestOutput* output,
-                             const std::string& instance)
-{
-  std::string png;
-  if (RetrievePngPreview(png, instance))
-  {
-    OrthancPluginAnswerBuffer(context_, output, png.c_str(), png.size(), "image/png");
-  }
-  else
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
-  }
-}
-
-
-static void AnswerJpegPreview(OrthancPluginRestOutput* output,
-                              const std::string& instance)
-{
-  // Retrieve the preview in the PNG format
-  std::string png;
-  if (!RetrievePngPreview(png, instance))
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
-  }
-
-  // Decode the PNG file
-  OrthancPluginImage* image = OrthancPluginUncompressImage(
-    context_, png.c_str(), png.size(), OrthancPluginImageFormat_Png);
-
-  // Convert to JPEG
-  OrthancPluginCompressAndAnswerJpegImage(
-    context_, output, 
-    OrthancPluginGetImagePixelFormat(context_, image),
-    OrthancPluginGetImageWidth(context_, image),
-    OrthancPluginGetImageHeight(context_, image),
-    OrthancPluginGetImagePitch(context_, image),
-    OrthancPluginGetImageBuffer(context_, image), 
-    90 /*quality*/);
-
-  OrthancPluginFreeImage(context_, image);
-}
-
-
-void WadoCallback(OrthancPluginRestOutput* output,
-                  const char* url,
-                  const OrthancPluginHttpRequest* request)
-{
-  if (request->method != OrthancPluginHttpMethod_Get)
-  {
-    OrthancPluginSendMethodNotAllowed(context_, output, "GET");
-    return;
-  }
-
-  std::string instance;
-  std::string contentType = "image/jpg";  // By default, JPEG image will be returned
-  if (!LocateInstance(instance, contentType, request))
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
-  }
-
-  if (contentType == "application/dicom")
-  {
-    AnswerDicom(output, instance);
-  }
-  else if (contentType == "image/png")
-  {
-    AnswerPngPreview(output, instance);
-  }
-  else if (contentType == "image/jpeg" ||
-           contentType == "image/jpg")
-  {
-    AnswerJpegPreview(output, instance);
-  }
-  else
-  {
-    std::string msg = "WADO: Unsupported content type: \"" + contentType + "\"";
-    OrthancPluginLogError(context_, msg.c_str());
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest);
-  }
-}
--- a/Plugin/Wado.h	Thu Jan 28 17:09:48 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-/**
- * 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 "Configuration.h"
-
-void WadoCallback(OrthancPluginRestOutput* output,
-                  const char* url,
-                  const OrthancPluginHttpRequest* request);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/WadoUri.cpp	Mon Apr 25 13:29:40 2016 +0200
@@ -0,0 +1,270 @@
+/**
+ * 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 "WadoUri.h"
+#include "Plugin.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+#include "Configuration.h"
+
+#include <string>
+
+static bool MapWadoToOrthancIdentifier(std::string& orthanc,
+                                       char* (*func) (OrthancPluginContext*, const char*),
+                                       const std::string& dicom)
+{
+  char* tmp = func(context_, dicom.c_str());
+
+  if (tmp)
+  {
+    orthanc = tmp;
+    OrthancPluginFreeString(context_, tmp);
+    return true;
+  }
+  else
+  {
+    return false;
+  }
+}
+
+
+static bool LocateInstance(std::string& instance,
+                           std::string& contentType,
+                           const OrthancPluginHttpRequest* request)
+{
+  std::string requestType, studyUid, seriesUid, objectUid;
+
+  for (uint32_t i = 0; i < request->getCount; i++)
+  {
+    std::string key(request->getKeys[i]);
+    std::string value(request->getValues[i]);
+
+    if (key == "studyUID")
+    {
+      studyUid = value;
+    }
+    else if (key == "seriesUID")
+    {
+      seriesUid = value;
+    }
+    else if (key == "objectUID")  // In WADO-URI, "objectUID" corresponds to "SOPInstanceUID"
+    {
+      objectUid = value;
+    }
+    else if (key == "requestType")
+    {
+      requestType = value;
+    }
+    else if (key == "contentType")
+    {
+      contentType = value;
+    }
+  }
+
+  if (requestType != "WADO")
+  {
+    std::string msg = "WADO-URI: Invalid requestType: \"" + requestType + "\"";
+    OrthancPluginLogError(context_, msg.c_str());
+    return false;
+  }
+
+  if (objectUid.empty())
+  {
+    OrthancPluginLogError(context_, "WADO-URI: No SOPInstanceUID provided");
+    return false;
+  }
+
+  if (!MapWadoToOrthancIdentifier(instance, OrthancPluginLookupInstance, objectUid))
+  {
+    std::string msg = "WADO-URI: No such SOPInstanceUID in Orthanc: \"" + objectUid + "\"";
+    OrthancPluginLogError(context_, msg.c_str());
+    return false;
+  }
+
+  /**
+   * Below are only sanity checks to ensure that the possibly provided
+   * "seriesUID" and "studyUID" match that of the provided instance.
+   **/
+
+  if (!seriesUid.empty())
+  {
+    std::string series;
+    if (!MapWadoToOrthancIdentifier(series, OrthancPluginLookupSeries, seriesUid))
+    {
+      std::string msg = "WADO-URI: No such SeriesInstanceUID in Orthanc: \"" + seriesUid + "\"";
+      OrthancPluginLogError(context_, msg.c_str());
+      return false;
+    }
+    else
+    {
+      Json::Value info;
+      if (!OrthancPlugins::RestApiGetJson(info, context_, "/instances/" + instance + "/series") ||
+          info["MainDicomTags"]["SeriesInstanceUID"] != seriesUid)
+      {
+        std::string msg = "WADO-URI: Instance " + objectUid + " does not belong to series " + seriesUid;
+        OrthancPluginLogError(context_, msg.c_str());
+        return false;
+      }
+    }
+  }
+  
+  if (!studyUid.empty())
+  {
+    std::string study;
+    if (!MapWadoToOrthancIdentifier(study, OrthancPluginLookupStudy, studyUid))
+    {
+      std::string msg = "WADO-URI: No such StudyInstanceUID in Orthanc: \"" + studyUid + "\"";
+      OrthancPluginLogError(context_, msg.c_str());
+      return false;
+    }
+    else
+    {
+      Json::Value info;
+      if (!OrthancPlugins::RestApiGetJson(info, context_, "/instances/" + instance + "/study") ||
+          info["MainDicomTags"]["StudyInstanceUID"] != studyUid)
+      {
+        std::string msg = "WADO-URI: Instance " + objectUid + " does not belong to study " + studyUid;
+        OrthancPluginLogError(context_, msg.c_str());
+        return false;
+      }
+    }
+  }
+  
+  return true;
+}
+
+
+static void AnswerDicom(OrthancPluginRestOutput* output,
+                        const std::string& instance)
+{
+  std::string uri = "/instances/" + instance + "/file";
+
+  std::string dicom;
+  if (OrthancPlugins::RestApiGetString(dicom, context_, uri))
+  {
+    OrthancPluginAnswerBuffer(context_, output, dicom.c_str(), dicom.size(), "application/dicom");
+  }
+  else
+  {
+    std::string msg = "WADO-URI: Unable to retrieve DICOM file from " + uri;
+    OrthancPluginLogError(context_, msg.c_str());
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+  }
+}
+
+
+static bool RetrievePngPreview(std::string& png,
+                               const std::string& instance)
+{
+  std::string uri = "/instances/" + instance + "/preview";
+
+  if (OrthancPlugins::RestApiGetString(png, context_, uri, true))
+  {
+    return true;
+  }
+  else
+  {
+    std::string msg = "WADO-URI: Unable to generate a preview image for " + uri;
+    OrthancPluginLogError(context_, msg.c_str());
+    return false;
+  }
+}
+
+
+static void AnswerPngPreview(OrthancPluginRestOutput* output,
+                             const std::string& instance)
+{
+  std::string png;
+  if (RetrievePngPreview(png, instance))
+  {
+    OrthancPluginAnswerBuffer(context_, output, png.c_str(), png.size(), "image/png");
+  }
+  else
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+  }
+}
+
+
+static void AnswerJpegPreview(OrthancPluginRestOutput* output,
+                              const std::string& instance)
+{
+  // Retrieve the preview in the PNG format
+  std::string png;
+  if (!RetrievePngPreview(png, instance))
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+  }
+
+  // Decode the PNG file
+  OrthancPluginImage* image = OrthancPluginUncompressImage(
+    context_, png.c_str(), png.size(), OrthancPluginImageFormat_Png);
+
+  // Convert to JPEG
+  OrthancPluginCompressAndAnswerJpegImage(
+    context_, output, 
+    OrthancPluginGetImagePixelFormat(context_, image),
+    OrthancPluginGetImageWidth(context_, image),
+    OrthancPluginGetImageHeight(context_, image),
+    OrthancPluginGetImagePitch(context_, image),
+    OrthancPluginGetImageBuffer(context_, image), 
+    90 /*quality*/);
+
+  OrthancPluginFreeImage(context_, image);
+}
+
+
+void WadoUriCallback(OrthancPluginRestOutput* output,
+                     const char* url,
+                     const OrthancPluginHttpRequest* request)
+{
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context_, output, "GET");
+    return;
+  }
+
+  std::string instance;
+  std::string contentType = "image/jpg";  // By default, JPEG image will be returned
+  if (!LocateInstance(instance, contentType, request))
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+  }
+
+  if (contentType == "application/dicom")
+  {
+    AnswerDicom(output, instance);
+  }
+  else if (contentType == "image/png")
+  {
+    AnswerPngPreview(output, instance);
+  }
+  else if (contentType == "image/jpeg" ||
+           contentType == "image/jpg")
+  {
+    AnswerJpegPreview(output, instance);
+  }
+  else
+  {
+    std::string msg = "WADO-URI: Unsupported content type: \"" + contentType + "\"";
+    OrthancPluginLogError(context_, msg.c_str());
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/WadoUri.h	Mon Apr 25 13:29:40 2016 +0200
@@ -0,0 +1,27 @@
+/**
+ * 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 "Configuration.h"
+
+void WadoUriCallback(OrthancPluginRestOutput* output,
+                     const char* url,
+                     const OrthancPluginHttpRequest* request);