changeset 1857:4f2386d0f326 dcmtk-3.6.1

integration mainline->dcmtk-3.6.1
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 02 Dec 2015 09:52:56 +0100
parents c131566b8252 (current diff) 36ab170733d6 (diff)
children 483f26479743
files Plugins/Samples/GdcmDecoding/CMakeLists.txt Plugins/Samples/GdcmDecoding/OrthancContext.cpp Plugins/Samples/GdcmDecoding/OrthancContext.h Plugins/Samples/GdcmDecoding/Plugin.cpp Resources/CMake/DcmtkConfiguration.cmake
diffstat 126 files changed, 4919 insertions(+), 1325 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Wed Nov 18 10:16:21 2015 +0100
+++ b/CMakeLists.txt	Wed Dec 02 09:52:56 2015 +0100
@@ -31,6 +31,7 @@
 SET(ENABLE_JPEG_LOSSLESS ON CACHE BOOL "Enable JPEG-LS (Lossless) decompression")
 SET(ENABLE_PLUGINS ON CACHE BOOL "Enable plugins")
 SET(BUILD_SERVE_FOLDERS ON CACHE BOOL "Build the ServeFolders plugin")
+SET(BUILD_MODALITY_WORKLISTS ON CACHE BOOL "Build the sample plugin to serve modality worklists")
 
 # Advanced parameters to fine-tune linking against system libraries
 SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp")
@@ -179,6 +180,8 @@
   OrthancServer/OrthancRestApi/OrthancRestSystem.cpp
   OrthancServer/ParsedDicomFile.cpp
   OrthancServer/QueryRetrieveHandler.cpp
+  OrthancServer/Search/HierarchicalMatcher.cpp
+  OrthancServer/Search/IFindConstraint.cpp
   OrthancServer/Search/LookupIdentifierQuery.cpp
   OrthancServer/Search/LookupResource.cpp
   OrthancServer/Search/SetOfResources.cpp
@@ -373,6 +376,7 @@
   -DORTHANC_VERSION="${ORTHANC_VERSION}"
   -DORTHANC_DATABASE_VERSION=${ORTHANC_DATABASE_VERSION}
   -DORTHANC_ENABLE_LOGGING=1
+  -DORTHANC_MAXIMUM_TAG_LENGTH=256
   )
 
 list(LENGTH OPENSSL_SOURCES OPENSSL_SOURCES_LENGTH)
@@ -509,6 +513,49 @@
 
 
 #####################################################################
+## Build the "ModalityWorklists" plugin
+#####################################################################
+
+if (ENABLE_PLUGINS AND BUILD_MODALITY_WORKLISTS)
+  execute_process(
+    COMMAND 
+    ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py
+    ${ORTHANC_VERSION} ModalityWorklists ModalityWorklists.dll "Sample Orthanc plugin to serve modality worklists"
+    ERROR_VARIABLE Failure
+    OUTPUT_FILE ${AUTOGENERATED_DIR}/ModalityWorklists.rc
+    )
+
+  if (Failure)
+    message(FATAL_ERROR "Error while computing the version information: ${Failure}")
+  endif()
+
+  add_definitions(-DMODALITY_WORKLISTS_VERSION="${ORTHANC_VERSION}")
+
+  include_directories(${CMAKE_SOURCE_DIR}/Plugins/Include)
+
+  add_library(ModalityWorklists SHARED 
+    ${BOOST_SOURCES}
+    ${JSONCPP_SOURCES}
+    Plugins/Samples/ModalityWorklists/Plugin.cpp
+    ${AUTOGENERATED_DIR}/ModalityWorklists.rc
+    )
+
+  set_target_properties(
+    ModalityWorklists PROPERTIES 
+    VERSION ${ORTHANC_VERSION} 
+    SOVERSION ${ORTHANC_VERSION}
+    )
+
+  install(
+    TARGETS ModalityWorklists
+    RUNTIME DESTINATION lib    # Destination for Windows
+    LIBRARY DESTINATION share/orthanc/plugins    # Destination for Linux
+    )
+endif()
+
+
+
+#####################################################################
 ## Generate the documentation if Doxygen is present
 #####################################################################
 
--- a/Core/DicomFormat/DicomMap.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/Core/DicomFormat/DicomMap.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -61,7 +61,11 @@
     DicomTag(0x0020, 0x0010),   // StudyID
     DICOM_TAG_STUDY_DESCRIPTION,
     DICOM_TAG_ACCESSION_NUMBER,
-    DICOM_TAG_STUDY_INSTANCE_UID
+    DICOM_TAG_STUDY_INSTANCE_UID,
+    DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION,   // New in db v6
+    DICOM_TAG_INSTITUTION_NAME,                  // New in db v6
+    DICOM_TAG_REQUESTING_PHYSICIAN,              // New in db v6
+    DICOM_TAG_REFERRING_PHYSICIAN_NAME           // New in db v6
   };
 
   static DicomTag seriesTags[] =
@@ -83,7 +87,12 @@
     DICOM_TAG_NUMBER_OF_SLICES,
     DICOM_TAG_NUMBER_OF_TIME_SLICES,
     DICOM_TAG_SERIES_INSTANCE_UID,
-    DICOM_TAG_IMAGE_ORIENTATION_PATIENT    // New in db v6
+    DICOM_TAG_IMAGE_ORIENTATION_PATIENT,                  // New in db v6
+    DICOM_TAG_SERIES_TYPE,                                // New in db v6
+    DICOM_TAG_OPERATOR_NAME,                              // New in db v6
+    DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION,       // New in db v6
+    DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION,  // New in db v6
+    DICOM_TAG_CONTRAST_BOLUS_AGENT                        // New in db v6
   };
 
   static DicomTag instanceTags[] =
@@ -96,7 +105,8 @@
     DICOM_TAG_NUMBER_OF_FRAMES,
     DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER,
     DICOM_TAG_SOP_INSTANCE_UID,
-    DICOM_TAG_IMAGE_POSITION_PATIENT    // New in db v6
+    DICOM_TAG_IMAGE_POSITION_PATIENT,    // New in db v6
+    DICOM_TAG_IMAGE_COMMENTS             // New in db v6
   };
 
 
@@ -296,6 +306,12 @@
     SetupFindTemplate(result, studyTags, sizeof(studyTags) / sizeof(DicomTag));
     result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "");
     result.SetValue(DICOM_TAG_PATIENT_ID, "");
+
+    // These main DICOM tags are only indirectly related to the
+    // General Study Module, remove them
+    result.Remove(DICOM_TAG_INSTITUTION_NAME);
+    result.Remove(DICOM_TAG_REQUESTING_PHYSICIAN);
+    result.Remove(DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION);
   }
 
   void DicomMap::SetupFindSeriesTemplate(DicomMap& result)
@@ -315,6 +331,9 @@
     result.Remove(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS);
     result.Remove(DICOM_TAG_NUMBER_OF_TIME_SLICES);
     result.Remove(DICOM_TAG_IMAGE_ORIENTATION_PATIENT);
+    result.Remove(DICOM_TAG_SERIES_TYPE);
+    result.Remove(DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION);
+    result.Remove(DICOM_TAG_CONTRAST_BOLUS_AGENT);
   }
 
   void DicomMap::SetupFindInstanceTemplate(DicomMap& result)
--- a/Core/DicomFormat/DicomTag.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/Core/DicomFormat/DicomTag.h	Wed Dec 02 09:52:56 2015 +0100
@@ -66,6 +66,11 @@
       return element_;
     }
 
+    bool IsPrivate() const
+    {
+      return group_ % 2 == 1;
+    }
+
     const char* GetMainTagsName() const;
 
     bool operator< (const DicomTag& other) const;
@@ -152,4 +157,16 @@
   static const DicomTag DICOM_TAG_SERIES_TIME(0x0008, 0x0031);
   static const DicomTag DICOM_TAG_STUDY_DATE(0x0008, 0x0020);
   static const DicomTag DICOM_TAG_STUDY_TIME(0x0008, 0x0030);
+
+  // Various tags
+  static const DicomTag DICOM_TAG_SERIES_TYPE(0x0054, 0x1000);
+  static const DicomTag DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION(0x0032, 0x1060);
+  static const DicomTag DICOM_TAG_INSTITUTION_NAME(0x0008, 0x0080);
+  static const DicomTag DICOM_TAG_REQUESTING_PHYSICIAN(0x0032, 0x1032);
+  static const DicomTag DICOM_TAG_REFERRING_PHYSICIAN_NAME(0x0008, 0x0090);
+  static const DicomTag DICOM_TAG_OPERATOR_NAME(0x0008, 0x1070);
+  static const DicomTag DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION(0x0040, 0x0254);
+  static const DicomTag DICOM_TAG_IMAGE_COMMENTS(0x0020, 0x4000);
+  static const DicomTag DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION(0x0018, 0x1400);
+  static const DicomTag DICOM_TAG_CONTRAST_BOLUS_AGENT(0x0018, 0x0010);
 }
--- a/Core/Enumerations.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/Core/Enumerations.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -325,6 +325,9 @@
       case ErrorCode_CannotOrderSlices:
         return "Unable to order the slices of the series";
 
+      case ErrorCode_NoWorklistHandler:
+        return "No request handler factory for DICOM C-Find Modality SCP";
+
       default:
         if (error >= ErrorCode_START_PLUGINS)
         {
@@ -678,8 +681,8 @@
       case RequestOrigin_DicomProtocol:
         return "DicomProtocol";
 
-      case RequestOrigin_Http:
-        return "Http";
+      case RequestOrigin_RestApi:
+        return "RestApi";
 
       case RequestOrigin_Plugins:
         return "Plugins";
--- a/Core/Enumerations.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/Core/Enumerations.h	Wed Dec 02 09:52:56 2015 +0100
@@ -138,6 +138,7 @@
     ErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
     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_START_PLUGINS = 1000000
   };
 
@@ -369,7 +370,7 @@
   {
     RequestOrigin_Unknown,
     RequestOrigin_DicomProtocol,
-    RequestOrigin_Http,
+    RequestOrigin_RestApi,
     RequestOrigin_Plugins,
     RequestOrigin_Lua
   };
--- a/Core/HttpServer/HttpOutput.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/Core/HttpServer/HttpOutput.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -442,7 +442,7 @@
 
     multipartBoundary_ = Toolbox::GenerateUuid();
     multipartContentType_ = contentType;
-    header += "Content-Type: multipart/related; type=multipart/" + subType + "; boundary=" + multipartBoundary_ + "\r\n\r\n";
+    header += "Content-Type: multipart/" + subType + "; type=" + contentType + "; boundary=" + multipartBoundary_ + "\r\n\r\n";
 
     stream_.Send(true, header.c_str(), header.size());
     state_ = State_WritingMultipart;
@@ -451,10 +451,10 @@
 
   void HttpOutput::StateMachine::SendMultipartItem(const void* item, size_t length)
   {
-    std::string header = "--" + multipartBoundary_ + "\n";
-    header += "Content-Type: " + multipartContentType_ + "\n";
-    header += "Content-Length: " + boost::lexical_cast<std::string>(length) + "\n";
-    header += "MIME-Version: 1.0\n\n";
+    std::string header = "--" + multipartBoundary_ + "\r\n";
+    header += "Content-Type: " + multipartContentType_ + "\r\n";
+    header += "Content-Length: " + boost::lexical_cast<std::string>(length) + "\r\n";
+    header += "MIME-Version: 1.0\r\n\r\n";
 
     stream_.Send(false, header.c_str(), header.size());
 
@@ -463,7 +463,7 @@
       stream_.Send(false, item, length);
     }
 
-    stream_.Send(false, "\n", 1);
+    stream_.Send(false, "\r\n", 1);
   }
 
 
@@ -478,7 +478,7 @@
     // closed the connection. Such an error is ignored.
     try
     {
-      std::string header = "--" + multipartBoundary_ + "--\n";
+      std::string header = "--" + multipartBoundary_ + "--\r\n";
       stream_.Send(false, header.c_str(), header.size());
     }
     catch (OrthancException&)
--- a/Core/HttpServer/MongooseServer.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/Core/HttpServer/MongooseServer.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -727,7 +727,7 @@
       {
         if (that->HasHandler())
         {
-          found = that->GetHandler().Handle(output, RequestOrigin_Http, remoteIp, username.c_str(), 
+          found = that->GetHandler().Handle(output, RequestOrigin_RestApi, remoteIp, username.c_str(), 
                                             method, uri, headers, argumentsGET, body.c_str(), body.size());
         }
       }
--- a/Core/MultiThreading/RunnableWorkersPool.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/Core/MultiThreading/RunnableWorkersPool.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -105,7 +105,7 @@
   {
     pimpl_->continue_ = true;
 
-    if (countWorkers <= 0)
+    if (countWorkers == 0)
     {
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
--- a/Core/Toolbox.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/Core/Toolbox.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -494,16 +494,16 @@
 
   void Toolbox::ComputeMD5(std::string& result,
                            const void* data,
-                           size_t length)
+                           size_t size)
   {
     md5_state_s state;
     md5_init(&state);
 
-    if (length > 0)
+    if (size > 0)
     {
       md5_append(&state, 
                  reinterpret_cast<const md5_byte_t*>(data), 
-                 static_cast<int>(length));
+                 static_cast<int>(size));
     }
 
     md5_byte_t actualHash[16];
@@ -554,6 +554,14 @@
   }
 #  endif
 
+
+  void Toolbox::EncodeDataUriScheme(std::string& result,
+                                    const std::string& mime,
+                                    const std::string& content)
+  {
+    result = "data:" + mime + ";base64," + base64_encode(content);
+  }
+
 #endif
 
 
@@ -751,14 +759,16 @@
     return result;
   }
 
+
   void Toolbox::ComputeSHA1(std::string& result,
-                            const std::string& data)
+                            const void* data,
+                            size_t size)
   {
     boost::uuids::detail::sha1 sha1;
 
-    if (data.size() > 0)
+    if (size > 0)
     {
-      sha1.process_bytes(&data[0], data.size());
+      sha1.process_bytes(data, size);
     }
 
     unsigned int digest[5];
@@ -777,6 +787,20 @@
             digest[4]);
   }
 
+  void Toolbox::ComputeSHA1(std::string& result,
+                            const std::string& data)
+  {
+    if (data.size() > 0)
+    {
+      ComputeSHA1(result, data.c_str(), data.size());
+    }
+    else
+    {
+      ComputeSHA1(result, NULL, 0);
+    }
+  }
+
+
   bool Toolbox::IsSHA1(const char* str,
                        size_t size)
   {
--- a/Core/Toolbox.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/Core/Toolbox.h	Wed Dec 02 09:52:56 2015 +0100
@@ -100,12 +100,16 @@
 
     void ComputeMD5(std::string& result,
                     const void* data,
-                    size_t length);
+                    size_t size);
 #endif
 
     void ComputeSHA1(std::string& result,
                      const std::string& data);
 
+    void ComputeSHA1(std::string& result,
+                     const void* data,
+                     size_t size);
+
     bool IsSHA1(const char* str,
                 size_t size);
 
@@ -123,6 +127,10 @@
                              std::string& content,
                              const std::string& source);
 #  endif
+
+    void EncodeDataUriScheme(std::string& result,
+                             const std::string& mime,
+                             const std::string& content);
 #endif
 
     std::string GetPathToExecutable();
--- a/NEWS	Wed Nov 18 10:16:21 2015 +0100
+++ b/NEWS	Wed Dec 02 09:52:56 2015 +0100
@@ -1,30 +1,60 @@
 Pending changes in the mainline
 ===============================
 
-* Full indexation of the patient/study tags to speed up searches and C-Find
-* Add ".dcm" suffix to files in ZIP archives (cf. URI ".../archive")
-* "/tools/create-dicom": Support of binary tags encoded using data URI scheme
-* "/tools/create-dicom": Support of hierarchical structures (creation of sequences)
+Major
+-----
+
+* Experimental support of DICOM C-Find SCP for modality worklists through plugins
+* Support of DICOM C-Find SCU for modality worklists ("/modalities/{dicom}/find-worklist")
+
+REST API
+--------
+
+* New URIs:
+  - "/series/.../ordered-slices" to order the slices of a 2D+t or 3D series
+  - "/tools/shutdown" to stop Orthanc from the REST API
+  - ".../compress", ".../uncompress" and ".../is-compressed" for attachments
+  - "/tools/create-archive" to create ZIP from a set of resources
+  - "/tools/create-media" to create ZIP+DICOMDIR from a set of resources
+  - "/instances/.../header" to get the meta information (header) of the DICOM instance
+* "/tools/create-dicom":
+  - Support of binary tags encoded using data URI scheme
+  - Support of hierarchical structures (creation of sequences)
+  - Create tags with unknown VR
 * "/modify" can insert/modify sequences
-* "/series/.../ordered-slices" to order the slices of a 2D+t or 3D image
-* New URI "/tools/shutdown" to stop Orthanc from the REST API
-* New URIs for attachments: ".../compress", ".../uncompress" and ".../is-compressed"
-* New configuration option: "Dictionary" to declare custom DICOM tags
+* ".../preview" and ".../image-uint8" can return JPEG images if the HTTP Accept Header asks so
+* "Origin" metadata for the instances
+
+Minor
+-----
+
+* New configuration options:
+  - "UnknownSopClassAccepted" to disable promiscuous mode (accept unknown SOP class UID)
+  - New configuration option: "Dictionary" to declare custom DICOM tags
+* Add ".dcm" suffix to files in ZIP archives (cf. URI ".../archive")
 * MIME content type can be associated to custom attachments (cf. "UserContentType")
-* New URIs "/tools/create-archive" and "/tools/create-media" to create ZIP/DICOMDIR
-  from a set of resources
-* ".../preview" and ".../image-uint8" can return JPEG images if the HTTP Accept Header asks so
 
 Plugins
 -------
 
-* New function "OrthancPluginRegisterErrorCode()" to declare custom error codes
-* New function "OrthancPluginRegisterDictionaryTag()" to declare custom DICOM tags
-* New function "OrthancPluginRestApiGet2()" to provide HTTP headers when calling Orthanc API
-* New "OrthancStarted", "OrthancStopped", "UpdatedAttachment" 
-  and "UpdatedMetadata" events in change callbacks
+* New functions:
+  - "OrthancPluginRegisterDecodeImageCallback()" to replace the built-in image decoder
+  - "OrthancPluginDicomInstanceToJson()" to convert DICOM to JSON
+  - "OrthancPluginDicomBufferToJson()" to convert DICOM to JSON
+  - "OrthancPluginRegisterErrorCode()" to declare custom error codes
+  - "OrthancPluginRegisterDictionaryTag()" to declare custom DICOM tags
+  - "OrthancPluginRestApiGet2()" to provide HTTP headers when calling Orthanc API
+  - "OrthancPluginGetInstanceOrigin()" to know through which mechanism an instance was received
+  - "OrthancPluginCreateImage()" and "OrthancPluginCreateImageAccessor()" to create images
+  - "OrthancPluginDecodeDicomImage()" to decode DICOM images
+  - "OrthancPluginComputeMd5()" and "OrthancPluginComputeSha1()" to compute MD5/SHA-1 hash
+* New events in change callbacks:
+  - "OrthancStarted"
+  - "OrthancStopped"
+  - "UpdatedAttachment" 
+  - "UpdatedMetadata"
 * "/system" URI gives information about the plugins used for storage area and DB back-end
-* Plugin callbacks should now return explicit "OrthancPluginErrorCode" instead of integers
+* Plugin callbacks must now return explicit "OrthancPluginErrorCode" (instead of integers)
 
 Lua
 ---
@@ -34,13 +64,16 @@
 Maintenance
 -----------
 
-* Full refactoring of the searching features
+* Full indexation of the patient/study tags to speed up searches and C-Find
+* Many refactorings, notably of the searching features and of the image decoding
 * C-Move SCP for studies using AccessionNumber tag
 * Fix issue 4 (C-Store Association not renegotiated on Specific-to-specific transfer syntax change)
-* "/tools/create-dicom" can create tags with unknown VR
+* Fix formatting of multipart HTTP answers
 * "--logdir" flag creates a single log file instead of 3 separate files for errors/warnings/infos
 * "--errors" flag lists the error codes that could be returned by Orthanc
 * Under Windows, the exit status of Orthanc corresponds to the encountered error code
+* New "AgfaImpax", "EFilm2" and "Vitrea" modality manufacturers
+* Upgrade to Boost 1.59.0 for static builds
 
 
 Version 0.9.4 (2015/09/16)
--- a/OrthancServer/DatabaseWrapper.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/DatabaseWrapper.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -256,12 +256,20 @@
   }
 
     
-  DatabaseWrapper::DatabaseWrapper(const std::string& path) : listener_(NULL), base_(db_)
+  DatabaseWrapper::DatabaseWrapper(const std::string& path) : 
+    listener_(NULL), 
+    base_(db_),
+    signalRemainingAncestor_(NULL),
+    version_(0)
   {
     db_.Open(path);
   }
 
-  DatabaseWrapper::DatabaseWrapper() : listener_(NULL), base_(db_)
+  DatabaseWrapper::DatabaseWrapper() : 
+    listener_(NULL), 
+    base_(db_),
+    signalRemainingAncestor_(NULL),
+    version_(0)
   {
     db_.OpenInMemory();
   }
--- a/OrthancServer/DicomDirWriter.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/DicomDirWriter.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -527,7 +527,7 @@
       path = directory + '\\' + filename;
     }
 
-    DcmFileFormat& fileFormat = *reinterpret_cast<DcmFileFormat*>(dicom.GetDcmtkObject());
+    DcmFileFormat& fileFormat = dicom.GetDcmtkObject();
 
     DcmDirectoryRecord* instance;
     bool isNewInstance = pimpl_->CreateResource(instance, ResourceType_Instance, fileFormat, filename.c_str(), path.c_str());
--- a/OrthancServer/DicomInstanceToStore.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/DicomInstanceToStore.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -41,12 +41,6 @@
 
 namespace Orthanc
 {
-  static DcmDataset& GetDataset(ParsedDicomFile& file)
-  {
-    return *reinterpret_cast<DcmFileFormat*>(file.GetDcmtkObject())->getDataset();
-  }
-
-
   void DicomInstanceToStore::AddMetadata(ResourceType level,
                                          MetadataType metadata,
                                          const std::string& value)
@@ -69,17 +63,23 @@
     {
       if (!parsed_.HasContent())
       {
-        throw OrthancException(ErrorCode_NotImplemented);
+        if (!summary_.HasContent())
+        {
+          throw OrthancException(ErrorCode_NotImplemented);
+        }
+        else
+        {
+          parsed_.TakeOwnership(new ParsedDicomFile(summary_.GetConstContent()));
+        }                                
       }
-      else
+
+      // Serialize the parsed DICOM file
+      buffer_.Allocate();
+      if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer_.GetContent(), 
+                                               *parsed_.GetContent().GetDcmtkObject().getDataset()))
       {
-        // Serialize the parsed DICOM file
-        buffer_.Allocate();
-        if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer_.GetContent(), GetDataset(parsed_.GetContent())))
-        {
-          LOG(ERROR) << "Unable to serialize a DICOM file to a memory buffer";
-          throw OrthancException(ErrorCode_InternalError);
-        }
+        LOG(ERROR) << "Unable to serialize a DICOM file to a memory buffer";
+        throw OrthancException(ErrorCode_InternalError);
       }
     }
 
@@ -103,16 +103,18 @@
     if (!summary_.HasContent())
     {
       summary_.Allocate();
-      FromDcmtkBridge::Convert(summary_.GetContent(), GetDataset(parsed_.GetContent()));
+      FromDcmtkBridge::Convert(summary_.GetContent(), 
+                               *parsed_.GetContent().GetDcmtkObject().getDataset());
     }
     
     if (!json_.HasContent())
     {
       json_.Allocate();
-      FromDcmtkBridge::ToJson(json_.GetContent(), GetDataset(parsed_.GetContent()), 
+      FromDcmtkBridge::ToJson(json_.GetContent(), 
+                              *parsed_.GetContent().GetDcmtkObject().getDataset(),
                               DicomToJsonFormat_Full, 
                               DicomToJsonFlags_Default,
-                              256 /* max string length */);
+                              ORTHANC_MAXIMUM_TAG_LENGTH);
     }
   }
 
@@ -200,7 +202,7 @@
         break;
       }
 
-      case RequestOrigin_Http:
+      case RequestOrigin_RestApi:
       {
         result["RemoteIp"] = remoteIp_;
         result["Username"] = httpUsername_;
@@ -234,7 +236,7 @@
   {
     origin_ = call.GetRequestOrigin();
 
-    if (origin_ == RequestOrigin_Http)
+    if (origin_ == RequestOrigin_RestApi)
     {
       remoteIp_ = call.GetRemoteIp();
       httpUsername_ = call.GetUsername();
@@ -244,7 +246,7 @@
   void DicomInstanceToStore::SetHttpOrigin(const char* remoteIp,
                                            const char* username)
   {
-    origin_ = RequestOrigin_Http;
+    origin_ = RequestOrigin_RestApi;
     remoteIp_ = remoteIp;
     httpUsername_ = username;
   }
--- a/OrthancServer/DicomInstanceToStore.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/DicomInstanceToStore.h	Wed Dec 02 09:52:56 2015 +0100
@@ -171,6 +171,11 @@
 
     void SetPluginsOrigin();
 
+    RequestOrigin GetRequestOrigin() const
+    {
+      return origin_;
+    }
+
     const char* GetRemoteAet() const; 
 
     void SetBuffer(const std::string& dicom)
--- a/OrthancServer/DicomModification.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/DicomModification.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -161,7 +161,7 @@
     removals_.erase(tag);
     RemoveInternal(tag);
 
-    if (FromDcmtkBridge::IsPrivateTag(tag))
+    if (tag.IsPrivate())
     {
       privateTagsToKeep_.insert(tag);
     }
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -34,25 +34,166 @@
 #include "DicomFindAnswers.h"
 
 #include "../FromDcmtkBridge.h"
+#include "../ToDcmtkBridge.h"
+#include "../../Core/OrthancException.h"
+
+#include <memory>
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <boost/noncopyable.hpp>
+
 
 namespace Orthanc
 {
+  class DicomFindAnswers::Answer : public boost::noncopyable
+  {
+  private:
+    ParsedDicomFile* dicom_;
+    DicomMap*        map_;
+
+    void CleanupDicom()
+    {
+      if (dicom_ != NULL)
+      {
+        dicom_->Remove(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID);
+        dicom_->Remove(DICOM_TAG_SOP_INSTANCE_UID);
+      }
+    }
+
+  public:
+    Answer(ParsedDicomFile& dicom) : 
+      dicom_(dicom.Clone()),
+      map_(NULL)
+    {
+      CleanupDicom();
+    }
+
+    Answer(const char* dicom,
+           size_t size) : 
+      dicom_(new ParsedDicomFile(dicom, size)),
+      map_(NULL)
+    {
+      CleanupDicom();
+    }
+
+    Answer(const DicomMap& map) : 
+      dicom_(NULL),
+      map_(map.Clone())
+    {
+    }
+
+    ~Answer()
+    {
+      if (dicom_ != NULL)
+      {
+        delete dicom_;
+      }
+
+      if (map_ != NULL)
+      {
+        delete map_;
+      }
+    }
+
+    ParsedDicomFile& GetDicomFile()
+    {
+      if (dicom_ == NULL)
+      {
+        assert(map_ != NULL);
+        dicom_ = new ParsedDicomFile(*map_);
+      }
+
+      return *dicom_;
+    }
+
+    DcmDataset* ExtractDcmDataset() const
+    {
+      if (dicom_ != NULL)
+      {
+        return new DcmDataset(*dicom_->GetDcmtkObject().getDataset());
+      }
+      else
+      {
+        assert(map_ != NULL);
+        return ToDcmtkBridge::Convert(*map_);
+      }
+    }
+  };
+
+
   void DicomFindAnswers::Clear()
   {
-    for (size_t i = 0; i < items_.size(); i++)
+    for (size_t i = 0; i < answers_.size(); i++)
     {
-      delete items_[i];
+      assert(answers_[i] != NULL);
+      delete answers_[i];
+    }
+
+    answers_.clear();
+  }
+
+
+  void DicomFindAnswers::Reserve(size_t size)
+  {
+    if (size > answers_.size())
+    {
+      answers_.reserve(size);
     }
   }
 
-  void DicomFindAnswers::Reserve(size_t size)
+
+  void DicomFindAnswers::Add(const DicomMap& map)
+  {
+    answers_.push_back(new Answer(map));
+  }
+
+
+  void DicomFindAnswers::Add(ParsedDicomFile& dicom)
   {
-    if (size > items_.size())
+    answers_.push_back(new Answer(dicom));
+  }
+
+
+  void DicomFindAnswers::Add(const char* dicom,
+                             size_t size)
+  {
+    answers_.push_back(new Answer(dicom, size));
+  }
+
+
+  DicomFindAnswers::Answer& DicomFindAnswers::GetAnswerInternal(size_t index) const
+  {
+    if (index < answers_.size())
     {
-      items_.reserve(size);
+      return *answers_.at(index);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
   }
 
+
+  ParsedDicomFile& DicomFindAnswers::GetAnswer(size_t index) const
+  {
+    return GetAnswerInternal(index).GetDicomFile();
+  }
+
+
+  DcmDataset* DicomFindAnswers::ExtractDcmDataset(size_t index) const
+  {
+    return GetAnswerInternal(index).ExtractDcmDataset();
+  }
+
+
+  void DicomFindAnswers::ToJson(Json::Value& target,
+                                size_t index,
+                                bool simplify) const
+  {
+    DicomToJsonFormat format = (simplify ? DicomToJsonFormat_Simple : DicomToJsonFormat_Full);
+    GetAnswer(index).ToJson(target, format, DicomToJsonFlags_None, 0);
+  }
+
+
   void DicomFindAnswers::ToJson(Json::Value& target,
                                 bool simplify) const
   {
@@ -60,8 +201,8 @@
 
     for (size_t i = 0; i < GetSize(); i++)
     {
-      Json::Value answer(Json::objectValue);
-      FromDcmtkBridge::ToJson(answer, GetAnswer(i), simplify);
+      Json::Value answer;
+      ToJson(answer, i, simplify);
       target.append(answer);
     }
   }
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.h	Wed Dec 02 09:52:56 2015 +0100
@@ -32,19 +32,25 @@
 
 #pragma once
 
-#include "../../Core/DicomFormat/DicomMap.h"
-
-#include <vector>
-#include <json/json.h>
+#include "../ParsedDicomFile.h"
 
 namespace Orthanc
 {
-  class DicomFindAnswers
+  class DicomFindAnswers : public boost::noncopyable
   {
   private:
-    std::vector<DicomMap*> items_;
+    class Answer;
+
+    std::vector<Answer*> answers_;
+    bool                 complete_;
+
+    Answer& GetAnswerInternal(size_t index) const;
 
   public:
+    DicomFindAnswers() : complete_(true)
+    {
+    }
+
     ~DicomFindAnswers()
     {
       Clear();
@@ -54,22 +60,37 @@
 
     void Reserve(size_t index);
 
-    void Add(const DicomMap& map)
-    {
-      items_.push_back(map.Clone());
-    }
+    void Add(const DicomMap& map);
+
+    void Add(ParsedDicomFile& dicom);
+
+    void Add(const char* dicom,
+             size_t size);
 
     size_t GetSize() const
     {
-      return items_.size();
+      return answers_.size();
     }
 
-    const DicomMap& GetAnswer(size_t index) const
-    {
-      return *items_.at(index);
-    }
+    ParsedDicomFile& GetAnswer(size_t index) const;
+
+    DcmDataset* ExtractDcmDataset(size_t index) const;
 
     void ToJson(Json::Value& target,
                 bool simplify) const;
+
+    void ToJson(Json::Value& target,
+                size_t index,
+                bool simplify) const;
+
+    bool IsComplete() const
+    {
+      return complete_;
+    }
+
+    void SetComplete(bool isComplete)
+    {
+      complete_ = isComplete;
+    }
   };
 }
--- a/OrthancServer/DicomProtocol/DicomServer.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/DicomProtocol/DicomServer.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -94,6 +94,7 @@
     findRequestHandlerFactory_ = NULL;
     moveRequestHandlerFactory_ = NULL;
     storeRequestHandlerFactory_ = NULL;
+    worklistRequestHandlerFactory_ = NULL;
     applicationEntityFilter_ = NULL;
     checkCalledAet_ = true;
     clientTimeout_ = 30;
@@ -245,6 +246,29 @@
     }
   }
 
+  void DicomServer::SetWorklistRequestHandlerFactory(IWorklistRequestHandlerFactory& factory)
+  {
+    Stop();
+    worklistRequestHandlerFactory_ = &factory;
+  }
+
+  bool DicomServer::HasWorklistRequestHandlerFactory() const
+  {
+    return (worklistRequestHandlerFactory_ != NULL);
+  }
+
+  IWorklistRequestHandlerFactory& DicomServer::GetWorklistRequestHandlerFactory() const
+  {
+    if (HasWorklistRequestHandlerFactory())
+    {
+      return *worklistRequestHandlerFactory_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NoWorklistHandler);
+    }
+  }
+
   void DicomServer::SetApplicationEntityFilter(IApplicationEntityFilter& factory)
   {
     Stop();
--- a/OrthancServer/DicomProtocol/DicomServer.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/DicomProtocol/DicomServer.h	Wed Dec 02 09:52:56 2015 +0100
@@ -35,6 +35,7 @@
 #include "IFindRequestHandlerFactory.h"
 #include "IMoveRequestHandlerFactory.h"
 #include "IStoreRequestHandlerFactory.h"
+#include "IWorklistRequestHandlerFactory.h"
 #include "IApplicationEntityFilter.h"
 
 #include <boost/shared_ptr.hpp>
@@ -53,11 +54,11 @@
     std::string aet_;
     uint16_t port_;
     bool continue_;
-    bool started_;
     uint32_t clientTimeout_;
     IFindRequestHandlerFactory* findRequestHandlerFactory_;
     IMoveRequestHandlerFactory* moveRequestHandlerFactory_;
     IStoreRequestHandlerFactory* storeRequestHandlerFactory_;
+    IWorklistRequestHandlerFactory* worklistRequestHandlerFactory_;
     IApplicationEntityFilter* applicationEntityFilter_;
 
     static void ServerThread(DicomServer* server);
@@ -91,6 +92,10 @@
     bool HasStoreRequestHandlerFactory() const;
     IStoreRequestHandlerFactory& GetStoreRequestHandlerFactory() const;
 
+    void SetWorklistRequestHandlerFactory(IWorklistRequestHandlerFactory& handler);
+    bool HasWorklistRequestHandlerFactory() const;
+    IWorklistRequestHandlerFactory& GetWorklistRequestHandlerFactory() const;
+
     void SetApplicationEntityFilter(IApplicationEntityFilter& handler);
     bool HasApplicationEntityFilter() const;
     IApplicationEntityFilter& GetApplicationEntityFilter() const;
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -353,7 +353,8 @@
     struct FindPayload
     {
       DicomFindAnswers* answers;
-      std::string       level;
+      const char*       level;
+      bool              isWorklist;
     };
   }
 
@@ -371,15 +372,23 @@
 
     if (responseIdentifiers != NULL)
     {
-      DicomMap m;
-      FromDcmtkBridge::Convert(m, *responseIdentifiers);
-
-      if (!m.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL))
+      if (payload.isWorklist)
+      {
+        ParsedDicomFile answer(*responseIdentifiers);
+        payload.answers->Add(answer);
+      }
+      else
       {
-        m.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, payload.level);
+        DicomMap m;
+        FromDcmtkBridge::Convert(m, *responseIdentifiers);
+
+        if (!m.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL))
+        {
+          m.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, payload.level);
+        }
+
+        payload.answers->Add(m);
       }
-
-      payload.answers->Add(m);
     }
   }
 
@@ -438,12 +447,15 @@
   {
     switch (manufacturer)
     {
+      case ModalityManufacturer_AgfaImpax:
       case ModalityManufacturer_SyngoVia:
       {
         std::auto_ptr<DicomMap> fix(fields.Clone());
 
         // This issue for Syngo.Via and its solution was reported by
-        // Emsy Chan by private mail on June 17th, 2015.
+        // Emsy Chan by private mail on 2015-06-17. According to
+        // Robert van Ommen (2015-11-30), the same fix is required for
+        // Agfa Impax.
         std::set<DicomTag> tags;
         fix->GetTags(tags);
 
@@ -474,6 +486,52 @@
   }
 
 
+  static void ExecuteFind(DicomFindAnswers& answers,
+                          T_ASC_Association* association,
+                          DcmDataset* dataset,
+                          const char* sopClass,
+                          bool isWorklist,
+                          const char* level,
+                          uint32_t dimseTimeout)
+  {
+    assert(isWorklist ^ (level != NULL));
+
+    FindPayload payload;
+    payload.answers = &answers;
+    payload.level = level;
+    payload.isWorklist = isWorklist;
+
+    // Figure out which of the accepted presentation contexts should be used
+    int presID = ASC_findAcceptedPresentationContextID(association, sopClass);
+    if (presID == 0)
+    {
+      throw OrthancException(ErrorCode_DicomFindUnavailable);
+    }
+
+    T_DIMSE_C_FindRQ request;
+    memset(&request, 0, sizeof(request));
+    request.MessageID = association->nextMsgID++;
+    strcpy(request.AffectedSOPClassUID, sopClass);
+    request.DataSetType = DIMSE_DATASET_PRESENT;
+    request.Priority = DIMSE_PRIORITY_MEDIUM;
+
+    T_DIMSE_C_FindRSP response;
+    DcmDataset* statusDetail = NULL;
+    OFCondition cond = DIMSE_findUser(association, presID, &request, dataset,
+                                      FindCallback, &payload,
+                                      /*opt_blockMode*/ DIMSE_BLOCKING, 
+                                      /*opt_dimse_timeout*/ dimseTimeout,
+                                      &response, &statusDetail);
+
+    if (statusDetail)
+    {
+      delete statusDetail;
+    }
+
+    Check(cond);
+  }
+
+
   void DicomUserConnection::Find(DicomFindAnswers& result,
                                  ResourceType level,
                                  const DicomMap& originalFields)
@@ -483,34 +541,32 @@
 
     CheckIsOpen();
 
-    FindPayload payload;
-    payload.answers = &result;
+    std::auto_ptr<DcmDataset> dataset(ConvertQueryFields(fields, manufacturer_));
+    const char* clevel = NULL;
+    const char* sopClass = NULL;
 
-    std::auto_ptr<DcmDataset> dataset(ConvertQueryFields(fields, manufacturer_));
-
-    const char* sopClass;
     switch (level)
     {
       case ResourceType_Patient:
-        payload.level = "PATIENT";
+        clevel = "PATIENT";
         DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "PATIENT");
         sopClass = UID_FINDPatientRootQueryRetrieveInformationModel;
         break;
 
       case ResourceType_Study:
-        payload.level = "STUDY";
+        clevel = "STUDY";
         DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "STUDY");
         sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
         break;
 
       case ResourceType_Series:
-        payload.level = "SERIES";
+        clevel = "SERIES";
         DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "SERIES");
         sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
         break;
 
       case ResourceType_Instance:
-        payload.level = "INSTANCE";
+        clevel = "INSTANCE";
         if (manufacturer_ == ModalityManufacturer_ClearCanvas ||
             manufacturer_ == ModalityManufacturer_Dcm4Chee)
         {
@@ -565,34 +621,8 @@
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    // Figure out which of the accepted presentation contexts should be used
-    int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass);
-    if (presID == 0)
-    {
-      throw OrthancException(ErrorCode_DicomFindUnavailable);
-    }
-
-    T_DIMSE_C_FindRQ request;
-    memset(&request, 0, sizeof(request));
-    request.MessageID = pimpl_->assoc_->nextMsgID++;
-    strcpy(request.AffectedSOPClassUID, sopClass);
-    request.DataSetType = DIMSE_DATASET_PRESENT;
-    request.Priority = DIMSE_PRIORITY_MEDIUM;
-
-    T_DIMSE_C_FindRSP response;
-    DcmDataset* statusDetail = NULL;
-    OFCondition cond = DIMSE_findUser(pimpl_->assoc_, presID, &request, dataset.get(),
-                                      FindCallback, &payload,
-                                      /*opt_blockMode*/ DIMSE_BLOCKING, 
-                                      /*opt_dimse_timeout*/ pimpl_->dimseTimeout_,
-                                      &response, &statusDetail);
-
-    if (statusDetail)
-    {
-      delete statusDetail;
-    }
-
-    Check(cond);
+    assert(clevel != NULL && sopClass != NULL);
+    ExecuteFind(result, pimpl_->assoc_, dataset.get(), sopClass, false, clevel, pimpl_->dimseTimeout_);
   }
 
 
@@ -685,13 +715,14 @@
     defaultStorageSOPClasses_.clear();
 
     // Copy the short list of storage SOP classes from DCMTK, making
-    // room for the 4 SOP classes reserved for C-ECHO, C-FIND, C-MOVE.
+    // room for the 5 SOP classes reserved for C-ECHO, C-FIND, C-MOVE at (**).
 
     std::set<std::string> uncommon;
     uncommon.insert(UID_BlendingSoftcopyPresentationStateStorage);
     uncommon.insert(UID_GrayscaleSoftcopyPresentationStateStorage);
     uncommon.insert(UID_ColorSoftcopyPresentationStateStorage);
     uncommon.insert(UID_PseudoColorSoftcopyPresentationStateStorage);
+    uncommon.insert(UID_XAXRFGrayscaleSoftcopyPresentationStateStorage);
 
     // Add the storage syntaxes for C-STORE
     for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++)
@@ -721,11 +752,12 @@
     pimpl_->params_ = NULL;
     pimpl_->assoc_ = NULL;
 
-    // SOP classes for C-ECHO, C-FIND and C-MOVE
+    // SOP classes for C-ECHO, C-FIND and C-MOVE (**)
     reservedStorageSOPClasses_.push_back(UID_VerificationSOPClass);
     reservedStorageSOPClasses_.push_back(UID_FINDPatientRootQueryRetrieveInformationModel);
     reservedStorageSOPClasses_.push_back(UID_FINDStudyRootQueryRetrieveInformationModel);
     reservedStorageSOPClasses_.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel);
+    reservedStorageSOPClasses_.push_back(UID_FINDModalityWorklistInformationModel);
 
     ResetStorageSOPClasses();
   }
@@ -1101,4 +1133,15 @@
     CheckStorageSOPClassesInvariant();
   }
 
+
+  void DicomUserConnection::FindWorklist(DicomFindAnswers& result,
+                                         ParsedDicomFile& query)
+  {
+    CheckIsOpen();
+
+    DcmDataset* dataset = query.GetDcmtkObject().getDataset();
+    const char* sopClass = UID_FINDModalityWorklistInformationModel;
+
+    ExecuteFind(result, pimpl_->assoc_, dataset, sopClass, true, NULL, pimpl_->dimseTimeout_);
+  }
 }
--- a/OrthancServer/DicomProtocol/DicomUserConnection.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.h	Wed Dec 02 09:52:56 2015 +0100
@@ -164,5 +164,8 @@
     void SetTimeout(uint32_t seconds);
 
     void DisableTimeout();
+
+    void FindWorklist(DicomFindAnswers& result,
+                      ParsedDicomFile& query);
   };
 }
--- a/OrthancServer/DicomProtocol/IApplicationEntityFilter.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/DicomProtocol/IApplicationEntityFilter.h	Wed Dec 02 09:52:56 2015 +0100
@@ -38,22 +38,29 @@
 
 namespace Orthanc
 {
-  class IApplicationEntityFilter
+  class IApplicationEntityFilter : public boost::noncopyable
   {
   public:
     virtual ~IApplicationEntityFilter()
     {
     }
 
-    virtual bool IsAllowedConnection(const std::string& callingIp,
-                                     const std::string& callingAet) = 0;
+    virtual bool IsAllowedConnection(const std::string& remoteIp,
+                                     const std::string& remoteAet,
+                                     const std::string& calledAet) = 0;
 
-    virtual bool IsAllowedRequest(const std::string& callingIp,
-                                  const std::string& callingAet,
+    virtual bool IsAllowedRequest(const std::string& remoteIp,
+                                  const std::string& remoteAet,
+                                  const std::string& calledAet,
                                   DicomRequestType type) = 0;
 
-    virtual bool IsAllowedTransferSyntax(const std::string& callingIp,
-                                         const std::string& callingAet,
+    virtual bool IsAllowedTransferSyntax(const std::string& remoteIp,
+                                         const std::string& remoteAet,
+                                         const std::string& calledAet,
                                          TransferSyntax syntax) = 0;
+
+    virtual bool IsUnknownSopClassAccepted(const std::string& remoteIp,
+                                           const std::string& remoteAet,
+                                           const std::string& calledAet) = 0;
   };
 }
--- a/OrthancServer/DicomProtocol/IFindRequestHandler.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/DicomProtocol/IFindRequestHandler.h	Wed Dec 02 09:52:56 2015 +0100
@@ -34,28 +34,19 @@
 
 #include "DicomFindAnswers.h"
 
-#include <vector>
-#include <string>
-
-
 namespace Orthanc
 {
-  class IFindRequestHandler
+  class IFindRequestHandler : public boost::noncopyable
   {
   public:
     virtual ~IFindRequestHandler()
     {
     }
 
-    /**
-     * Can throw exceptions. Returns "false" iff too many results have
-     * to be returned. In such a case, a "Matching terminated due to
-     * Cancel request" DIMSE code would be returned.
-     * https://www.dabsoft.ch/dicom/4/V.4.1/
-     **/
-    virtual bool Handle(DicomFindAnswers& answers,
+    virtual void Handle(DicomFindAnswers& answers,
                         const DicomMap& input,
                         const std::string& remoteIp,
-                        const std::string& remoteAet) = 0;
+                        const std::string& remoteAet,
+                        const std::string& calledAet) = 0;
   };
 }
--- a/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h	Wed Dec 02 09:52:56 2015 +0100
@@ -36,7 +36,7 @@
 
 namespace Orthanc
 {
-  class IFindRequestHandlerFactory
+  class IFindRequestHandlerFactory : public boost::noncopyable
   {
   public:
     virtual ~IFindRequestHandlerFactory()
--- a/OrthancServer/DicomProtocol/IMoveRequestHandler.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/DicomProtocol/IMoveRequestHandler.h	Wed Dec 02 09:52:56 2015 +0100
@@ -40,7 +40,7 @@
 
 namespace Orthanc
 {
-  class IMoveRequestIterator
+  class IMoveRequestIterator : public boost::noncopyable
   {
   public:
     enum Status
@@ -70,7 +70,8 @@
     virtual IMoveRequestIterator* Handle(const std::string& target,
                                          const DicomMap& input,
                                          const std::string& remoteIp,
-                                         const std::string& remoteAet) = 0;
+                                         const std::string& remoteAet,
+                                         const std::string& calledAet) = 0;
   };
 
 }
--- a/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h	Wed Dec 02 09:52:56 2015 +0100
@@ -36,7 +36,7 @@
 
 namespace Orthanc
 {
-  class IMoveRequestHandlerFactory
+  class IMoveRequestHandlerFactory : public boost::noncopyable
   {
   public:
     virtual ~IMoveRequestHandlerFactory()
--- a/OrthancServer/DicomProtocol/IStoreRequestHandler.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/DicomProtocol/IStoreRequestHandler.h	Wed Dec 02 09:52:56 2015 +0100
@@ -40,7 +40,7 @@
 
 namespace Orthanc
 {
-  class IStoreRequestHandler
+  class IStoreRequestHandler : public boost::noncopyable
   {
   public:
     virtual ~IStoreRequestHandler()
--- a/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h	Wed Dec 02 09:52:56 2015 +0100
@@ -36,7 +36,7 @@
 
 namespace Orthanc
 {
-  class IStoreRequestHandlerFactory
+  class IStoreRequestHandlerFactory : public boost::noncopyable
   {
   public:
     virtual ~IStoreRequestHandlerFactory()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/DicomProtocol/IWorklistRequestHandler.h	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,52 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "DicomFindAnswers.h"
+
+namespace Orthanc
+{
+  class IWorklistRequestHandler : public boost::noncopyable
+  {
+  public:
+    virtual ~IWorklistRequestHandler()
+    {
+    }
+
+    virtual void Handle(DicomFindAnswers& answers,
+                        ParsedDicomFile& query,
+                        const std::string& remoteIp,
+                        const std::string& remoteAet,
+                        const std::string& calledAet) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/DicomProtocol/IWorklistRequestHandlerFactory.h	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,48 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IWorklistRequestHandler.h"
+
+namespace Orthanc
+{
+  class IWorklistRequestHandlerFactory : public boost::noncopyable
+  {
+  public:
+    virtual ~IWorklistRequestHandlerFactory()
+    {
+    }
+
+    virtual IWorklistRequestHandler* ConstructWorklistRequestHandler() = 0;
+  };
+}
--- a/OrthancServer/FromDcmtkBridge.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/FromDcmtkBridge.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -354,19 +354,6 @@
   }
 
 
-  bool FromDcmtkBridge::IsPrivateTag(const DicomTag& tag)
-  {
-#if 1
-    DcmTagKey tmp(tag.GetGroup(), tag.GetElement());
-    return tmp.isPrivate();
-#else
-    // Implementation for Orthanc versions <= 0.8.5
-    DcmTag tmp(tag.GetGroup(), tag.GetElement());
-    return IsPrivateTag(tmp);
-#endif
-  }
-
-
   DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element,
                                                   DicomToJsonFlags flags,
                                                   Encoding encoding)
@@ -377,26 +364,28 @@
       throw OrthancException(ErrorCode_BadParameterType);
     }
 
-    if (element.isaString())
+    char *c = NULL;
+    if (element.isaString() &&
+        element.getString(c).good())
     {
-      char *c;
-      if (element.getString(c).good())
+      if (c == NULL)  // This case corresponds to the empty string
+      {
+        return new DicomValue("", false);
+      }
+      else
       {
-        if (c == NULL)  // This case corresponds to the empty string
+        std::string s(c);
+        std::string utf8 = Toolbox::ConvertToUtf8(s, encoding);
+
+        if (utf8.size() > ORTHANC_MAXIMUM_TAG_LENGTH)
         {
-          return new DicomValue("", false);
+          return new DicomValue;  // Create a NULL value
         }
         else
         {
-          std::string s(c);
-          std::string utf8 = Toolbox::ConvertToUtf8(s, encoding);
           return new DicomValue(utf8, false);
         }
       }
-      else
-      {
-        return new DicomValue;
-      }
     }
 
     try
@@ -414,24 +403,6 @@
         case EVR_OW:  // other word
         case EVR_UN:  // unknown value representation
         case EVR_ox:  // OB or OW depending on context
-        {
-          if (!(flags & DicomToJsonFlags_ConvertBinaryToNull))
-          {
-            Uint8* data = NULL;
-            if (element.getUint8Array(data) == EC_Normal)
-            {
-              return new DicomValue(reinterpret_cast<const char*>(data), element.getLength(), true);
-            }
-          }
-
-          return new DicomValue;
-        }
-    
-          /**
-           * String types, should never happen at this point because of
-           * "element.isaString()".
-           **/
-      
         case EVR_DS:  // decimal string
         case EVR_IS:  // integer string
         case EVR_AS:  // age string
@@ -447,12 +418,24 @@
         case EVR_UT:  // unlimited text
         case EVR_PN:  // person name
         case EVR_UI:  // unique identifier
-          return new DicomValue;
-
+        case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
+        case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
+        {
+          if (!(flags & DicomToJsonFlags_ConvertBinaryToNull))
+          {
+            Uint8* data = NULL;
+            if (element.getUint8Array(data) == EC_Normal)
+            {
+              return new DicomValue(reinterpret_cast<const char*>(data), element.getLength(), true);
+            }
+          }
 
-          /**
-           * Numberic types
-           **/ 
+          return new DicomValue;
+        }
+    
+        /**
+         * Numberic types
+         **/ 
       
         case EVR_SL:  // signed long
         {
@@ -553,10 +536,8 @@
         case EVR_dirRecord:  // used internally for DICOMDIR records
         case EVR_pixelSQ:  // used internally for pixel sequences in a compressed image
         case EVR_pixelItem:  // used internally for pixel items in a compressed image
-        case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
         case EVR_PixelData:  // used internally for uncompressed pixeld data
         case EVR_OverlayData:  // used internally for overlay data
-        case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
           return new DicomValue;
 
 
@@ -782,8 +763,11 @@
         throw OrthancException(ErrorCode_InternalError);
       }
 
-      if (!(flags & DicomToJsonFlags_IncludePrivateTags) &&
-          element->getTag().isPrivate())
+      DicomTag tag(FromDcmtkBridge::Convert(element->getTag()));
+
+      /*element->getTag().isPrivate()*/
+      if (tag.IsPrivate() &&
+          !(flags & DicomToJsonFlags_IncludePrivateTags))    
       {
         continue;
       }
@@ -805,8 +789,6 @@
           evr == EVR_ox)
       {
         // This is a binary tag
-        DicomTag tag(FromDcmtkBridge::Convert(element->getTag()));
-
         if ((tag == DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludePixelData)) ||
             (tag != DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludeBinary)))
         {
@@ -830,6 +812,17 @@
   }
 
 
+  void FromDcmtkBridge::ToJson(Json::Value& target, 
+                               DcmMetaInfo& dataset,
+                               DicomToJsonFormat format,
+                               DicomToJsonFlags flags,
+                               unsigned int maxStringLength)
+  {
+    target = Json::objectValue;
+    DatasetToJson(target, dataset, format, flags, maxStringLength, Encoding_Ascii);
+  }
+
+
   std::string FromDcmtkBridge::GetName(const DicomTag& t)
   {
     // Some patches for important tags because of different DICOM
@@ -1073,6 +1066,9 @@
       case EVR_TM:
         return ValueRepresentation_Time;
 
+      case EVR_SQ:
+        return ValueRepresentation_Sequence;
+
       default:
         return ValueRepresentation_Other;
     }
@@ -1081,8 +1077,7 @@
 
   static bool IsBinaryTag(const DcmTag& key)
   {
-    return (key.isPrivate() || 
-            key.isUnknownVR() || 
+    return (key.isUnknownVR() || 
             key.getEVR() == EVR_OB ||
             key.getEVR() == EVR_OF ||
             key.getEVR() == EVR_OW ||
@@ -1095,7 +1090,8 @@
   {
     DcmTag key(tag.GetGroup(), tag.GetElement());
 
-    if (IsBinaryTag(key))
+    if (tag.IsPrivate() ||
+        IsBinaryTag(key))
     {
       return new DcmOtherByteOtherWord(key);
     }
@@ -1238,13 +1234,13 @@
   void FromDcmtkBridge::FillElementWithString(DcmElement& element,
                                               const DicomTag& tag,
                                               const std::string& utf8Value,
-                                              bool decodeBinaryTags,
+                                              bool decodeDataUriScheme,
                                               Encoding dicomEncoding)
   {
     std::string binary;
     const std::string* decoded = &utf8Value;
 
-    if (decodeBinaryTags &&
+    if (decodeDataUriScheme &&
         boost::starts_with(utf8Value, "data:application/octet-stream;base64,"))
     {
       std::string mime;
@@ -1259,7 +1255,8 @@
 
     DcmTag key(tag.GetGroup(), tag.GetElement());
 
-    if (IsBinaryTag(key))
+    if (tag.IsPrivate() ||
+        IsBinaryTag(key))
     {
       if (element.putUint8Array((const Uint8*) decoded->c_str(), decoded->size()).good())
       {
@@ -1409,7 +1406,7 @@
 
   DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag,
                                         const Json::Value& value,
-                                        bool decodeBinaryTags,
+                                        bool decodeDataUriScheme,
                                         Encoding dicomEncoding)
   {
     std::auto_ptr<DcmElement> element;
@@ -1418,7 +1415,7 @@
     {
       case Json::stringValue:
         element.reset(CreateElementForTag(tag));
-        FillElementWithString(*element, tag, value.asString(), decodeBinaryTags, dicomEncoding);
+        FillElementWithString(*element, tag, value.asString(), decodeDataUriScheme, dicomEncoding);
         break;
 
       case Json::arrayValue:
@@ -1439,7 +1436,7 @@
           Json::Value::Members members = value[i].getMemberNames();
           for (Json::Value::ArrayIndex j = 0; j < members.size(); j++)
           {
-            item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeBinaryTags, dicomEncoding));
+            item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeDataUriScheme, dicomEncoding));
           }
 
           sequence->append(item.release());
--- a/OrthancServer/FromDcmtkBridge.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/FromDcmtkBridge.h	Wed Dec 02 09:52:56 2015 +0100
@@ -37,6 +37,7 @@
 #include "../Core/DicomFormat/DicomMap.h"
 
 #include <dcmtk/dcmdata/dcdatset.h>
+#include <dcmtk/dcmdata/dcmetinf.h>
 #include <json/json.h>
 
 namespace Orthanc
@@ -60,8 +61,6 @@
 
     static DicomTag GetTag(const DcmElement& element);
 
-    static bool IsPrivateTag(const DicomTag& tag);
-
     static bool IsUnknownTag(const DicomTag& tag);
 
     static DicomValue* ConvertLeafElement(DcmElement& element,
@@ -81,6 +80,12 @@
                        DicomToJsonFlags flags,
                        unsigned int maxStringLength);
 
+    static void ToJson(Json::Value& target, 
+                       DcmMetaInfo& header,
+                       DicomToJsonFormat format,
+                       DicomToJsonFlags flags,
+                       unsigned int maxStringLength);
+
     static std::string GetName(const DicomTag& tag);
 
     static DicomTag ParseTag(const char* name);
@@ -125,12 +130,12 @@
     static void FillElementWithString(DcmElement& element,
                                       const DicomTag& tag,
                                       const std::string& utf8alue,  // Encoded using UTF-8
-                                      bool interpretBinaryTags,
+                                      bool decodeDataUriScheme,
                                       Encoding dicomEncoding);
 
     static DcmElement* FromJson(const DicomTag& tag,
                                 const Json::Value& element,  // Encoding using UTF-8
-                                bool interpretBinaryTags,
+                                bool decodeDataUriScheme,
                                 Encoding dicomEncoding);
 
     static DcmEVR ParseValueRepresentation(const std::string& s);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/IDicomImageDecoder.h	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,53 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Core/Images/ImageAccessor.h"
+
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class ParsedDicomFile;
+
+  class IDicomImageDecoder : public boost::noncopyable
+  {
+  public:
+    virtual ~IDicomImageDecoder()
+    {
+    }
+
+    virtual ImageAccessor* Decode(ParsedDicomFile& dicom,
+                                  unsigned int frame) = 0;
+  };
+}
--- a/OrthancServer/Internals/CommandDispatcher.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/Internals/CommandDispatcher.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -91,15 +91,11 @@
 #include <dcmtk/dcmnet/dcasccfg.h>      /* for class DcmAssociationConfiguration */
 #include <boost/lexical_cast.hpp>
 
-#define ORTHANC_PROMISCUOUS 1
-
 static OFBool    opt_rejectWithoutImplementationUID = OFFalse;
 
 
 
-#if ORTHANC_PROMISCUOUS == 1
-static
-DUL_PRESENTATIONCONTEXT *
+static DUL_PRESENTATIONCONTEXT *
 findPresentationContextID(LST_HEAD * head,
                           T_ASC_PresentationContextID presentationContextID)
 {
@@ -231,7 +227,6 @@
   }
   return cond;
 }
-#endif
 
 
 
@@ -430,6 +425,11 @@
         knownAbstractSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel);
       }
 
+      if (server.HasWorklistRequestHandlerFactory())
+      {
+        knownAbstractSyntaxes.push_back(UID_FINDModalityWorklistInformationModel);
+      }
+
       // For C-MOVE
       if (server.HasMoveRequestHandlerFactory())
       {
@@ -460,17 +460,17 @@
       }
 
       // Retrieve the AET and the IP address of the remote modality
-      std::string callingAet;
-      std::string callingIp;
+      std::string remoteAet;
+      std::string remoteIp;
       std::string calledAet;
   
       {
-        DIC_AE callingAet_C;
+        DIC_AE remoteAet_C;
         DIC_AE calledAet_C;
-        DIC_AE callingIp_C;
+        DIC_AE remoteIp_C;
         DIC_AE calledIP_C;
-        if (ASC_getAPTitles(assoc->params, callingAet_C, calledAet_C, NULL).bad() ||
-            ASC_getPresentationAddresses(assoc->params, callingIp_C, calledIP_C).bad())
+        if (ASC_getAPTitles(assoc->params, remoteAet_C, calledAet_C, NULL).bad() ||
+            ASC_getPresentationAddresses(assoc->params, remoteIp_C, calledIP_C).bad())
         {
           T_ASC_RejectParameters rej =
             {
@@ -483,13 +483,13 @@
           return NULL;
         }
 
-        callingIp = std::string(/*OFSTRING_GUARD*/(callingIp_C));
-        callingAet = std::string(/*OFSTRING_GUARD*/(callingAet_C));
+        remoteIp = std::string(/*OFSTRING_GUARD*/(remoteIp_C));
+        remoteAet = std::string(/*OFSTRING_GUARD*/(remoteAet_C));
         calledAet = (/*OFSTRING_GUARD*/(calledAet_C));
       }
 
-      LOG(INFO) << "Association Received from AET " << callingAet 
-                << " on IP " << callingIp;
+      LOG(INFO) << "Association Received from AET " << remoteAet 
+                << " on IP " << remoteIp;
 
 
       std::vector<const char*> transferSyntaxes;
@@ -501,13 +501,13 @@
 
       // New transfer syntaxes supported since Orthanc 0.7.2
       if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, TransferSyntax_Deflated))
+          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Deflated))
       {
         transferSyntaxes.push_back(UID_DeflatedExplicitVRLittleEndianTransferSyntax); 
       }
 
       if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, TransferSyntax_Jpeg))
+          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg))
       {
         transferSyntaxes.push_back(UID_JPEGProcess1TransferSyntax);
         transferSyntaxes.push_back(UID_JPEGProcess2_4TransferSyntax);
@@ -532,14 +532,14 @@
       }
 
       if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, TransferSyntax_Jpeg2000))
+          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg2000))
       {
         transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax);
         transferSyntaxes.push_back(UID_JPEG2000TransferSyntax);
       }
 
       if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, TransferSyntax_JpegLossless))
+          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_JpegLossless))
       {
         transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax);
         transferSyntaxes.push_back(UID_JPEG2000TransferSyntax);
@@ -548,21 +548,21 @@
       }
 
       if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, TransferSyntax_Jpip))
+          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpip))
       {
         transferSyntaxes.push_back(UID_JPIPReferencedTransferSyntax);
         transferSyntaxes.push_back(UID_JPIPReferencedDeflateTransferSyntax);
       }
 
       if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, TransferSyntax_Mpeg2))
+          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Mpeg2))
       {
         transferSyntaxes.push_back(UID_MPEG2MainProfileAtMainLevelTransferSyntax);
         transferSyntaxes.push_back(UID_MPEG2MainProfileAtHighLevelTransferSyntax);
       }
 
       if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, TransferSyntax_Rle))
+          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Rle))
       {
         transferSyntaxes.push_back(UID_RLELosslessTransferSyntax);
       }
@@ -585,17 +585,22 @@
         return NULL;
       }
 
-#if ORTHANC_PROMISCUOUS == 1
-      /* accept everything not known not to be a storage SOP class */
-      cond = acceptUnknownContextsWithPreferredTransferSyntaxes(
-        assoc->params, &transferSyntaxes[0], transferSyntaxes.size());
-      if (cond.bad())
+      if (!server.HasApplicationEntityFilter() ||
+          server.GetApplicationEntityFilter().IsUnknownSopClassAccepted(remoteIp, remoteAet, calledAet))
       {
-        LOG(INFO) << cond.text();
-        AssociationCleanup(assoc);
-        return NULL;
+        /*
+         * Promiscous mode is enabled: Accept everything not known not
+         * to be a storage SOP class.
+         **/
+        cond = acceptUnknownContextsWithPreferredTransferSyntaxes(
+          assoc->params, &transferSyntaxes[0], transferSyntaxes.size());
+        if (cond.bad())
+        {
+          LOG(INFO) << cond.text();
+          AssociationCleanup(assoc);
+          return NULL;
+        }
       }
-#endif
 
       /* set our app title */
       ASC_setAPTitles(assoc->params, NULL, NULL, server.GetApplicationEntityTitle().c_str());
@@ -638,9 +643,9 @@
       }
 
       if (server.HasApplicationEntityFilter() &&
-          !server.GetApplicationEntityFilter().IsAllowedConnection(callingIp, callingAet))
+          !server.GetApplicationEntityFilter().IsAllowedConnection(remoteIp, remoteAet, calledAet))
       {
-        LOG(WARNING) << "Rejected association for remote AET " << callingAet << " on IP " << callingIp;
+        LOG(WARNING) << "Rejected association for remote AET " << remoteAet << " on IP " << remoteIp;
         T_ASC_RejectParameters rej =
           {
             ASC_RESULT_REJECTEDPERMANENT,
@@ -687,7 +692,7 @@
       }
 
       IApplicationEntityFilter* filter = server.HasApplicationEntityFilter() ? &server.GetApplicationEntityFilter() : NULL;
-      return new CommandDispatcher(server, assoc, callingIp, callingAet, filter);
+      return new CommandDispatcher(server, assoc, remoteIp, remoteAet, calledAet, filter);
     }
 
     bool CommandDispatcher::Step()
@@ -771,7 +776,7 @@
         if (supported && 
             request != DicomRequestType_Echo &&  // Always allow incoming ECHO requests
             filter_ != NULL &&
-            !filter_->IsAllowedRequest(remoteIp_, remoteAet_, request))
+            !filter_->IsAllowedRequest(remoteIp_, remoteAet_, calledAet_, request))
         {
           LOG(ERROR) << EnumerationToString(request) 
                      << " requests are disallowed for the AET \"" 
@@ -798,7 +803,11 @@
               {
                 std::auto_ptr<IStoreRequestHandler> handler
                   (server_.GetStoreRequestHandlerFactory().ConstructStoreRequestHandler());
-                cond = Internals::storeScp(assoc_, &msg, presID, *handler, remoteIp_);
+
+                if (handler.get() != NULL)
+                {
+                  cond = Internals::storeScp(assoc_, &msg, presID, *handler, remoteIp_);
+                }
               }
               break;
 
@@ -807,16 +816,32 @@
               {
                 std::auto_ptr<IMoveRequestHandler> handler
                   (server_.GetMoveRequestHandlerFactory().ConstructMoveRequestHandler());
-                cond = Internals::moveScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_);
+
+                if (handler.get() != NULL)
+                {
+                  cond = Internals::moveScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_, calledAet_);
+                }
               }
               break;
 
             case DicomRequestType_Find:
-              if (server_.HasFindRequestHandlerFactory()) // Should always be true
+              if (server_.HasFindRequestHandlerFactory() || // Should always be true
+                  server_.HasWorklistRequestHandlerFactory())
               {
-                std::auto_ptr<IFindRequestHandler> handler
-                  (server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler());
-                cond = Internals::findScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_);
+                std::auto_ptr<IFindRequestHandler> findHandler;
+                if (server_.HasFindRequestHandlerFactory())
+                {
+                  findHandler.reset(server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler());
+                }
+
+                std::auto_ptr<IWorklistRequestHandler> worklistHandler;
+                if (server_.HasWorklistRequestHandlerFactory())
+                {
+                  worklistHandler.reset(server_.GetWorklistRequestHandlerFactory().ConstructWorklistRequestHandler());
+                }
+
+                cond = Internals::findScp(assoc_, &msg, presID, findHandler.get(), 
+                                          worklistHandler.get(), remoteIp_, remoteAet_, calledAet_);
               }
               break;
 
--- a/OrthancServer/Internals/CommandDispatcher.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/Internals/CommandDispatcher.h	Wed Dec 02 09:52:56 2015 +0100
@@ -52,6 +52,7 @@
       T_ASC_Association* assoc_;
       std::string remoteIp_;
       std::string remoteAet_;
+      std::string calledAet_;
       IApplicationEntityFilter* filter_;
 
     public:
@@ -59,11 +60,13 @@
                         T_ASC_Association* assoc,
                         const std::string& remoteIp,
                         const std::string& remoteAet,
+                        const std::string& calledAet,
                         IApplicationEntityFilter* filter) :
         server_(server),
         assoc_(assoc),
         remoteIp_(remoteIp),
         remoteAet_(remoteAet),
+        calledAet_(calledAet),
         filter_(filter)
       {
         clientTimeout_ = server.GetClientTimeout();
--- a/OrthancServer/Internals/DicomImageDecoder.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/Internals/DicomImageDecoder.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -82,14 +82,17 @@
 
 #include "../../Core/Logging.h"
 #include "../../Core/OrthancException.h"
+#include "../../Core/Images/Image.h"
 #include "../../Core/Images/ImageProcessing.h"
-#include "../../Core/Images/PngWriter.h"  // TODO REMOVE THIS
 #include "../../Core/DicomFormat/DicomIntegerPixelAccessor.h"
 #include "../ToDcmtkBridge.h"
 #include "../FromDcmtkBridge.h"
+#include "../ParsedDicomFile.h"
 
 #include <boost/lexical_cast.hpp>
 
+#include <dcmtk/dcmdata/dcfilefo.h>
+
 #if ORTHANC_JPEG_LOSSLESS_ENABLED == 1
 #include <dcmtk/dcmjpls/djcodecd.h>
 #include <dcmtk/dcmjpls/djcparam.h>
@@ -303,8 +306,7 @@
   };
 
 
-  void DicomImageDecoder::SetupImageBuffer(ImageBuffer& target,
-                                           DcmDataset& dataset)
+  ImageAccessor* DicomImageDecoder::CreateImage(DcmDataset& dataset)
   {
     DicomMap m;
     FromDcmtkBridge::Convert(m, dataset);
@@ -323,9 +325,7 @@
       throw OrthancException(ErrorCode_NotImplemented);
     }
 
-    target.SetHeight(info.GetHeight());
-    target.SetWidth(info.GetWidth());
-    target.SetFormat(format);
+    return new Image(format, info.GetWidth(), info.GetHeight());
   }
 
 
@@ -373,22 +373,20 @@
   }
 
 
-  void DicomImageDecoder::DecodeUncompressedImage(ImageBuffer& target,
-                                                  DcmDataset& dataset,
-                                                  unsigned int frame)
+  ImageAccessor* DicomImageDecoder::DecodeUncompressedImage(DcmDataset& dataset,
+                                                            unsigned int frame)
   {
     if (!IsUncompressedImage(dataset))
     {
       throw OrthancException(ErrorCode_BadParameterType);
     }
 
-    DecodeUncompressedImageInternal(target, dataset, frame);
+    return DecodeUncompressedImageInternal(dataset, frame);
   }
 
 
-  void DicomImageDecoder::DecodeUncompressedImageInternal(ImageBuffer& target,
-                                                          DcmDataset& dataset,
-                                                          unsigned int frame)
+  ImageAccessor* DicomImageDecoder::DecodeUncompressedImageInternal(DcmDataset& dataset,
+                                                                    unsigned int frame)
   {
     ImageSource source;
     source.Setup(dataset, frame);
@@ -398,10 +396,10 @@
      * Resize the target image.
      **/
 
-    SetupImageBuffer(target, dataset);
+    std::auto_ptr<ImageAccessor> target(CreateImage(dataset));
 
-    if (source.GetWidth() != target.GetWidth() ||
-        source.GetHeight() != target.GetHeight())
+    if (source.GetWidth() != target->GetWidth() ||
+        source.GetHeight() != target->GetHeight())
     {
       throw OrthancException(ErrorCode_InternalError);
     }
@@ -412,7 +410,6 @@
      * direct access to copy its values.
      **/
 
-    ImageAccessor targetAccessor(target.GetAccessor());
     const DicomImageInformation& info = source.GetAccessor().GetInformation();
 
     bool fastVersionSuccess = false;
@@ -434,8 +431,8 @@
                                      info.GetWidth() * GetBytesPerPixel(sourceFormat),
                                      buffer + frame * frameSize);
 
-          ImageProcessing::Convert(targetAccessor, sourceImage);
-          ImageProcessing::ShiftRight(targetAccessor, info.GetShift());
+          ImageProcessing::Convert(*target, sourceImage);
+          ImageProcessing::ShiftRight(*target, info.GetShift());
           fastVersionSuccess = true;
         }
       }
@@ -452,33 +449,34 @@
 
     if (!fastVersionSuccess)
     {
-      switch (target.GetFormat())
+      switch (target->GetFormat())
       {
         case PixelFormat_RGB24:
         case PixelFormat_RGBA32:
         case PixelFormat_Grayscale8:
-          CopyPixels<uint8_t>(targetAccessor, source.GetAccessor());
+          CopyPixels<uint8_t>(*target, source.GetAccessor());
           break;
         
         case PixelFormat_Grayscale16:
-          CopyPixels<uint16_t>(targetAccessor, source.GetAccessor());
+          CopyPixels<uint16_t>(*target, source.GetAccessor());
           break;
 
         case PixelFormat_SignedGrayscale16:
-          CopyPixels<int16_t>(targetAccessor, source.GetAccessor());
+          CopyPixels<int16_t>(*target, source.GetAccessor());
           break;
 
         default:
           throw OrthancException(ErrorCode_InternalError);
       }
     }
+
+    return target.release();
   }
 
 
 #if ORTHANC_JPEG_LOSSLESS_ENABLED == 1
-  void DicomImageDecoder::DecodeJpegLossless(ImageBuffer& target,
-                                             DcmDataset& dataset,
-                                             unsigned int frame)
+  ImageAccessor* DicomImageDecoder::DecodeJpegLossless(DcmDataset& dataset,
+                                                       unsigned int frame)
   {
     if (!IsJpegLossless(dataset))
     {
@@ -499,9 +497,7 @@
       throw OrthancException(ErrorCode_BadFileFormat);
     }
 
-    SetupImageBuffer(target, dataset);
-
-    ImageAccessor targetAccessor(target.GetAccessor());
+    std::auto_ptr<ImageAccessor> target(CreateImage(dataset));
 
     /**
      * The "DJLSLosslessDecoder" and "DJLSNearLosslessDecoder" in DCMTK
@@ -517,36 +513,36 @@
     OFString decompressedColorModel;  // Out
     DJ_RPLossless representationParameter;
     OFCondition c = decoder.decodeFrame(&representationParameter, pixelSequence, &parameters, 
-                                        &dataset, frame, startFragment, targetAccessor.GetBuffer(), 
-                                        targetAccessor.GetSize(), decompressedColorModel);
+                                        &dataset, frame, startFragment, target->GetBuffer(), 
+                                        target->GetSize(), decompressedColorModel);
 
     if (!c.good())
     {
       throw OrthancException(ErrorCode_InternalError);
     }
+
+    return target.release();
   }
 #endif
 
 
 
 
-  bool DicomImageDecoder::Decode(ImageBuffer& target,
-                                 DcmDataset& dataset,
-                                 unsigned int frame)
+  ImageAccessor* DicomImageDecoder::Decode(ParsedDicomFile& dicom,
+                                           unsigned int frame)
   {
+    DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset();
+
     if (IsUncompressedImage(dataset))
     {
-      DecodeUncompressedImage(target, dataset, frame);
-      return true;
+      return DecodeUncompressedImage(dataset, frame);
     }
 
-
 #if ORTHANC_JPEG_LOSSLESS_ENABLED == 1
     if (IsJpegLossless(dataset))
     {
       LOG(INFO) << "Decoding a JPEG-LS image";
-      DecodeJpegLossless(target, dataset, frame);
-      return true;
+      return DecodeJpegLossless(dataset, frame);
     }
 #endif
 
@@ -555,7 +551,6 @@
     // TODO Implement this part to speed up JPEG decompression
 #endif
 
-
     /**
      * This DICOM image format is not natively supported by
      * Orthanc. As a last resort, try and decode it through
@@ -572,12 +567,11 @@
 
       if (converted->canWriteXfer(EXS_LittleEndianExplicit))
       {
-        DecodeUncompressedImageInternal(target, *converted, frame);
-        return true;
+        return DecodeUncompressedImageInternal(*converted, frame);
       }
     }
 
-    return false;
+    return NULL;
   }
 
 
@@ -588,23 +582,13 @@
   }
 
 
-  bool DicomImageDecoder::DecodeAndTruncate(ImageBuffer& target,
-                                            DcmDataset& dataset,
-                                            unsigned int frame,
-                                            PixelFormat format,
-                                            bool allowColorConversion)
+  bool DicomImageDecoder::TruncateDecodedImage(std::auto_ptr<ImageAccessor>& image,
+                                               PixelFormat format,
+                                               bool allowColorConversion)
   {
-    // TODO Special case for uncompressed images
-    
-    ImageBuffer source;
-    if (!Decode(source, dataset, frame))
-    {
-      return false;
-    }
-
     // If specified, prevent the conversion between color and
     // grayscale images
-    bool isSourceColor = IsColorImage(source.GetFormat());
+    bool isSourceColor = IsColorImage(image->GetFormat());
     bool isTargetColor = IsColorImage(format);
 
     if (!allowColorConversion)
@@ -615,43 +599,25 @@
       }
     }
 
-    if (source.GetFormat() == format)
+    if (image->GetFormat() != format)
     {
-      // No conversion is required, return the temporary image
-      target.AcquireOwnership(source);
-      return true;
+      // A conversion is required
+      std::auto_ptr<ImageAccessor> target(new Image(format, image->GetWidth(), image->GetHeight()));
+      ImageProcessing::Convert(*target, *image);
+      image = target;
     }
 
-    target.SetFormat(format);
-    target.SetWidth(source.GetWidth());
-    target.SetHeight(source.GetHeight());
-
-    ImageAccessor targetAccessor(target.GetAccessor());
-    ImageAccessor sourceAccessor(source.GetAccessor());
-    ImageProcessing::Convert(targetAccessor, sourceAccessor);
-
     return true;
   }
 
 
-  bool DicomImageDecoder::DecodePreview(ImageBuffer& target,
-                                        DcmDataset& dataset,
-                                        unsigned int frame)
+  bool DicomImageDecoder::PreviewDecodedImage(std::auto_ptr<ImageAccessor>& image)
   {
-    // TODO Special case for uncompressed images
-    
-    ImageBuffer source;
-    if (!Decode(source, dataset, frame))
-    {
-      return false;
-    }
-
-    switch (source.GetFormat())
+    switch (image->GetFormat())
     {
       case PixelFormat_RGB24:
       {
-        // Directly return color images (RGB)
-        target.AcquireOwnership(source);
+        // Directly return color images without modification (RGB)
         return true;
       }
 
@@ -660,32 +626,24 @@
       case PixelFormat_SignedGrayscale16:
       {
         // Grayscale image: Stretch its dynamics to the [0,255] range
-        target.SetFormat(PixelFormat_Grayscale8);
-        target.SetWidth(source.GetWidth());
-        target.SetHeight(source.GetHeight());
+        int64_t a, b;
+        ImageProcessing::GetMinMaxValue(a, b, *image);
 
-        ImageAccessor targetAccessor(target.GetAccessor());
-        ImageAccessor sourceAccessor(source.GetAccessor());
-
-        int64_t a, b;
-        ImageProcessing::GetMinMaxValue(a, b, sourceAccessor);
-        
         if (a == b)
         {
-          ImageProcessing::Set(targetAccessor, 0);
+          ImageProcessing::Set(*image, 0);
         }
         else
         {
-          ImageProcessing::ShiftScale(sourceAccessor, static_cast<float>(-a), 255.0f / static_cast<float>(b - a));
+          ImageProcessing::ShiftScale(*image, static_cast<float>(-a), 255.0f / static_cast<float>(b - a));
+        }
 
-          if (source.GetFormat() == PixelFormat_Grayscale8)
-          {
-            target.AcquireOwnership(source);
-          }
-          else
-          {
-            ImageProcessing::Convert(targetAccessor, sourceAccessor);
-          }
+        // If the source image is not grayscale 8bpp, convert it
+        if (image->GetFormat() != PixelFormat_Grayscale8)
+        {
+          std::auto_ptr<ImageAccessor> target(new Image(PixelFormat_Grayscale8, image->GetWidth(), image->GetHeight()));
+          ImageProcessing::Convert(*target, *image);
+          image = target;
         }
 
         return true;
--- a/OrthancServer/Internals/DicomImageDecoder.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/Internals/DicomImageDecoder.h	Wed Dec 02 09:52:56 2015 +0100
@@ -32,51 +32,44 @@
 
 #pragma once
 
-#include <dcmtk/dcmdata/dcfilefo.h>
+#include <memory>
 
-#include "../../Core/Images/ImageBuffer.h"
+#include "../IDicomImageDecoder.h"
+
+class DcmDataset;
 
 namespace Orthanc
 {
-  class DicomImageDecoder
+  class DicomImageDecoder : public IDicomImageDecoder
   {
   private:
     class ImageSource;
 
-    static void DecodeUncompressedImageInternal(ImageBuffer& target,
-                                                DcmDataset& dataset,
-                                                unsigned int frame);
+    static ImageAccessor* DecodeUncompressedImageInternal(DcmDataset& dataset,
+                                                          unsigned int frame);
 
     static bool IsPsmctRle1(DcmDataset& dataset);
 
-    static void SetupImageBuffer(ImageBuffer& target,
-                                 DcmDataset& dataset);
+    static ImageAccessor* CreateImage(DcmDataset& dataset);
 
     static bool IsUncompressedImage(const DcmDataset& dataset);
 
-    static void DecodeUncompressedImage(ImageBuffer& target,
-                                        DcmDataset& dataset,
-                                        unsigned int frame);
+    static ImageAccessor* DecodeUncompressedImage(DcmDataset& dataset,
+                                                  unsigned int frame);
 
 #if ORTHANC_JPEG_LOSSLESS_ENABLED == 1
-    static void DecodeJpegLossless(ImageBuffer& target,
-                                   DcmDataset& dataset,
-                                   unsigned int frame);
+    static ImageAccessor* DecodeJpegLossless(DcmDataset& dataset,
+                                             unsigned int frame);
 #endif
 
   public:
-    static bool Decode(ImageBuffer& target,
-                       DcmDataset& dataset,
-                       unsigned int frame);
+    virtual ImageAccessor *Decode(ParsedDicomFile& dicom,
+                                  unsigned int frame);
 
-    static bool DecodeAndTruncate(ImageBuffer& target,
-                                  DcmDataset& dataset,
-                                  unsigned int frame,
-                                  PixelFormat format,
-                                  bool allowColorConversion);
+    static bool TruncateDecodedImage(std::auto_ptr<ImageAccessor>& image,
+                                     PixelFormat format,
+                                     bool allowColorConversion);
 
-    static bool DecodePreview(ImageBuffer& target,
-                              DcmDataset& dataset,
-                              unsigned int frame);
+    static bool PreviewDecodedImage(std::auto_ptr<ImageAccessor>& image);
   };
 }
--- a/OrthancServer/Internals/FindScp.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/Internals/FindScp.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -87,7 +87,7 @@
 #include "../../Core/Logging.h"
 #include "../../Core/OrthancException.h"
 
-
+#include <dcmtk/dcmdata/dcfilefo.h>
 
 namespace Orthanc
 {
@@ -95,13 +95,13 @@
   {  
     struct FindScpData
     {
-      IFindRequestHandler* handler_;
-      DicomMap input_;
+      IFindRequestHandler* findHandler_;
+      IWorklistRequestHandler* worklistHandler_;
       DicomFindAnswers answers_;
       DcmDataset* lastRequest_;
       const std::string* remoteIp_;
       const std::string* remoteAet_;
-      bool noCroppingOfResults_;
+      const std::string* calledAet_;
     };
 
 
@@ -120,20 +120,55 @@
       bzero(response, sizeof(T_DIMSE_C_FindRSP));
       *statusDetail = NULL;
 
+      std::string sopClassUid(request->AffectedSOPClassUID);
+
       FindScpData& data = *reinterpret_cast<FindScpData*>(callbackData);
       if (data.lastRequest_ == NULL)
       {
-        FromDcmtkBridge::Convert(data.input_, *requestIdentifiers);
+        bool ok = false;
 
         try
         {
-          data.noCroppingOfResults_ = data.handler_->Handle(data.answers_, data.input_, 
-                                                            *data.remoteIp_, *data.remoteAet_);
+          if (sopClassUid == UID_FINDModalityWorklistInformationModel)
+          {
+            if (data.worklistHandler_ != NULL)
+            {
+              ParsedDicomFile query(*requestIdentifiers);
+              data.worklistHandler_->Handle(data.answers_, query,
+                                            *data.remoteIp_, *data.remoteAet_,
+                                            *data.calledAet_);
+              ok = true;
+            }
+            else
+            {
+              LOG(ERROR) << "No worklist handler is installed, cannot handle this C-FIND request";
+            }
+          }
+          else
+          {
+            if (data.findHandler_ != NULL)
+            {
+              DicomMap input;
+              FromDcmtkBridge::Convert(input, *requestIdentifiers);
+              data.findHandler_->Handle(data.answers_, input,
+                                        *data.remoteIp_, *data.remoteAet_,
+                                        *data.calledAet_);
+              ok = true;
+            }
+            else
+            {
+              LOG(ERROR) << "No C-Find handler is installed, cannot handle this request";
+            }
+          }
         }
         catch (OrthancException& e)
         {
           // Internal error!
           LOG(ERROR) <<  "C-FIND request handler has failed: " << e.What();
+        }
+
+        if (!ok)
+        {
           response->DimseStatus = STATUS_FIND_Failed_UnableToProcess;
           *responseIdentifiers = NULL;   
           return;
@@ -153,9 +188,9 @@
       {
         // There are pending results that are still to be sent
         response->DimseStatus = STATUS_Pending;
-        *responseIdentifiers = ToDcmtkBridge::Convert(data.answers_.GetAnswer(responseCount - 1));
+        *responseIdentifiers = data.answers_.ExtractDcmDataset(responseCount - 1);
       }
-      else if (data.noCroppingOfResults_)
+      else if (data.answers_.IsComplete())
       {
         // Success: All the results have been sent
         response->DimseStatus = STATUS_Success;
@@ -175,16 +210,19 @@
   OFCondition Internals::findScp(T_ASC_Association * assoc, 
                                  T_DIMSE_Message * msg, 
                                  T_ASC_PresentationContextID presID,
-                                 IFindRequestHandler& handler,
+                                 IFindRequestHandler* findHandler,
+                                 IWorklistRequestHandler* worklistHandler,
                                  const std::string& remoteIp,
-                                 const std::string& remoteAet)
+                                 const std::string& remoteAet,
+                                 const std::string& calledAet)
   {
     FindScpData data;
     data.lastRequest_ = NULL;
-    data.handler_ = &handler;
+    data.findHandler_ = findHandler;
+    data.worklistHandler_ = worklistHandler;
     data.remoteIp_ = &remoteIp;
     data.remoteAet_ = &remoteAet;
-    data.noCroppingOfResults_ = true;
+    data.calledAet_ = &calledAet;
 
     OFCondition cond = DIMSE_findProvider(assoc, presID, &msg->msg.CFindRQ, 
                                           FindScpCallback, &data,
--- a/OrthancServer/Internals/FindScp.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/Internals/FindScp.h	Wed Dec 02 09:52:56 2015 +0100
@@ -33,6 +33,7 @@
 #pragma once
 
 #include "../DicomProtocol/IFindRequestHandler.h"
+#include "../DicomProtocol/IWorklistRequestHandler.h"
 
 #include <dcmtk/dcmnet/dimse.h>
 
@@ -43,8 +44,10 @@
     OFCondition findScp(T_ASC_Association * assoc, 
                         T_DIMSE_Message * msg, 
                         T_ASC_PresentationContextID presID,
-                        IFindRequestHandler& handler,
+                        IFindRequestHandler* findHandler,   // can be NULL
+                        IWorklistRequestHandler* worklistHandler,   // can be NULL
                         const std::string& remoteIp,
-                        const std::string& remoteAet);
+                        const std::string& remoteAet,
+                        const std::string& calledAet);
   }
 }
--- a/OrthancServer/Internals/MoveScp.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/Internals/MoveScp.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -98,7 +98,6 @@
     {
       std::string target_;
       IMoveRequestHandler* handler_;
-      DicomMap input_;
       DcmDataset* lastRequest_;
       unsigned int subOperationCount_;
       unsigned int failureCount_;
@@ -106,6 +105,7 @@
       std::auto_ptr<IMoveRequestIterator> iterator_;
       const std::string* remoteIp_;
       const std::string* remoteAet_;
+      const std::string* calledAet_;
     };
 
 
@@ -128,12 +128,14 @@
       MoveScpData& data = *reinterpret_cast<MoveScpData*>(callbackData);
       if (data.lastRequest_ == NULL)
       {
-        FromDcmtkBridge::Convert(data.input_, *requestIdentifiers);
+        DicomMap input;
+        FromDcmtkBridge::Convert(input, *requestIdentifiers);
 
         try
         {
-          data.iterator_.reset(data.handler_->Handle(data.target_, data.input_, 
-                                                     *data.remoteIp_, *data.remoteAet_));
+          data.iterator_.reset(data.handler_->Handle(data.target_, input,
+                                                     *data.remoteIp_, *data.remoteAet_,
+                                                     *data.calledAet_));
 
           if (data.iterator_.get() == NULL)
           {
@@ -215,7 +217,8 @@
                                  T_ASC_PresentationContextID presID,
                                  IMoveRequestHandler& handler,
                                  const std::string& remoteIp,
-                                 const std::string& remoteAet)
+                                 const std::string& remoteAet,
+                                 const std::string& calledAet)
   {
     MoveScpData data;
     data.target_ = std::string(msg->msg.CMoveRQ.MoveDestination);
@@ -223,6 +226,7 @@
     data.handler_ = &handler;
     data.remoteIp_ = &remoteIp;
     data.remoteAet_ = &remoteAet;
+    data.calledAet_ = &calledAet;
 
     OFCondition cond = DIMSE_moveProvider(assoc, presID, &msg->msg.CMoveRQ, 
                                           MoveScpCallback, &data,
--- a/OrthancServer/Internals/MoveScp.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/Internals/MoveScp.h	Wed Dec 02 09:52:56 2015 +0100
@@ -45,6 +45,7 @@
                         T_ASC_PresentationContextID presID,
                         IMoveRequestHandler& handler,
                         const std::string& remoteIp,
-                        const std::string& remoteAet);
+                        const std::string& remoteAet,
+                        const std::string& calledAet);
   }
 }
--- a/OrthancServer/Internals/StoreScp.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/Internals/StoreScp.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -171,7 +171,7 @@
             FromDcmtkBridge::ToJson(dicomJson, **imageDataSet,
                                     DicomToJsonFormat_Full, 
                                     DicomToJsonFlags_Default, 
-                                    256 /* max string length */);
+                                    ORTHANC_MAXIMUM_TAG_LENGTH);
 
             if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, **imageDataSet))
             {
--- a/OrthancServer/OrthancFindRequestHandler.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/OrthancFindRequestHandler.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -88,13 +88,50 @@
   }
 
 
-  bool OrthancFindRequestHandler::Handle(DicomFindAnswers& answers,
+
+  bool OrthancFindRequestHandler::FilterQueryTag(std::string& value /* can be modified */,
+                                                 ResourceType level,
+                                                 const DicomTag& tag,
+                                                 ModalityManufacturer manufacturer)
+  {
+    switch (manufacturer)
+    {
+      case ModalityManufacturer_EFilm2:
+        // Following Denis Nesterov's mail on 2015-11-30
+        if (tag == DicomTag(0x0008, 0x0000) ||  // "GenericGroupLength"
+            tag == DicomTag(0x0010, 0x0000) ||  // "GenericGroupLength"
+            tag == DicomTag(0x0020, 0x0000))    // "GenericGroupLength"
+        {
+          return false;
+        }
+
+        break;
+
+      case ModalityManufacturer_Vitrea:
+        // Following Denis Nesterov's mail on 2015-11-30
+        if (tag == DicomTag(0x5653, 0x0010))  // "PrivateCreator = Vital Images SW 3.4"
+        {
+          return false;
+        }
+
+        break;
+
+      default:
+        break;
+    }
+
+    return true;
+  }
+
+
+  void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers,
                                          const DicomMap& input,
                                          const std::string& remoteIp,
-                                         const std::string& remoteAet)
+                                         const std::string& remoteAet,
+                                         const std::string& calledAet)
   {
     /**
-     * Ensure that the calling modality is known to Orthanc.
+     * Ensure that the remote modality is known to Orthanc.
      **/
 
     RemoteModalityParameters modality;
@@ -104,8 +141,6 @@
       throw OrthancException(ErrorCode_UnknownModality);
     }
 
-    // ModalityManufacturer manufacturer = modality.GetManufacturer();
-
     bool caseSensitivePN = Configuration::GetGlobalBoolParameter("CaseSensitivePN", false);
 
 
@@ -118,6 +153,7 @@
         levelTmp->IsNull() ||
         levelTmp->IsBinary())
     {
+      LOG(ERROR) << "C-FIND request without the tag 0008,0052 (QueryRetrieveLevel)";
       throw OrthancException(ErrorCode_BadRequest);
     }
 
@@ -170,17 +206,25 @@
         continue;
       }
 
-      ValueRepresentation vr = FromDcmtkBridge::GetValueRepresentation(tag);
+      if (FilterQueryTag(value, level, tag, modality.GetManufacturer()))
+      {
+        ValueRepresentation vr = FromDcmtkBridge::GetValueRepresentation(tag);
 
-      // DICOM specifies that searches must be case sensitive, except
-      // for tags with a PN value representation
-      bool sensitive = true;
-      if (vr == ValueRepresentation_PatientName)
+        // DICOM specifies that searches must be case sensitive, except
+        // for tags with a PN value representation
+        bool sensitive = true;
+        if (vr == ValueRepresentation_PatientName)
+        {
+          sensitive = caseSensitivePN;
+        }
+
+        finder.AddDicomConstraint(tag, value, sensitive);
+      }
+      else
       {
-        sensitive = caseSensitivePN;
+        LOG(INFO) << "Because of a patch for the manufacturer of the remote modality, " 
+                  << "ignoring constraint on tag (" << tag.Format() << ") " << FromDcmtkBridge::GetName(tag);
       }
-
-      finder.AddDicomConstraint(tag, value, sensitive);
     }
 
 
@@ -194,7 +238,7 @@
     context_.GetIndex().FindCandidates(resources, instances, finder);
 
     assert(resources.size() == instances.size());
-    bool finished = true;
+    bool complete = true;
 
     for (size_t i = 0; i < instances.size(); i++)
     {
@@ -206,7 +250,7 @@
         if (maxResults != 0 &&
             answers.GetSize() >= maxResults)
         {
-          finished = false;
+          complete = false;
           break;
         }
         else
@@ -218,6 +262,6 @@
 
     LOG(INFO) << "Number of matching resources: " << answers.GetSize();
 
-    return finished;
+    answers.SetComplete(complete);
   }
 }
--- a/OrthancServer/OrthancFindRequestHandler.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/OrthancFindRequestHandler.h	Wed Dec 02 09:52:56 2015 +0100
@@ -47,6 +47,11 @@
     bool HasReachedLimit(const DicomFindAnswers& answers,
                          ResourceType level) const;
 
+    bool FilterQueryTag(std::string& value /* can be modified */,
+                        ResourceType level,
+                        const DicomTag& tag,
+                        ModalityManufacturer manufacturer);
+
   public:
     OrthancFindRequestHandler(ServerContext& context) :
       context_(context), 
@@ -55,10 +60,11 @@
     {
     }
 
-    virtual bool Handle(DicomFindAnswers& answers,
+    virtual void Handle(DicomFindAnswers& answers,
                         const DicomMap& input,
                         const std::string& remoteIp,
-                        const std::string& remoteAet);
+                        const std::string& remoteAet,
+                        const std::string& calledAet);
 
     unsigned int GetMaxResults() const
     {
--- a/OrthancServer/OrthancMoveRequestHandler.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/OrthancMoveRequestHandler.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -165,7 +165,8 @@
   IMoveRequestIterator* OrthancMoveRequestHandler::Handle(const std::string& targetAet,
                                                           const DicomMap& input,
                                                           const std::string& remoteIp,
-                                                          const std::string& remoteAet)
+                                                          const std::string& remoteAet,
+                                                          const std::string& calledAet)
   {
     LOG(WARNING) << "Move-SCU request received for AET \"" << targetAet << "\"";
 
--- a/OrthancServer/OrthancMoveRequestHandler.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/OrthancMoveRequestHandler.h	Wed Dec 02 09:52:56 2015 +0100
@@ -54,6 +54,7 @@
     virtual IMoveRequestIterator* Handle(const std::string& targetAet,
                                          const DicomMap& input,
                                          const std::string& remoteIp,
-                                         const std::string& remoteAet);
+                                         const std::string& remoteAet,
+                                         const std::string& calledAet);
   };
 }
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -620,7 +620,7 @@
       throw OrthancException(ErrorCode_BadRequest);
     }
 
-    ParsedDicomFile dicom;
+    ParsedDicomFile dicom(true);
 
     {
       Encoding encoding;
@@ -834,7 +834,7 @@
     else
     {
       // Compatibility with Orthanc <= 0.9.3
-      ParsedDicomFile dicom;
+      ParsedDicomFile dicom(true);
       CreateDicomV1(dicom, call, request);
 
       std::string id;
--- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -451,8 +451,10 @@
       ArchiveWriterVisitor(HierarchicalZipWriter& writer,
                            ServerContext& context) :
         writer_(writer),
-        context_(context)
+        context_(context),
+        countInstances_(0)
       {
+        snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%%08d.dcm");
       }
 
       virtual void Open(ResourceType level,
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -280,6 +280,18 @@
   }
 
 
+  static void CopyTagIfExists(DicomMap& target,
+                              ParsedDicomFile& source,
+                              const DicomTag& tag)
+  {
+    std::string tmp;
+    if (source.GetTagValue(tmp, tag))
+    {
+      target.SetValue(tag, tmp);
+    }
+  }
+
+
   static void DicomFind(RestApiPostCall& call)
   {
     LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri();
@@ -303,15 +315,16 @@
     Json::Value result = Json::arrayValue;
     for (size_t i = 0; i < patients.GetSize(); i++)
     {
-      Json::Value patient(Json::objectValue);
-      FromDcmtkBridge::ToJson(patient, patients.GetAnswer(i), true);
+      Json::Value patient;
+      patients.ToJson(patient, i, true);
 
       DicomMap::SetupFindStudyTemplate(m);
       if (!MergeQueryAndTemplate(m, call.GetBodyData(), call.GetBodySize()))
       {
         return;
       }
-      m.CopyTagIfExists(patients.GetAnswer(i), DICOM_TAG_PATIENT_ID);
+
+      CopyTagIfExists(m, patients.GetAnswer(i), DICOM_TAG_PATIENT_ID);
 
       DicomFindAnswers studies;
       FindStudy(studies, locker.GetConnection(), m);
@@ -321,16 +334,17 @@
       // Loop over the found studies
       for (size_t j = 0; j < studies.GetSize(); j++)
       {
-        Json::Value study(Json::objectValue);
-        FromDcmtkBridge::ToJson(study, studies.GetAnswer(j), true);
+        Json::Value study;
+        studies.ToJson(study, j, true);
 
         DicomMap::SetupFindSeriesTemplate(m);
         if (!MergeQueryAndTemplate(m, call.GetBodyData(), call.GetBodySize()))
         {
           return;
         }
-        m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_PATIENT_ID);
-        m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID);
+
+        CopyTagIfExists(m, studies.GetAnswer(j), DICOM_TAG_PATIENT_ID);
+        CopyTagIfExists(m, studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID);
 
         DicomFindAnswers series;
         FindSeries(series, locker.GetConnection(), m);
@@ -339,8 +353,8 @@
         study["Series"] = Json::arrayValue;
         for (size_t k = 0; k < series.GetSize(); k++)
         {
-          Json::Value series2(Json::objectValue);
-          FromDcmtkBridge::ToJson(series2, series.GetAnswer(k), true);
+          Json::Value series2;
+          series.ToJson(series2, k, true);
           study["Series"].append(series2);
         }
 
@@ -465,8 +479,13 @@
   static void GetQueryOneAnswer(RestApiGetCall& call)
   {
     size_t index = boost::lexical_cast<size_t>(call.GetUriComponent("index", ""));
+
     QueryAccessor query(call);
-    AnswerDicomMap(call, query->GetAnswer(index), call.HasArgument("simplify"));
+
+    DicomMap map;
+    query->GetAnswer(map, index);
+
+    AnswerDicomMap(call, map, call.HasArgument("simplify"));
   }
 
 
@@ -547,7 +566,9 @@
 
     // Ensure that the answer of interest does exist
     size_t index = boost::lexical_cast<size_t>(call.GetUriComponent("index", ""));
-    query->GetAnswer(index);
+
+    DicomMap map;
+    query->GetAnswer(map, index);
 
     RestApi::AutoListChildren(call);
   }
@@ -844,6 +865,32 @@
   }
 
 
+  static void DicomFindWorklist(RestApiPostCall& call)
+  {
+    ServerContext& context = OrthancRestApi::GetContext(call);
+
+    Json::Value json;
+    if (call.ParseJsonRequest(json))
+    {
+      const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle();
+      RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
+
+      std::auto_ptr<ParsedDicomFile> query(ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(0)));
+
+      DicomFindAnswers answers;
+
+      {
+        ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote);
+        locker.GetConnection().FindWorklist(answers, *query);
+      }
+
+      Json::Value result;
+      answers.ToJson(result, true);
+      call.GetOutput().AnswerJson(result);
+    }
+  }
+
+
   void OrthancRestApi::RegisterModalities()
   {
     Register("/modalities", ListModalities);
@@ -877,5 +924,7 @@
     Register("/peers/{id}", UpdatePeer);
     Register("/peers/{id}", DeletePeer);
     Register("/peers/{id}/store", PeerStore);
+
+    Register("/modalities/{id}/find-worklist", DicomFindWorklist);
   }
 }
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -39,6 +39,7 @@
 #include "../FromDcmtkBridge.h"
 #include "../ServerContext.h"
 #include "../SliceOrdering.h"
+#include "../Internals/DicomImageDecoder.h"
 
 
 namespace Orthanc
@@ -265,6 +266,7 @@
     class ImageToEncode
     {
     private:
+      IDicomImageDecoder& decoder_;
       std::string         format_;
       std::string         encoded_;
       ParsedDicomFile&    dicom_;
@@ -272,9 +274,11 @@
       ImageExtractionMode mode_;
 
     public:
-      ImageToEncode(ParsedDicomFile& dicom,
+      ImageToEncode(IDicomImageDecoder& decoder,
+                    ParsedDicomFile& dicom,
                     unsigned int frame,
                     ImageExtractionMode mode) : 
+        decoder_(decoder),
         dicom_(dicom),
         frame_(frame),
         mode_(mode)
@@ -310,6 +314,11 @@
       {
         output.AnswerBuffer(encoded_, format_);
       }
+
+      IDicomImageDecoder& GetDecoder() const
+      {
+        return decoder_;
+      }
     };
 
     class EncodePng : public HttpContentNegociation::IHandler
@@ -327,7 +336,8 @@
       {
         assert(type == "image");
         assert(subtype == "png");
-        image_.GetDicom().ExtractPngImage(image_.GetTarget(), image_.GetFrame(), image_.GetMode());
+        image_.GetDicom().ExtractPngImage(image_.GetTarget(), image_.GetDecoder(), 
+                                          image_.GetFrame(), image_.GetMode());
         image_.SetFormat("image/png");
       }
     };
@@ -349,7 +359,7 @@
         try
         {
           quality_ = boost::lexical_cast<unsigned int>(v);
-          ok = (quality_ >= 0 && quality_ <= 100);
+          ok = (quality_ >= 1 && quality_ <= 100);
         }
         catch (boost::bad_lexical_cast&)
         {
@@ -367,7 +377,8 @@
       {
         assert(type == "image");
         assert(subtype == "jpeg");
-        image_.GetDicom().ExtractJpegImage(image_.GetTarget(), image_.GetFrame(), image_.GetMode(), quality_);
+        image_.GetDicom().ExtractJpegImage(image_.GetTarget(), image_.GetDecoder(), 
+                                           image_.GetFrame(), image_.GetMode(), quality_);
         image_.SetFormat("image/jpeg");
       }
     };
@@ -399,7 +410,13 @@
 
     try
     {
-      ImageToEncode image(dicom, frame, mode);
+#if ORTHANC_PLUGINS_ENABLED == 1
+      IDicomImageDecoder& decoder = context.GetPlugins();
+#else
+      DicomImageDecoder decoder;  // This is Orthanc's built-in decoder
+#endif
+
+      ImageToEncode image(decoder, dicom, frame, mode);
 
       HttpContentNegociation negociation;
       EncodePng png(image);          negociation.Register("image/png", png);
@@ -451,14 +468,17 @@
     std::string dicomContent;
     context.ReadFile(dicomContent, publicId, FileContentType_Dicom);
 
+#if ORTHANC_PLUGINS_ENABLED == 1
+    IDicomImageDecoder& decoder = context.GetPlugins();
+#else
+    DicomImageDecoder decoder;  // This is Orthanc's built-in decoder
+#endif
+
     ParsedDicomFile dicom(dicomContent);
-    ImageBuffer buffer;
-    dicom.ExtractImage(buffer, frame);
-
-    ImageAccessor accessor(buffer.GetConstAccessor());
+    std::auto_ptr<ImageAccessor> decoded(dicom.ExtractImage(decoder, frame));
 
     std::string result;
-    accessor.ToMatlabString(result);
+    decoded->ToMatlabString(result);
 
     call.GetOutput().AnswerBuffer(result, "text/plain");
   }
@@ -1077,11 +1097,13 @@
       size_t limit = 0;
       if (request.isMember("Limit"))
       {
-        limit = request["CaseSensitive"].asInt();
-        if (limit < 0)
+        int tmp = request["CaseSensitive"].asInt();
+        if (tmp < 0)
         {
           throw OrthancException(ErrorCode_ParameterOutOfRange);
         }
+
+        limit = static_cast<size_t>(tmp);
       }
 
       std::string level = request["Level"].asString();
@@ -1255,6 +1277,34 @@
   }
 
 
+  static void GetInstanceHeader(RestApiGetCall& call)
+  {
+    ServerContext& context = OrthancRestApi::GetContext(call);
+
+    std::string publicId = call.GetUriComponent("id", "");
+    bool simplify = call.HasArgument("simplify");
+
+    std::string dicomContent;
+    context.ReadFile(dicomContent, publicId, FileContentType_Dicom);
+
+    ParsedDicomFile dicom(dicomContent);
+
+    Json::Value header;
+    dicom.HeaderToJson(header, DicomToJsonFormat_Full);
+
+    if (simplify)
+    {
+      Json::Value simplified;
+      Toolbox::SimplifyTags(simplified, header);
+      call.GetOutput().AnswerJson(simplified);
+    }
+    else
+    {
+      call.GetOutput().AnswerJson(header);
+    }
+  }
+
+
   void OrthancRestApi::RegisterResources()
   {
     Register("/instances", ListResources<ResourceType_Instance>);
@@ -1303,6 +1353,7 @@
     Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>);
     Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>);
     Register("/instances/{id}/matlab", GetMatlabImage);
+    Register("/instances/{id}/header", GetInstanceHeader);
 
     Register("/patients/{id}/protected", IsProtectedPatient);
     Register("/patients/{id}/protected", SetPatientProtection);
--- a/OrthancServer/ParsedDicomFile.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/ParsedDicomFile.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -80,12 +80,12 @@
 
 #include "ParsedDicomFile.h"
 
+#include "OrthancInitialization.h"
 #include "ServerToolbox.h"
 #include "FromDcmtkBridge.h"
 #include "ToDcmtkBridge.h"
 #include "Internals/DicomImageDecoder.h"
 #include "../Core/DicomFormat/DicomIntegerPixelAccessor.h"
-#include "../Core/Images/ImageBuffer.h"
 #include "../Core/Images/JpegWriter.h"
 #include "../Core/Images/JpegReader.h"
 #include "../Core/Images/PngReader.h"
@@ -153,7 +153,8 @@
 
 
   // This method can only be called from the constructors!
-  void ParsedDicomFile::Setup(const char* buffer, size_t size)
+  void ParsedDicomFile::Setup(const void* buffer, 
+                              size_t size)
   {
     DcmInputBufferStream is;
     if (size > 0)
@@ -592,9 +593,9 @@
 
   void ParsedDicomFile::Insert(const DicomTag& tag,
                                const Json::Value& value,
-                               bool decodeBinaryTags)
+                               bool decodeDataUriScheme)
   {
-    std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeBinaryTags, GetEncoding()));
+    std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding()));
     InsertInternal(*pimpl_->file_->getDataset(), element.release());
   }
 
@@ -629,7 +630,7 @@
 
   void ParsedDicomFile::UpdateStorageUid(const DicomTag& tag,
                                          const std::string& utf8Value,
-                                         bool decodeBinaryTags)
+                                         bool decodeDataUriScheme)
   {
     if (tag != DICOM_TAG_SOP_CLASS_UID &&
         tag != DICOM_TAG_SOP_INSTANCE_UID)
@@ -640,7 +641,7 @@
     std::string binary;
     const std::string* decoded = &utf8Value;
 
-    if (decodeBinaryTags &&
+    if (decodeDataUriScheme &&
         boost::starts_with(utf8Value, "data:application/octet-stream;base64,"))
     {
       std::string mime;
@@ -691,10 +692,10 @@
     
   void ParsedDicomFile::Replace(const DicomTag& tag,
                                 const Json::Value& value,
-                                bool decodeBinaryTags,
+                                bool decodeDataUriScheme,
                                 DicomReplaceMode mode)
   {
-    std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeBinaryTags, GetEncoding()));
+    std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding()));
     ReplaceInternal(*pimpl_->file_->getDataset(), element, mode);
 
     if (tag == DICOM_TAG_SOP_CLASS_UID ||
@@ -705,7 +706,7 @@
         throw OrthancException(ErrorCode_BadParameterType);
       }
 
-      UpdateStorageUid(tag, value.asString(), decodeBinaryTags);
+      UpdateStorageUid(tag, value.asString(), decodeDataUriScheme);
     }
   }
 
@@ -727,7 +728,7 @@
     DcmTagKey k(tag.GetGroup(), tag.GetElement());
     DcmDataset& dataset = *pimpl_->file_->getDataset();
 
-    if (FromDcmtkBridge::IsPrivateTag(tag) ||
+    if (tag.IsPrivate() ||
         FromDcmtkBridge::IsUnknownTag(tag) ||
         tag == DICOM_TAG_PIXEL_DATA ||
         tag == DICOM_TAG_ENCAPSULATED_DOCUMENT)
@@ -813,17 +814,34 @@
   }
 
 
-  ParsedDicomFile::ParsedDicomFile() : pimpl_(new PImpl)
+  ParsedDicomFile::ParsedDicomFile(bool createIdentifiers) : pimpl_(new PImpl)
   {
     pimpl_->file_.reset(new DcmFileFormat);
-    Replace(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient));
-    Replace(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study));
-    Replace(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series));
-    Replace(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
+
+    if (createIdentifiers)
+    {
+      Replace(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient));
+      Replace(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study));
+      Replace(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series));
+      Replace(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
+    }
   }
 
 
-  ParsedDicomFile::ParsedDicomFile(const char* content, size_t size) : pimpl_(new PImpl)
+  ParsedDicomFile::ParsedDicomFile(const DicomMap& map) : pimpl_(new PImpl)
+  {
+    std::auto_ptr<DcmDataset> dataset(ToDcmtkBridge::Convert(map));
+
+    // NOTE: This implies an unnecessary memory copy of the dataset, but no way to get around
+    // http://support.dcmtk.org/redmine/issues/544
+    std::auto_ptr<DcmFileFormat> fileFormat(new DcmFileFormat(dataset.get()));
+
+    pimpl_->file_.reset(fileFormat.release());
+  }
+
+
+  ParsedDicomFile::ParsedDicomFile(const void* content, 
+                                   size_t size) : pimpl_(new PImpl)
   {
     Setup(content, size);
   }
@@ -851,15 +869,27 @@
   }
 
 
+  ParsedDicomFile::ParsedDicomFile(DcmDataset& dicom) : pimpl_(new PImpl)
+  {
+    pimpl_->file_.reset(new DcmFileFormat(&dicom));
+  }
+
+
+  ParsedDicomFile::ParsedDicomFile(DcmFileFormat& dicom) : pimpl_(new PImpl)
+  {
+    pimpl_->file_.reset(new DcmFileFormat(dicom));
+  }
+
+
   ParsedDicomFile::~ParsedDicomFile()
   {
     delete pimpl_;
   }
 
 
-  void* ParsedDicomFile::GetDcmtkObject()
+  DcmFileFormat& ParsedDicomFile::GetDcmtkObject() const
   {
-    return pimpl_->file_.get();
+    return *pimpl_->file_.get();
   }
 
 
@@ -1017,69 +1047,80 @@
   }
 
   
-  void ParsedDicomFile::ExtractImage(ImageBuffer& result,
-                                     unsigned int frame)
+  ImageAccessor* ParsedDicomFile::ExtractImage(IDicomImageDecoder& decoder,
+                                               unsigned int frame)
   {
-    DcmDataset& dataset = *pimpl_->file_->getDataset();
+    std::auto_ptr<ImageAccessor> decoded(decoder.Decode(*this, frame));
 
-    if (!DicomImageDecoder::Decode(result, dataset, frame))
+    if (decoded.get() == NULL)
     {
+      LOG(ERROR) << "Cannot decode a DICOM image";
       throw OrthancException(ErrorCode_BadFileFormat);
     }
+    else
+    {
+      return decoded.release();
+    }
   }
 
 
-  void ParsedDicomFile::ExtractImage(ImageBuffer& result,
-                                     unsigned int frame,
-                                     ImageExtractionMode mode)
+  ImageAccessor* ParsedDicomFile::ExtractImage(IDicomImageDecoder& decoder,
+                                               unsigned int frame,
+                                               ImageExtractionMode mode)
   {
-    DcmDataset& dataset = *pimpl_->file_->getDataset();
+    std::auto_ptr<ImageAccessor> decoded(ExtractImage(decoder, frame));
 
     bool ok = false;
 
     switch (mode)
     {
       case ImageExtractionMode_UInt8:
-        ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_Grayscale8, false);
+        ok = DicomImageDecoder::TruncateDecodedImage(decoded, PixelFormat_Grayscale8, false);
         break;
 
       case ImageExtractionMode_UInt16:
-        ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_Grayscale16, false);
+        ok = DicomImageDecoder::TruncateDecodedImage(decoded, PixelFormat_Grayscale16, false);
         break;
 
       case ImageExtractionMode_Int16:
-        ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_SignedGrayscale16, false);
+        ok = DicomImageDecoder::TruncateDecodedImage(decoded, PixelFormat_SignedGrayscale16, false);
         break;
 
       case ImageExtractionMode_Preview:
-        ok = DicomImageDecoder::DecodePreview(result, dataset, frame);
+        ok = DicomImageDecoder::PreviewDecodedImage(decoded);
         break;
 
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    if (!ok)
+    if (ok)
     {
-      throw OrthancException(ErrorCode_BadFileFormat);
+      assert(decoded.get() != NULL);
+      return decoded.release();
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
     }
   }
 
 
   void ParsedDicomFile::ExtractPngImage(std::string& result,
+                                        IDicomImageDecoder& decoder,
                                         unsigned int frame,
                                         ImageExtractionMode mode)
   {
-    ImageBuffer buffer;
-    ExtractImage(buffer, frame, mode);
+    std::auto_ptr<ImageAccessor> decoded(ExtractImage(decoder, frame, mode));
+    assert(decoded.get() != NULL);
 
-    ImageAccessor accessor(buffer.GetConstAccessor());
     PngWriter writer;
-    writer.WriteToMemory(result, accessor);
+    writer.WriteToMemory(result, *decoded);
   }
 
 
   void ParsedDicomFile::ExtractJpegImage(std::string& result,
+                                         IDicomImageDecoder& decoder,
                                          unsigned int frame,
                                          ImageExtractionMode mode,
                                          uint8_t quality)
@@ -1090,13 +1131,12 @@
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    ImageBuffer buffer;
-    ExtractImage(buffer, frame, mode);
+    std::auto_ptr<ImageAccessor> decoded(ExtractImage(decoder, frame, mode));
+    assert(decoded.get() != NULL);
 
-    ImageAccessor accessor(buffer.GetConstAccessor());
     JpegWriter writer;
     writer.SetQuality(quality);
-    writer.WriteToMemory(result, accessor);
+    writer.WriteToMemory(result, *decoded);
   }
 
 
@@ -1128,6 +1168,13 @@
   }
 
 
+  void ParsedDicomFile::HeaderToJson(Json::Value& target, 
+                                     DicomToJsonFormat format)
+  {
+    FromDcmtkBridge::ToJson(target, *pimpl_->file_->getMetaInfo(), format, DicomToJsonFlags_None, 0);
+  }
+
+
   bool ParsedDicomFile::HasTag(const DicomTag& tag) const
   {
     DcmTag key(tag.GetGroup(), tag.GetElement());
@@ -1222,4 +1269,60 @@
   {
     FromDcmtkBridge::Convert(tags, *pimpl_->file_->getDataset());
   }
+
+
+  ParsedDicomFile* ParsedDicomFile::CreateFromJson(const Json::Value& json,
+                                                   DicomFromJsonFlags flags)
+  {
+    std::string tmp = Configuration::GetGlobalStringParameter("DefaultEncoding", "Latin1");
+    Encoding encoding = StringToEncoding(tmp.c_str());
+
+    Json::Value::Members tags = json.getMemberNames();
+    
+    for (size_t i = 0; i < tags.size(); i++)
+    {
+      DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]);
+      if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET)
+      {
+        const Json::Value& value = json[tags[i]];
+        if (value.type() != Json::stringValue ||
+            !GetDicomEncoding(encoding, value.asCString()))
+        {
+          LOG(ERROR) << "Unknown encoding while creating DICOM from JSON: " << value;
+          throw OrthancException(ErrorCode_BadRequest);
+        }
+      }
+    }
+
+    const bool generateIdentifiers = (flags & DicomFromJsonFlags_GenerateIdentifiers);
+    const bool decodeDataUriScheme = (flags & DicomFromJsonFlags_DecodeDataUriScheme);
+
+    std::auto_ptr<ParsedDicomFile> result(new ParsedDicomFile(generateIdentifiers));
+    result->SetEncoding(encoding);
+
+    for (size_t i = 0; i < tags.size(); i++)
+    {
+      DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]);
+      const Json::Value& value = json[tags[i]];
+
+      if (tag == DICOM_TAG_PIXEL_DATA ||
+          tag == DICOM_TAG_ENCAPSULATED_DOCUMENT)
+      {
+        if (value.type() != Json::stringValue)
+        {
+          throw OrthancException(ErrorCode_BadRequest);
+        }
+        else
+        {
+          result->EmbedContent(value.asString());
+        }
+      }
+      else if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET)
+      {
+        result->Replace(tag, value, decodeDataUriScheme);
+      }
+    }
+
+    return result.release();
+  }
 }
--- a/OrthancServer/ParsedDicomFile.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/ParsedDicomFile.h	Wed Dec 02 09:52:56 2015 +0100
@@ -33,11 +33,13 @@
 #pragma once
 
 #include "../Core/DicomFormat/DicomInstanceHasher.h"
+#include "../Core/IDynamicObject.h"
 #include "../Core/RestApi/RestApiOutput.h"
+#include "IDicomImageDecoder.h"
 #include "ServerEnumerations.h"
-#include "../Core/Images/ImageAccessor.h"
-#include "../Core/Images/ImageBuffer.h"
-#include "../Core/IDynamicObject.h"
+
+class DcmDataset;
+class DcmFileFormat;
 
 namespace Orthanc
 {
@@ -49,26 +51,32 @@
 
     ParsedDicomFile(ParsedDicomFile& other);
 
-    void Setup(const char* content,
+    void Setup(const void* content,
                size_t size);
 
     void RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep);
 
     void UpdateStorageUid(const DicomTag& tag,
                           const std::string& value,
-                          bool decodeBinaryTags);
+                          bool decodeDataUriScheme);
 
   public:
-    ParsedDicomFile();  // Create a minimal DICOM instance
+    ParsedDicomFile(bool createIdentifiers);  // Create a minimal DICOM instance
 
-    ParsedDicomFile(const char* content,
+    ParsedDicomFile(const DicomMap& map);
+
+    ParsedDicomFile(const void* content,
                     size_t size);
 
     ParsedDicomFile(const std::string& content);
 
+    ParsedDicomFile(DcmDataset& dicom);
+
+    ParsedDicomFile(DcmFileFormat& dicom);
+
     ~ParsedDicomFile();
 
-    void* GetDcmtkObject();
+    DcmFileFormat& GetDcmtkObject() const;
 
     ParsedDicomFile* Clone();
 
@@ -85,12 +93,12 @@
 
     void Replace(const DicomTag& tag,
                  const Json::Value& value,  // Assumed to be encoded with UTF-8
-                 bool decodeBinaryTags,
+                 bool decodeDataUriScheme,
                  DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent);
 
     void Insert(const DicomTag& tag,
                 const Json::Value& value,   // Assumed to be encoded with UTF-8
-                bool decodeBinaryTags);
+                bool decodeDataUriScheme);
 
     void RemovePrivateTags()
     {
@@ -118,18 +126,20 @@
     void EmbedImage(const std::string& mime,
                     const std::string& content);
 
-    void ExtractImage(ImageBuffer& result,
-                      unsigned int frame);
+    ImageAccessor* ExtractImage(IDicomImageDecoder& decoder,
+                                unsigned int frame);
 
-    void ExtractImage(ImageBuffer& result,
-                      unsigned int frame,
-                      ImageExtractionMode mode);
+    ImageAccessor* ExtractImage(IDicomImageDecoder& decoder,
+                                unsigned int frame,
+                                ImageExtractionMode mode);
 
     void ExtractPngImage(std::string& result,
+                         IDicomImageDecoder& decoder,
                          unsigned int frame,
                          ImageExtractionMode mode);
 
     void ExtractJpegImage(std::string& result,
+                          IDicomImageDecoder& decoder,
                           unsigned int frame,
                           ImageExtractionMode mode,
                           uint8_t quality);
@@ -143,6 +153,9 @@
                 DicomToJsonFlags flags,
                 unsigned int maxStringLength);
 
+    void HeaderToJson(Json::Value& target, 
+                      DicomToJsonFormat format);
+
     bool HasTag(const DicomTag& tag) const;
 
     void EmbedPdf(const std::string& pdf);
@@ -150,6 +163,9 @@
     bool ExtractPdf(std::string& pdf);
 
     void Convert(DicomMap& tags);
+
+    static ParsedDicomFile* CreateFromJson(const Json::Value& value,
+                                           DicomFromJsonFlags flags);
   };
 
 }
--- a/OrthancServer/QueryRetrieveHandler.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/QueryRetrieveHandler.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -95,31 +95,24 @@
   }
 
 
-  const DicomMap& QueryRetrieveHandler::GetAnswer(size_t i)
+  void QueryRetrieveHandler::GetAnswer(DicomMap& target,
+                                       size_t i)
   {
     Run();
-
-    if (i >= answers_.GetSize())
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    return answers_.GetAnswer(i);
+    answers_.GetAnswer(i).Convert(target);
   }
 
 
   void QueryRetrieveHandler::Retrieve(const std::string& target,
                                       size_t i)
   {
-    Run();
+    DicomMap map;
+    GetAnswer(map, i);
 
-    if (i >= answers_.GetSize())
     {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
+      ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), localAet_, modality_);
+      locker.GetConnection().Move(target, map);
     }
-
-    ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), localAet_, modality_);
-    locker.GetConnection().Move(target, answers_.GetAnswer(i));
   }
 
 
--- a/OrthancServer/QueryRetrieveHandler.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/QueryRetrieveHandler.h	Wed Dec 02 09:52:56 2015 +0100
@@ -85,7 +85,8 @@
 
     size_t GetAnswerCount();
 
-    const DicomMap& GetAnswer(size_t i);
+    void GetAnswer(DicomMap& target,
+                   size_t i);
 
     void Retrieve(const std::string& target,
                   size_t i);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/HierarchicalMatcher.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,329 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersServer.h"
+#include "HierarchicalMatcher.h"
+
+#include "../../Core/OrthancException.h"
+#include "../FromDcmtkBridge.h"
+#include "../ToDcmtkBridge.h"
+
+#include <dcmtk/dcmdata/dcfilefo.h>
+
+namespace Orthanc
+{
+  HierarchicalMatcher::HierarchicalMatcher(ParsedDicomFile& query,
+                                           bool caseSensitivePN)
+  {
+    Setup(*query.GetDcmtkObject().getDataset(), 
+          caseSensitivePN,
+          query.GetEncoding());
+  }
+
+
+  HierarchicalMatcher::~HierarchicalMatcher()
+  {
+    for (Constraints::iterator it = constraints_.begin();
+         it != constraints_.end(); ++it)
+    {
+      if (it->second != NULL)
+      {
+        delete it->second;
+      }
+    }
+
+    for (Sequences::iterator it = sequences_.begin();
+         it != sequences_.end(); ++it)
+    {
+      if (it->second != NULL)
+      {
+        delete it->second;
+      }
+    }
+  }
+
+
+  void HierarchicalMatcher::Setup(DcmItem& dataset,
+                                  bool caseSensitivePN,
+                                  Encoding encoding)
+  {
+    for (unsigned long i = 0; i < dataset.card(); i++)
+    {
+      DcmElement* element = dataset.getElement(i);
+      if (element == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      DicomTag tag(FromDcmtkBridge::Convert(element->getTag()));
+      if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET)
+      {
+        // Ignore this specific tag
+        continue;
+      }
+
+      ValueRepresentation vr = FromDcmtkBridge::GetValueRepresentation(tag);
+
+      if (constraints_.find(tag) != constraints_.end() ||
+          sequences_.find(tag) != sequences_.end())
+      {
+        throw OrthancException(ErrorCode_BadRequest);        
+      }
+
+      if (vr == ValueRepresentation_Sequence)
+      {
+        DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element);
+
+        if (sequence.card() == 0 ||
+            (sequence.card() == 1 && sequence.getItem(0)->card() == 0))
+        {
+          // Universal matching of a sequence
+          sequences_[tag] = NULL;
+        }
+        else if (sequence.card() == 1)
+        {
+          sequences_[tag] = new HierarchicalMatcher(*sequence.getItem(0), caseSensitivePN, encoding);
+        }
+        else
+        {
+          throw OrthancException(ErrorCode_BadRequest);        
+        }
+      }
+      else
+      {
+        std::auto_ptr<DicomValue> value(FromDcmtkBridge::ConvertLeafElement
+                                        (*element, DicomToJsonFlags_None, encoding));
+
+        if (value->IsBinary() ||
+            value->IsNull())
+        {
+          throw OrthancException(ErrorCode_BadRequest);
+        }
+        else if (value->GetContent().empty())
+        {
+          // This is an universal matcher
+          constraints_[tag] = NULL;
+        }
+        else
+        {
+          // DICOM specifies that searches must be case sensitive, except
+          // for tags with a PN value representation
+          bool sensitive = true;
+          if (vr == ValueRepresentation_PatientName)
+          {
+            sensitive = caseSensitivePN;
+          }
+
+          constraints_[tag] = IFindConstraint::ParseDicomConstraint(tag, value->GetContent(), sensitive);
+        }
+      }
+    }
+  }
+
+
+  std::string HierarchicalMatcher::Format(const std::string& prefix) const
+  {
+    std::string s;
+    
+    for (Constraints::const_iterator it = constraints_.begin();
+         it != constraints_.end(); ++it)
+    {
+      s += prefix + it->first.Format() + " ";
+
+      if (it->second == NULL)
+      {
+        s += "*\n";
+      }
+      else
+      {
+        s += it->second->Format() + "\n";
+      }
+    }
+
+    for (Sequences::const_iterator it = sequences_.begin();
+         it != sequences_.end(); ++it)
+    {
+      s += prefix + it->first.Format() + " ";
+
+      if (it->second == NULL)
+      {
+        s += "*\n";
+      }
+      else
+      {
+        s += "Sequence:\n" + it->second->Format(prefix + "  ");
+      }
+    }
+
+    return s;
+  }
+
+
+  bool HierarchicalMatcher::Match(ParsedDicomFile& dicom) const
+  {
+    return MatchInternal(*dicom.GetDcmtkObject().getDataset(),
+                         dicom.GetEncoding());
+  }
+
+
+  bool HierarchicalMatcher::MatchInternal(DcmItem& item,
+                                          Encoding encoding) const
+  {
+    for (Constraints::const_iterator it = constraints_.begin();
+         it != constraints_.end(); ++it)
+    {
+      if (it->second != NULL)
+      {
+        DcmTagKey tag = ToDcmtkBridge::Convert(it->first);
+
+        DcmElement* element = NULL;
+        if (!item.findAndGetElement(tag, element).good() ||
+            element == NULL)
+        {
+          return false;
+        }
+
+        std::auto_ptr<DicomValue> value(FromDcmtkBridge::ConvertLeafElement
+                                        (*element, DicomToJsonFlags_None, encoding));
+
+        if (value->IsNull() ||
+            value->IsBinary() ||
+            !it->second->Match(value->GetContent()))
+        {
+          return false;
+        }
+      }
+    }
+
+    for (Sequences::const_iterator it = sequences_.begin();
+         it != sequences_.end(); ++it)
+    {
+      if (it->second != NULL)
+      {
+        DcmTagKey tag = ToDcmtkBridge::Convert(it->first);
+
+        DcmSequenceOfItems* sequence = NULL;
+        if (!item.findAndGetSequence(tag, sequence).good() ||
+            sequence == NULL)
+        {
+          return true;
+        }
+
+        bool match = false;
+
+        for (unsigned long i = 0; i < sequence->card(); i++)
+        {
+          if (it->second->MatchInternal(*sequence->getItem(i), encoding))
+          {
+            match = true;
+            break;
+          }
+        }
+
+        if (!match)
+        {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+
+  DcmDataset* HierarchicalMatcher::ExtractInternal(DcmItem& source,
+                                                   Encoding encoding) const
+  {
+    std::auto_ptr<DcmDataset> target(new DcmDataset);
+
+    for (Constraints::const_iterator it = constraints_.begin();
+         it != constraints_.end(); ++it)
+    {
+      DcmTagKey tag = ToDcmtkBridge::Convert(it->first);
+      
+      DcmElement* element = NULL;
+      if (source.findAndGetElement(tag, element).good() &&
+          element != NULL)
+      {
+        std::auto_ptr<DcmElement> cloned(FromDcmtkBridge::CreateElementForTag(it->first));
+        cloned->copyFrom(*element);
+        target->insert(cloned.release());
+      }
+    }
+
+    for (Sequences::const_iterator it = sequences_.begin();
+         it != sequences_.end(); ++it)
+    {
+      DcmTagKey tag = ToDcmtkBridge::Convert(it->first);
+
+      DcmSequenceOfItems* sequence = NULL;
+      if (source.findAndGetSequence(tag, sequence).good() &&
+          sequence != NULL)
+      {
+        std::auto_ptr<DcmSequenceOfItems> cloned(new DcmSequenceOfItems(tag));
+
+        for (unsigned long i = 0; i < sequence->card(); i++)
+        {
+          if (it->second == NULL)
+          {
+            cloned->append(new DcmItem(*sequence->getItem(i)));
+          }
+          else if (it->second->MatchInternal(*sequence->getItem(i), encoding))  // TODO Might be optimized
+          {
+            // It is necessary to encapsulate the child dataset into a
+            // "DcmItem" object before it can be included in a
+            // sequence. Otherwise, "dciodvfy" reports an error "Bad
+            // tag in sequence - Expecting Item or Sequence Delimiter."
+            std::auto_ptr<DcmDataset> child(it->second->ExtractInternal(*sequence->getItem(i), encoding));
+            cloned->append(new DcmItem(*child));
+          }
+        }
+
+        target->insert(cloned.release());
+      }
+    }
+
+    return target.release();
+  }
+
+
+  ParsedDicomFile* HierarchicalMatcher::Extract(ParsedDicomFile& dicom) const
+  {
+    std::auto_ptr<DcmDataset> dataset(ExtractInternal(*dicom.GetDcmtkObject().getDataset(),
+                                                      dicom.GetEncoding()));
+
+    std::auto_ptr<ParsedDicomFile> result(new ParsedDicomFile(*dataset));
+    result->SetEncoding(Encoding_Utf8);
+
+    return result.release();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/HierarchicalMatcher.h	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,81 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Core/DicomFormat/DicomMap.h"
+#include "IFindConstraint.h"
+#include "../ParsedDicomFile.h"
+
+class DcmItem;
+
+namespace Orthanc
+{
+  class HierarchicalMatcher : public boost::noncopyable
+  {
+  private:
+    typedef std::map<DicomTag, IFindConstraint*>      Constraints;
+    typedef std::map<DicomTag, HierarchicalMatcher*>  Sequences;
+
+    Constraints  constraints_;
+    Sequences    sequences_;
+
+    void Setup(DcmItem& query,
+               bool caseSensitivePN,
+               Encoding encoding);
+
+    HierarchicalMatcher(DcmItem& query,
+                        bool caseSensitivePN,
+                        Encoding encoding)
+    {
+      Setup(query, caseSensitivePN, encoding);
+    }
+
+    bool MatchInternal(DcmItem& dicom,
+                       Encoding encoding) const;
+
+    DcmDataset* ExtractInternal(DcmItem& dicom,
+                                Encoding encoding) const;
+
+  public:
+    HierarchicalMatcher(ParsedDicomFile& query,
+                        bool caseSensitivePN);
+
+    ~HierarchicalMatcher();
+
+    std::string Format(const std::string& prefix = "") const;
+
+    bool Match(ParsedDicomFile& dicom) const;
+
+    ParsedDicomFile* Extract(ParsedDicomFile& dicom) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/IFindConstraint.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,130 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersServer.h"
+#include "IFindConstraint.h"
+
+#include "ListConstraint.h"
+#include "RangeConstraint.h"
+#include "ValueConstraint.h"
+#include "WildcardConstraint.h"
+
+#include "../FromDcmtkBridge.h"
+#include "../../Core/OrthancException.h"
+
+namespace Orthanc
+{
+  IFindConstraint* IFindConstraint::ParseDicomConstraint(const DicomTag& tag,
+                                                         const std::string& dicomQuery,
+                                                         bool caseSensitive)
+  {
+    ValueRepresentation vr = FromDcmtkBridge::GetValueRepresentation(tag);
+
+    if (vr == ValueRepresentation_Sequence)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if ((vr == ValueRepresentation_Date ||
+         vr == ValueRepresentation_DateTime ||
+         vr == ValueRepresentation_Time) &&
+        dicomQuery.find('-') != std::string::npos)
+    {
+      /**
+       * Range matching is only defined for TM, DA and DT value
+       * representations. This code fixes issues 35 and 37.
+       *
+       * Reference: "Range matching is not defined for types of
+       * Attributes other than dates and times", DICOM PS 3.4,
+       * C.2.2.2.5 ("Range Matching").
+       **/
+      size_t separator = dicomQuery.find('-');
+      std::string lower = dicomQuery.substr(0, separator);
+      std::string upper = dicomQuery.substr(separator + 1);
+      return new RangeConstraint(lower, upper, caseSensitive);
+    }
+    else if (dicomQuery.find('\\') != std::string::npos)
+    {
+      std::auto_ptr<ListConstraint> constraint(new ListConstraint(caseSensitive));
+
+      std::vector<std::string> items;
+      Toolbox::TokenizeString(items, dicomQuery, '\\');
+
+      for (size_t i = 0; i < items.size(); i++)
+      {
+        constraint->AddAllowedValue(items[i]);
+      }
+
+      return constraint.release();
+    }
+    else if (dicomQuery.find('*') != std::string::npos ||
+             dicomQuery.find('?') != std::string::npos)
+    {
+      return new WildcardConstraint(dicomQuery, caseSensitive);
+    }
+    else
+    {
+      /**
+       * Case-insensitive match for PN value representation (Patient
+       * Name). Case-senstive match for all the other value
+       * representations.
+       *
+       * Reference: DICOM PS 3.4
+       *   - C.2.2.2.1 ("Single Value Matching") 
+       *   - C.2.2.2.4 ("Wild Card Matching")
+       * http://medical.nema.org/Dicom/2011/11_04pu.pdf
+       *
+       * "Except for Attributes with a PN Value Representation, only
+       * entities with values which match exactly the value specified in the
+       * request shall match. This matching is case-sensitive, i.e.,
+       * sensitive to the exact encoding of the key attribute value in
+       * character sets where a letter may have multiple encodings (e.g.,
+       * based on its case, its position in a word, or whether it is
+       * accented)
+       * 
+       * For Attributes with a PN Value Representation (e.g., Patient Name
+       * (0010,0010)), an application may perform literal matching that is
+       * either case-sensitive, or that is insensitive to some or all
+       * aspects of case, position, accent, or other character encoding
+       * variants."
+       *
+       * (0008,0018) UI SOPInstanceUID     => Case-sensitive
+       * (0008,0050) SH AccessionNumber    => Case-sensitive
+       * (0010,0020) LO PatientID          => Case-sensitive
+       * (0020,000D) UI StudyInstanceUID   => Case-sensitive
+       * (0020,000E) UI SeriesInstanceUID  => Case-sensitive
+       **/
+
+      return new ValueConstraint(dicomQuery, caseSensitive);
+    }
+  }
+}
--- a/OrthancServer/Search/IFindConstraint.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/Search/IFindConstraint.h	Wed Dec 02 09:52:56 2015 +0100
@@ -49,5 +49,11 @@
                        const DicomTag& tag) const = 0;
 
     virtual bool Match(const std::string& value) const = 0;
+
+    virtual std::string Format() const = 0;
+
+    static IFindConstraint* ParseDicomConstraint(const DicomTag& tag,
+                                                 const std::string& dicomQuery,
+                                                 bool caseSensitive);
   };
 }
--- a/OrthancServer/Search/ListConstraint.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/Search/ListConstraint.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -75,4 +75,23 @@
 
     return allowedValues_.find(v) != allowedValues_.end();
   }
+
+
+  std::string ListConstraint::Format() const
+  {
+    std::string s;
+
+    for (std::set<std::string>::const_iterator
+           it = allowedValues_.begin(); it != allowedValues_.end(); ++it)
+    {
+      if (!s.empty())
+      {
+        s += "\\";
+      }
+
+      s += *it;
+    }
+
+    return s;
+  }
 }
--- a/OrthancServer/Search/ListConstraint.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/Search/ListConstraint.h	Wed Dec 02 09:52:56 2015 +0100
@@ -67,5 +67,7 @@
                        const DicomTag& tag) const;
 
     virtual bool Match(const std::string& value) const;
+
+    virtual std::string Format() const;
   };
 }
--- a/OrthancServer/Search/LookupResource.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/Search/LookupResource.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -33,11 +33,6 @@
 #include "../PrecompiledHeadersServer.h"
 #include "LookupResource.h"
 
-#include "ListConstraint.h"
-#include "RangeConstraint.h"
-#include "ValueConstraint.h"
-#include "WildcardConstraint.h"
-
 #include "../../Core/OrthancException.h"
 #include "../../Core/FileStorage/StorageAccessor.h"
 #include "../ServerToolbox.h"
@@ -426,85 +421,16 @@
                                           const std::string& dicomQuery,
                                           bool caseSensitive)
   {
-    ValueRepresentation vr = FromDcmtkBridge::GetValueRepresentation(tag);
-
     // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained
     // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html  
     if (tag == DICOM_TAG_MODALITIES_IN_STUDY)
     {
       SetModalitiesInStudy(dicomQuery);
     }
-    else if ((vr == ValueRepresentation_Date ||
-              vr == ValueRepresentation_DateTime ||
-              vr == ValueRepresentation_Time) &&
-             dicomQuery.find('-') != std::string::npos)
-    {
-      /**
-       * Range matching is only defined for TM, DA and DT value
-       * representations. This code fixes issues 35 and 37.
-       *
-       * Reference: "Range matching is not defined for types of
-       * Attributes other than dates and times", DICOM PS 3.4,
-       * C.2.2.2.5 ("Range Matching").
-       **/
-      size_t separator = dicomQuery.find('-');
-      std::string lower = dicomQuery.substr(0, separator);
-      std::string upper = dicomQuery.substr(separator + 1);
-      Add(tag, new RangeConstraint(lower, upper, caseSensitive));
-    }
-    else if (dicomQuery.find('\\') != std::string::npos)
-    {
-      std::auto_ptr<ListConstraint> constraint(new ListConstraint(caseSensitive));
-
-      std::vector<std::string> items;
-      Toolbox::TokenizeString(items, dicomQuery, '\\');
-
-      for (size_t i = 0; i < items.size(); i++)
-      {
-        constraint->AddAllowedValue(items[i]);
-      }
-
-      Add(tag, constraint.release());
-    }
-    else if (dicomQuery.find('*') != std::string::npos ||
-             dicomQuery.find('?') != std::string::npos)
+    else 
     {
-      Add(tag, new WildcardConstraint(dicomQuery, caseSensitive));
-    }
-    else
-    {
-      /**
-       * Case-insensitive match for PN value representation (Patient
-       * Name). Case-senstive match for all the other value
-       * representations.
-       *
-       * Reference: DICOM PS 3.4
-       *   - C.2.2.2.1 ("Single Value Matching") 
-       *   - C.2.2.2.4 ("Wild Card Matching")
-       * http://medical.nema.org/Dicom/2011/11_04pu.pdf
-       *
-       * "Except for Attributes with a PN Value Representation, only
-       * entities with values which match exactly the value specified in the
-       * request shall match. This matching is case-sensitive, i.e.,
-       * sensitive to the exact encoding of the key attribute value in
-       * character sets where a letter may have multiple encodings (e.g.,
-       * based on its case, its position in a word, or whether it is
-       * accented)
-       * 
-       * For Attributes with a PN Value Representation (e.g., Patient Name
-       * (0010,0010)), an application may perform literal matching that is
-       * either case-sensitive, or that is insensitive to some or all
-       * aspects of case, position, accent, or other character encoding
-       * variants."
-       *
-       * (0008,0018) UI SOPInstanceUID     => Case-sensitive
-       * (0008,0050) SH AccessionNumber    => Case-sensitive
-       * (0010,0020) LO PatientID          => Case-sensitive
-       * (0020,000D) UI StudyInstanceUID   => Case-sensitive
-       * (0020,000E) UI SeriesInstanceUID  => Case-sensitive
-      **/
-
-      Add(tag, new ValueConstraint(dicomQuery, caseSensitive));
+      Add(tag, IFindConstraint::ParseDicomConstraint(tag, dicomQuery, caseSensitive));
     }
   }
+
 }
--- a/OrthancServer/Search/RangeConstraint.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/Search/RangeConstraint.h	Wed Dec 02 09:52:56 2015 +0100
@@ -64,5 +64,10 @@
                        const DicomTag& tag) const;
 
     virtual bool Match(const std::string& value) const;
+
+    virtual std::string Format() const
+    {
+      return lower_ + "-" + upper_;
+    }
   };
 }
--- a/OrthancServer/Search/ValueConstraint.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/Search/ValueConstraint.h	Wed Dec 02 09:52:56 2015 +0100
@@ -61,5 +61,10 @@
                        const DicomTag& tag) const;
 
     virtual bool Match(const std::string& value) const;
+
+    virtual std::string Format() const
+    {
+      return value_;
+    }
   };
 }
--- a/OrthancServer/Search/WildcardConstraint.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/Search/WildcardConstraint.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -78,4 +78,9 @@
   {
     lookup.AddConstraint(tag, IdentifierConstraintType_Wildcard, pimpl_->wildcard_);
   }
+
+  std::string WildcardConstraint::Format() const
+  {
+    return pimpl_->wildcard_;
+  }
 }
--- a/OrthancServer/Search/WildcardConstraint.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/Search/WildcardConstraint.h	Wed Dec 02 09:52:56 2015 +0100
@@ -59,5 +59,7 @@
                        const DicomTag& tag) const;
 
     virtual bool Match(const std::string& value) const;
+
+    virtual std::string Format() const;
   };
 }
--- a/OrthancServer/ServerContext.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/ServerContext.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -236,8 +236,7 @@
 
       typedef std::map<MetadataType, std::string>  InstanceMetadata;
       InstanceMetadata  instanceMetadata;
-      StoreStatus status = index_.Store(instanceMetadata, dicom.GetSummary(), attachments, 
-                                        dicom.GetRemoteAet(), dicom.GetMetadata());
+      StoreStatus status = index_.Store(instanceMetadata, dicom, attachments);
 
       // Only keep the metadata for the "instance" level
       dicom.GetMetadata().clear();
--- a/OrthancServer/ServerEnumerations.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/ServerEnumerations.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -63,6 +63,7 @@
     dictMetadataType_.Add(MetadataType_ModifiedFrom, "ModifiedFrom");
     dictMetadataType_.Add(MetadataType_AnonymizedFrom, "AnonymizedFrom");
     dictMetadataType_.Add(MetadataType_LastUpdate, "LastUpdate");
+    dictMetadataType_.Add(MetadataType_Instance_Origin, "Origin");
 
     dictContentType_.Add(FileContentType_Dicom, "dicom");
     dictContentType_.Add(FileContentType_DicomAsJson, "dicom-as-json");
@@ -336,6 +337,15 @@
       case ModalityManufacturer_SyngoVia:
         return "SyngoVia";
       
+      case ModalityManufacturer_AgfaImpax:
+        return "AgfaImpax";
+      
+      case ModalityManufacturer_EFilm2:
+        return "EFilm2";
+      
+      case ModalityManufacturer_Vitrea:
+        return "Vitrea";
+      
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
@@ -399,6 +409,18 @@
     {
       return ModalityManufacturer_SyngoVia;
     }
+    else if (manufacturer == "AgfaImpax")
+    {
+      return ModalityManufacturer_AgfaImpax;
+    }
+    else if (manufacturer == "Vitrea")
+    {
+      return ModalityManufacturer_Vitrea;
+    }
+    else if (manufacturer == "EFilm2")
+    {
+      return ModalityManufacturer_EFilm2;
+    }
     else
     {
       throw OrthancException(ErrorCode_ParameterOutOfRange);
--- a/OrthancServer/ServerEnumerations.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/ServerEnumerations.h	Wed Dec 02 09:52:56 2015 +0100
@@ -62,7 +62,10 @@
     ModalityManufacturer_ClearCanvas,
     ModalityManufacturer_MedInria,
     ModalityManufacturer_Dcm4Chee,
-    ModalityManufacturer_SyngoVia
+    ModalityManufacturer_SyngoVia,
+    ModalityManufacturer_AgfaImpax,
+    ModalityManufacturer_EFilm2,
+    ModalityManufacturer_Vitrea
   };
 
   enum DicomRequestType
@@ -98,7 +101,8 @@
     ValueRepresentation_PatientName,
     ValueRepresentation_Date,
     ValueRepresentation_DateTime,
-    ValueRepresentation_Time
+    ValueRepresentation_Time,
+    ValueRepresentation_Sequence
   };
 
   enum DicomToJsonFormat
@@ -119,12 +123,19 @@
 
     // Some predefined combinations
     DicomToJsonFlags_None     = 0,
-    DicomToJsonFlags_Default  = (DicomToJsonFlags_IncludePrivateTags | 
+    DicomToJsonFlags_Default  = (DicomToJsonFlags_IncludeBinary |
+                                 DicomToJsonFlags_IncludePixelData | 
+                                 DicomToJsonFlags_IncludePrivateTags | 
                                  DicomToJsonFlags_IncludeUnknownTags | 
-                                 DicomToJsonFlags_IncludePixelData | 
                                  DicomToJsonFlags_ConvertBinaryToNull)
   };
 
+  enum DicomFromJsonFlags
+  {
+    DicomFromJsonFlags_DecodeDataUriScheme = (1 << 0),
+    DicomFromJsonFlags_GenerateIdentifiers = (1 << 1)
+  };
+
   enum IdentifierConstraintType
   {
     IdentifierConstraintType_Equal,
@@ -156,6 +167,7 @@
     MetadataType_ModifiedFrom = 5,
     MetadataType_AnonymizedFrom = 6,
     MetadataType_LastUpdate = 7,
+    MetadataType_Instance_Origin = 8,   // New in Orthanc 0.9.5
 
     // Make sure that the value "65535" can be stored into this enumeration
     MetadataType_StartUser = 1024,
--- a/OrthancServer/ServerIndex.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/ServerIndex.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -50,6 +50,7 @@
 
 #include "FromDcmtkBridge.h"
 #include "ServerContext.h"
+#include "DicomInstanceToStore.h"
 
 #include <boost/lexical_cast.hpp>
 #include <stdio.h>
@@ -593,16 +594,17 @@
 
 
   StoreStatus ServerIndex::Store(std::map<MetadataType, std::string>& instanceMetadata,
-                                 const DicomMap& dicomSummary,
-                                 const Attachments& attachments,
-                                 const std::string& remoteAet,
-                                 const MetadataMap& metadata)
+                                 DicomInstanceToStore& instanceToStore,
+                                 const Attachments& attachments)
   {
     boost::mutex::scoped_lock lock(mutex_);
 
+    const DicomMap& dicomSummary = instanceToStore.GetSummary();
+    const ServerIndex::MetadataMap& metadata = instanceToStore.GetMetadata();
+
     instanceMetadata.clear();
 
-    DicomInstanceHasher hasher(dicomSummary);
+    DicomInstanceHasher hasher(instanceToStore.GetSummary());
 
     try
     {
@@ -766,8 +768,14 @@
       db_.SetMetadata(instance, MetadataType_Instance_ReceptionDate, now);
       instanceMetadata[MetadataType_Instance_ReceptionDate] = now;
 
-      db_.SetMetadata(instance, MetadataType_Instance_RemoteAet, remoteAet);
-      instanceMetadata[MetadataType_Instance_RemoteAet] = remoteAet;
+      db_.SetMetadata(instance, MetadataType_Instance_RemoteAet, instanceToStore.GetRemoteAet());
+      instanceMetadata[MetadataType_Instance_RemoteAet] = instanceToStore.GetRemoteAet();
+
+      {
+        std::string s = EnumerationToString(instanceToStore.GetRequestOrigin());
+        db_.SetMetadata(instance, MetadataType_Instance_Origin, s);
+        instanceMetadata[MetadataType_Instance_Origin] = s;
+      }
 
       const DicomValue* value;
       if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL ||
--- a/OrthancServer/ServerIndex.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/ServerIndex.h	Wed Dec 02 09:52:56 2015 +0100
@@ -42,11 +42,11 @@
 
 #include "IDatabaseWrapper.h"
 
-
 namespace Orthanc
 {
   class LookupResource;
   class ServerContext;
+  class DicomInstanceToStore;
 
   class ServerIndex : public boost::noncopyable
   {
@@ -140,10 +140,8 @@
     void SetMaximumPatientCount(unsigned int count);
 
     StoreStatus Store(std::map<MetadataType, std::string>& instanceMetadata,
-                      const DicomMap& dicomSummary,
-                      const Attachments& attachments,
-                      const std::string& remoteAet,
-                      const MetadataMap& metadata);
+                      DicomInstanceToStore& instance,
+                      const Attachments& attachments);
 
     void ComputeStatistics(Json::Value& target);                        
 
--- a/OrthancServer/ServerToolbox.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/ServerToolbox.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -297,7 +297,7 @@
       database.GetAllPublicIds(resources, level);
 
       for (std::list<std::string>::const_iterator
-             it = resources.begin(); it != resources.end(); it++)
+             it = resources.begin(); it != resources.end(); ++it)
       {
         // Locate the resource and one of its child instances
         int64_t resource, instance;
--- a/OrthancServer/SliceOrdering.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/SliceOrdering.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -276,21 +276,23 @@
     PositionComparator comparator(normal_);
     std::sort(instances_.begin(), instances_.end(), comparator);
 
-    float a = instances_.front()->ComputeRelativePosition(normal_);
-    float b = instances_.back()->ComputeRelativePosition(normal_);
-
-    if (std::fabs(b - a) <= 10.0f * std::numeric_limits<float>::epsilon())
+    float a = instances_[0]->ComputeRelativePosition(normal_);
+    for (size_t i = 1; i < instances_.size(); i++)
     {
-      // Not enough difference between the minimum and maximum
-      // positions along the normal of the volume
-      return false;
+      float b = instances_[i]->ComputeRelativePosition(normal_);
+
+      if (std::fabs(b - a) <= 10.0f * std::numeric_limits<float>::epsilon())
+      {
+        // Not enough space between two slices along the normal of the volume
+        return false;
+      }
+
+      a = b;
     }
-    else
-    {
-      // This is a 3D volume
-      isVolume_ = true;
-      return true;
-    }
+
+    // This is a 3D volume
+    isVolume_ = true;
+    return true;
   }
 
 
--- a/OrthancServer/main.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/OrthancServer/main.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -159,14 +159,16 @@
   {
   }
 
-  virtual bool IsAllowedConnection(const std::string& /*callingIp*/,
-                                   const std::string& /*callingAet*/)
+  virtual bool IsAllowedConnection(const std::string& /*remoteIp*/,
+                                   const std::string& /*remoteAet*/,
+                                   const std::string& /*calledAet*/)
   {
     return true;
   }
 
-  virtual bool IsAllowedRequest(const std::string& /*callingIp*/,
-                                const std::string& callingAet,
+  virtual bool IsAllowedRequest(const std::string& /*remoteIp*/,
+                                const std::string& remoteAet,
+                                const std::string& /*calledAet*/,
                                 DicomRequestType type)
   {
     if (type == DicomRequestType_Store)
@@ -175,9 +177,9 @@
       return true;
     }
 
-    if (!Configuration::IsKnownAETitle(callingAet))
+    if (!Configuration::IsKnownAETitle(remoteAet))
     {
-      LOG(ERROR) << "Unknown remote DICOM modality AET: \"" << callingAet << "\"";
+      LOG(ERROR) << "Unknown remote DICOM modality AET: \"" << remoteAet << "\"";
       return false;
     }
     else
@@ -186,8 +188,9 @@
     }
   }
 
-  virtual bool IsAllowedTransferSyntax(const std::string& callingIp,
-                                       const std::string& callingAet,
+  virtual bool IsAllowedTransferSyntax(const std::string& remoteIp,
+                                       const std::string& remoteAet,
+                                       const std::string& calledAet,
                                        TransferSyntax syntax)
   {
     std::string configuration;
@@ -234,8 +237,34 @@
       if (locker.GetLua().IsExistingFunction(lua.c_str()))
       {
         LuaFunctionCall call(locker.GetLua(), lua.c_str());
-        call.PushString(callingAet);
-        call.PushString(callingIp);
+        call.PushString(remoteAet);
+        call.PushString(remoteIp);
+        call.PushString(calledAet);
+        return call.ExecutePredicate();
+      }
+    }
+
+    return Configuration::GetGlobalBoolParameter(configuration, true);
+  }
+
+
+  virtual bool IsUnknownSopClassAccepted(const std::string& remoteIp,
+                                         const std::string& remoteAet,
+                                         const std::string& calledAet)
+  {
+    static const char* configuration = "UnknownSopClassAccepted";
+
+    {
+      std::string lua = "Is" + std::string(configuration);
+
+      LuaScripting::Locker locker(context_.GetLua());
+      
+      if (locker.GetLua().IsExistingFunction(lua.c_str()))
+      {
+        LuaFunctionCall call(locker.GetLua(), lua.c_str());
+        call.PushString(remoteAet);
+        call.PushString(remoteIp);
+        call.PushString(calledAet);
         return call.ExecutePredicate();
       }
     }
@@ -550,6 +579,7 @@
     PrintErrorCode(ErrorCode_DatabaseNotInitialized, "Plugin trying to call the database during its initialization");
     PrintErrorCode(ErrorCode_SslDisabled, "Orthanc has been built without SSL support");
     PrintErrorCode(ErrorCode_CannotOrderSlices, "Unable to order the slices of the series");
+    PrintErrorCode(ErrorCode_NoWorklistHandler, "No request handler factory for DICOM C-Find Modality SCP");
   }
 
   std::cout << std::endl;
@@ -704,12 +734,22 @@
   dicomServer.SetStoreRequestHandlerFactory(serverFactory);
   dicomServer.SetMoveRequestHandlerFactory(serverFactory);
   dicomServer.SetFindRequestHandlerFactory(serverFactory);
+
+#if ORTHANC_PLUGINS_ENABLED == 1
+  if (plugins &&
+      plugins->HasWorklistHandler())
+  {
+    dicomServer.SetWorklistRequestHandlerFactory(*plugins);
+  }
+#endif
+
   dicomServer.SetPortNumber(Configuration::GetGlobalIntegerParameter("DicomPort", 4242));
   dicomServer.SetApplicationEntityTitle(Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC"));
   dicomServer.SetApplicationEntityFilter(dicomFilter);
 
   dicomServer.Start();
-  LOG(WARNING) << "DICOM server listening on port: " << dicomServer.GetPortNumber();
+  LOG(WARNING) << "DICOM server listening with AET " << dicomServer.GetApplicationEntityTitle() 
+               << " on port: " << dicomServer.GetPortNumber();
 
   bool restart;
   ErrorCode error = ErrorCode_Success;
@@ -1068,6 +1108,23 @@
     {
       OrthancInitialize(configurationFile);
 
+      if (0)
+      {
+        // TODO REMOVE THIS TEST
+        DicomUserConnection c;
+        c.SetRemoteHost("localhost");
+        c.SetRemotePort(4243);
+        c.SetRemoteApplicationEntityTitle("ORTHANCTEST");
+        c.Open();
+        ParsedDicomFile f(false);
+        f.Replace(DICOM_TAG_PATIENT_NAME, "M*");
+        DicomFindAnswers a;
+        c.FindWorklist(a, f);
+        Json::Value j;
+        a.ToJson(j, true);
+        std::cout << j;
+      }
+
       bool restart = StartOrthanc(argc, argv, allowDatabaseUpgrade);
       if (restart)
       {
--- a/Plugins/Engine/OrthancPlugins.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/Plugins/Engine/OrthancPlugins.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -47,6 +47,8 @@
 #include "../../OrthancServer/OrthancInitialization.h"
 #include "../../OrthancServer/ServerContext.h"
 #include "../../OrthancServer/ServerToolbox.h"
+#include "../../OrthancServer/Search/HierarchicalMatcher.h"
+#include "../../OrthancServer/Internals/DicomImageDecoder.h"
 #include "../../Core/Compression/ZlibCompressor.h"
 #include "../../Core/Compression/GzipCompressor.h"
 #include "../../Core/Images/Image.h"
@@ -61,6 +63,46 @@
 
 namespace Orthanc
 {
+  static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target,
+                                 const void* data,
+                                 size_t size)
+  {
+    target.size = size;
+
+    if (size == 0)
+    {
+      target.data = NULL;
+    }
+    else
+    {
+      target.data = malloc(size);
+      if (target.data != NULL)
+      {
+        memcpy(target.data, data, size);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+      }
+    }
+  }
+
+
+  static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target,
+                                 const std::string& str)
+  {
+    if (str.size() == 0)
+    {
+      target.size = 0;
+      target.data = NULL;
+    }
+    else
+    {
+      CopyToMemoryBuffer(target, str.c_str(), str.size());
+    }
+  }
+
+
   namespace
   {
     class PluginStorageArea : public IStorageArea
@@ -244,10 +286,14 @@
     RestCallbacks restCallbacks_;
     OnStoredCallbacks  onStoredCallbacks_;
     OnChangeCallbacks  onChangeCallbacks_;
+    OrthancPluginWorklistCallback  worklistCallback_;
+    OrthancPluginDecodeImageCallback  decodeImageCallback_;
     std::auto_ptr<StorageAreaFactory>  storageArea_;
     boost::recursive_mutex restCallbackMutex_;
     boost::recursive_mutex storedCallbackMutex_;
     boost::recursive_mutex changeCallbackMutex_;
+    boost::mutex worklistCallbackMutex_;
+    boost::mutex decodeImageCallbackMutex_;
     boost::recursive_mutex invokeServiceMutex_;
     Properties properties_;
     int argc_;
@@ -257,6 +303,8 @@
 
     PImpl() : 
       context_(NULL), 
+      worklistCallback_(NULL),
+      decodeImageCallback_(NULL),
       argc_(1),
       argv_(NULL)
     {
@@ -265,6 +313,86 @@
 
 
   
+  class OrthancPlugins::WorklistHandler : public IWorklistRequestHandler
+  {
+  private:
+    OrthancPlugins&  that_;
+    std::auto_ptr<HierarchicalMatcher> matcher_;
+    ParsedDicomFile* currentQuery_;
+
+    void Reset()
+    {
+      matcher_.reset(NULL);
+      currentQuery_ = NULL;
+    }
+
+  public:
+    WorklistHandler(OrthancPlugins& that) : that_(that)
+    {
+      Reset();
+    }
+
+    virtual void Handle(DicomFindAnswers& answers,
+                        ParsedDicomFile& query,
+                        const std::string& remoteIp,
+                        const std::string& remoteAet,
+                        const std::string& calledAet)
+    {
+      bool caseSensitivePN = Configuration::GetGlobalBoolParameter("CaseSensitivePN", false);
+      matcher_.reset(new HierarchicalMatcher(query, caseSensitivePN));
+      currentQuery_ = &query;
+
+      {
+        boost::mutex::scoped_lock lock(that_.pimpl_->worklistCallbackMutex_);
+
+        if (that_.pimpl_->worklistCallback_)
+        {
+          OrthancPluginErrorCode error = that_.pimpl_->worklistCallback_
+            (reinterpret_cast<OrthancPluginWorklistAnswers*>(&answers),
+             reinterpret_cast<const OrthancPluginWorklistQuery*>(this),
+             remoteAet.c_str(),
+             calledAet.c_str());
+
+          if (error != OrthancPluginErrorCode_Success)
+          {
+            Reset();
+            that_.GetErrorDictionary().LogError(error, true);
+            throw OrthancException(static_cast<ErrorCode>(error));
+          }
+        }
+      }
+
+      Reset();
+    }
+
+    void GetDicomQuery(OrthancPluginMemoryBuffer& target) const
+    {
+      assert(currentQuery_ != NULL);
+      std::string dicom;
+      currentQuery_->SaveToMemoryBuffer(dicom);
+      CopyToMemoryBuffer(target, dicom.c_str(), dicom.size());
+    }
+
+    bool IsMatch(const void* dicom,
+                 size_t size) const
+    {
+      assert(matcher_.get() != NULL);
+      ParsedDicomFile f(dicom, size);
+      return matcher_->Match(f);
+    }
+
+    void AddAnswer(OrthancPluginWorklistAnswers* answers,
+                   const void* dicom,
+                   size_t size) const
+    {
+      assert(matcher_.get() != NULL);
+      ParsedDicomFile f(dicom, size);
+      std::auto_ptr<ParsedDicomFile> summary(matcher_->Extract(f));
+      reinterpret_cast<DicomFindAnswers*>(answers)->Add(*summary);
+    }
+  };
+
+  
   static char* CopyString(const std::string& str)
   {
     char *result = reinterpret_cast<char*>(malloc(str.size() + 1));
@@ -288,6 +416,7 @@
 
   OrthancPlugins::OrthancPlugins()
   {
+    /* Sanity check of the compiler */
     if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) ||
         sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) ||
         sizeof(int32_t) != sizeof(_OrthancPluginService) ||
@@ -301,16 +430,20 @@
         sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) ||
         sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) ||
         sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) ||
         sizeof(int32_t) != sizeof(_OrthancPluginDatabaseAnswerType) ||
         sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) ||
+        sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin) ||
         static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeBinary) != static_cast<int>(DicomToJsonFlags_IncludeBinary) ||
         static_cast<int>(OrthancPluginDicomToJsonFlags_IncludePrivateTags) != static_cast<int>(DicomToJsonFlags_IncludePrivateTags) ||
         static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeUnknownTags) != static_cast<int>(DicomToJsonFlags_IncludeUnknownTags) ||
         static_cast<int>(OrthancPluginDicomToJsonFlags_IncludePixelData) != static_cast<int>(DicomToJsonFlags_IncludePixelData) ||
         static_cast<int>(OrthancPluginDicomToJsonFlags_ConvertBinaryToNull) != static_cast<int>(DicomToJsonFlags_ConvertBinaryToNull) ||
-        static_cast<int>(OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii) != static_cast<int>(DicomToJsonFlags_ConvertBinaryToAscii))
+        static_cast<int>(OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii) != static_cast<int>(DicomToJsonFlags_ConvertBinaryToAscii) ||
+        static_cast<int>(OrthancPluginCreateDicomFlags_DecodeDataUriScheme) != static_cast<int>(DicomFromJsonFlags_DecodeDataUriScheme) ||
+        static_cast<int>(OrthancPluginCreateDicomFlags_GenerateIdentifiers) != static_cast<int>(DicomFromJsonFlags_GenerateIdentifiers))
+
     {
-      /* Sanity check of the compiler */
       throw OrthancException(ErrorCode_Plugin);
     }
 
@@ -546,46 +679,6 @@
 
 
 
-  static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target,
-                                 const void* data,
-                                 size_t size)
-  {
-    target.size = size;
-
-    if (size == 0)
-    {
-      target.data = NULL;
-    }
-    else
-    {
-      target.data = malloc(size);
-      if (target.data != NULL)
-      {
-        memcpy(target.data, data, size);
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_NotEnoughMemory);
-      }
-    }
-  }
-
-
-  static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target,
-                                 const std::string& str)
-  {
-    if (str.size() == 0)
-    {
-      target.size = 0;
-      target.data = NULL;
-    }
-    else
-    {
-      CopyToMemoryBuffer(target, str.c_str(), str.size());
-    }
-  }
-
-
   void OrthancPlugins::RegisterRestCallback(const void* parameters,
                                             bool lock)
   {
@@ -622,6 +715,47 @@
   }
 
 
+  void OrthancPlugins::RegisterWorklistCallback(const void* parameters)
+  {
+    const _OrthancPluginWorklistCallback& p = 
+      *reinterpret_cast<const _OrthancPluginWorklistCallback*>(parameters);
+
+    boost::mutex::scoped_lock lock(pimpl_->worklistCallbackMutex_);
+
+    if (pimpl_->worklistCallback_ != NULL)
+    {
+      LOG(ERROR) << "Can only register one plugin to handle modality worklists";
+      throw OrthancException(ErrorCode_Plugin);
+    }
+    else
+    {
+      LOG(INFO) << "Plugin has registered a callback to handle modality worklists";
+      pimpl_->worklistCallback_ = p.callback;
+    }
+  }
+
+
+  void OrthancPlugins::RegisterDecodeImageCallback(const void* parameters)
+  {
+    const _OrthancPluginDecodeImageCallback& p = 
+      *reinterpret_cast<const _OrthancPluginDecodeImageCallback*>(parameters);
+
+    boost::mutex::scoped_lock lock(pimpl_->decodeImageCallbackMutex_);
+
+    if (pimpl_->decodeImageCallback_ != NULL)
+    {
+      LOG(ERROR) << "Can only register one plugin to handle the decompression of DICOM images";
+      throw OrthancException(ErrorCode_Plugin);
+    }
+    else
+    {
+      LOG(INFO) << "Plugin has registered a callback to decode DICOM images";
+      pimpl_->decodeImageCallback_ = p.callback;
+    }
+  }
+
+
+
 
   void OrthancPlugins::AnswerBuffer(const void* parameters)
   {
@@ -1061,6 +1195,10 @@
         return;
       }
 
+      case _OrthancPluginService_GetInstanceOrigin:   // New in Orthanc 0.9.5
+        *p.resultOrigin = Plugins::Convert(instance.GetRequestOrigin());
+        return;
+
       default:
         throw OrthancException(ErrorCode_InternalError);
     }
@@ -1125,6 +1263,25 @@
   }
 
 
+  static OrthancPluginImage* ReturnImage(std::auto_ptr<ImageAccessor>& image)
+  {
+    // Images returned to plugins are assumed to be writeable. If the
+    // input image is read-only, we return a copy so that it can be modified.
+
+    if (image->IsReadOnly())
+    {
+      std::auto_ptr<Image> copy(new Image(image->GetFormat(), image->GetWidth(), image->GetHeight()));
+      ImageProcessing::Copy(*copy, *image);
+      image.reset(NULL);
+      return reinterpret_cast<OrthancPluginImage*>(copy.release());
+    }
+    else
+    {
+      return reinterpret_cast<OrthancPluginImage*>(image.release());
+    }
+  }
+
+
   void OrthancPlugins::UncompressImage(const void* parameters)
   {
     const _OrthancPluginUncompressImage& p = *reinterpret_cast<const _OrthancPluginUncompressImage*>(parameters);
@@ -1147,11 +1304,18 @@
         break;
       }
 
+      case OrthancPluginImageFormat_Dicom:
+      {
+        ParsedDicomFile dicom(p.data, p.size);
+        image.reset(Decode(dicom, 0));
+        break;
+      }
+
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    *(p.target) = reinterpret_cast<OrthancPluginImage*>(image.release());
+    *(p.target) = ReturnImage(image);
   }
 
 
@@ -1241,7 +1405,7 @@
     std::auto_ptr<ImageAccessor> target(new Image(Plugins::Convert(p.targetFormat), source.GetWidth(), source.GetHeight()));
     ImageProcessing::Convert(*target, source);
 
-    *(p.target) = reinterpret_cast<OrthancPluginImage*>(target.release());
+    *(p.target) = ReturnImage(target);
   }
 
 
@@ -1311,6 +1475,104 @@
   }
         
 
+  void OrthancPlugins::ApplyCreateDicom(_OrthancPluginService service,
+                                        const void* parameters)
+  {
+    const _OrthancPluginCreateDicom& p =
+      *reinterpret_cast<const _OrthancPluginCreateDicom*>(parameters);
+
+    Json::Value json;
+
+    if (p.json == NULL)
+    {
+      json = Json::objectValue;
+    }
+    else
+    {
+      Json::Reader reader;
+      if (!reader.parse(p.json, json))
+      {
+        throw OrthancException(ErrorCode_BadJson);
+      }
+    }
+
+    std::string dicom;
+
+    {
+      std::auto_ptr<ParsedDicomFile> file
+        (ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(p.flags)));
+
+      if (p.pixelData)
+      {
+        file->EmbedImage(*reinterpret_cast<const ImageAccessor*>(p.pixelData));
+      }
+
+      file->SaveToMemoryBuffer(dicom);
+    }
+
+    CopyToMemoryBuffer(*p.target, dicom);
+  }
+
+
+  void OrthancPlugins::ComputeHash(_OrthancPluginService service,
+                                   const void* parameters)
+  {
+    const _OrthancPluginComputeHash& p =
+      *reinterpret_cast<const _OrthancPluginComputeHash*>(parameters);
+ 
+    std::string hash;
+    switch (service)
+    {
+      case _OrthancPluginService_ComputeMd5:
+        Toolbox::ComputeMD5(hash, p.buffer, p.size);
+        break;
+
+      case _OrthancPluginService_ComputeSha1:
+        Toolbox::ComputeSHA1(hash, p.buffer, p.size);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+   
+    *p.result = CopyString(hash);
+  }
+
+
+  void OrthancPlugins::ApplyCreateImage(_OrthancPluginService service,
+                                        const void* parameters)
+  {
+    const _OrthancPluginCreateImage& p =
+      *reinterpret_cast<const _OrthancPluginCreateImage*>(parameters);
+
+    std::auto_ptr<ImageAccessor> result;
+
+    switch (service)
+    {
+      case _OrthancPluginService_CreateImage:
+        result.reset(new Image(Plugins::Convert(p.format), p.width, p.height));
+        break;
+
+      case _OrthancPluginService_CreateImageAccessor:
+        result.reset(new ImageAccessor);
+        result->AssignWritable(Plugins::Convert(p.format), p.width, p.height, p.pitch, p.buffer);
+        break;
+
+      case _OrthancPluginService_DecodeDicomImage:
+      {
+        ParsedDicomFile dicom(p.constBuffer, p.bufferSize);
+        result.reset(Decode(dicom, p.frameIndex));
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+
+    *(p.target) = ReturnImage(result);
+  }
+        
+
   void OrthancPlugins::DatabaseAnswer(const void* parameters)
   {
     const _OrthancPluginDatabaseAnswer& p =
@@ -1346,8 +1608,7 @@
       return true;
     }
 
-
-    std::auto_ptr<boost::recursive_mutex::scoped_lock> lock;   // (*)
+    boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);   // (*)
 
     switch (service)
     {
@@ -1401,6 +1662,14 @@
         RegisterOnChangeCallback(parameters);
         return true;
 
+      case _OrthancPluginService_RegisterWorklistCallback:
+        RegisterWorklistCallback(parameters);
+        return true;
+
+      case _OrthancPluginService_RegisterDecodeImageCallback:
+        RegisterDecodeImageCallback(parameters);
+        return true;
+
       case _OrthancPluginService_AnswerBuffer:
         AnswerBuffer(parameters);
         return true;
@@ -1496,6 +1765,7 @@
       case _OrthancPluginService_GetInstanceSimplifiedJson:
       case _OrthancPluginService_HasInstanceMetadata:
       case _OrthancPluginService_GetInstanceMetadata:
+      case _OrthancPluginService_GetInstanceOrigin:
         AccessDicomInstance(service, parameters);
         return true;
 
@@ -1712,7 +1982,7 @@
       case _OrthancPluginService_GetImageBuffer:
       {
         const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters);
-        *(p.resultBuffer) = reinterpret_cast<const ImageAccessor*>(p.image)->GetConstBuffer();
+        *(p.resultBuffer) = reinterpret_cast<const ImageAccessor*>(p.image)->GetBuffer();
         return true;
       }
 
@@ -1831,6 +2101,53 @@
         ApplyDicomToJson(service, parameters);
         return true;
 
+      case _OrthancPluginService_CreateDicom:
+        ApplyCreateDicom(service, parameters);
+        return true;
+
+      case _OrthancPluginService_WorklistAddAnswer:
+      {
+        const _OrthancPluginWorklistAnswersOperation& p =
+          *reinterpret_cast<const _OrthancPluginWorklistAnswersOperation*>(parameters);
+        reinterpret_cast<const WorklistHandler*>(p.query)->AddAnswer(p.answers, p.dicom, p.size);
+        return true;
+      }
+
+      case _OrthancPluginService_WorklistMarkIncomplete:
+      {
+        const _OrthancPluginWorklistAnswersOperation& p =
+          *reinterpret_cast<const _OrthancPluginWorklistAnswersOperation*>(parameters);
+        reinterpret_cast<DicomFindAnswers*>(p.answers)->SetComplete(false);
+        return true;
+      }
+
+      case _OrthancPluginService_WorklistIsMatch:
+      {
+        const _OrthancPluginWorklistQueryOperation& p =
+          *reinterpret_cast<const _OrthancPluginWorklistQueryOperation*>(parameters);
+        *p.isMatch = reinterpret_cast<const WorklistHandler*>(p.query)->IsMatch(p.dicom, p.size);
+        return true;
+      }
+
+      case _OrthancPluginService_WorklistGetDicomQuery:
+      {
+        const _OrthancPluginWorklistQueryOperation& p =
+          *reinterpret_cast<const _OrthancPluginWorklistQueryOperation*>(parameters);
+        reinterpret_cast<const WorklistHandler*>(p.query)->GetDicomQuery(*p.target);
+        return true;
+      }
+
+      case _OrthancPluginService_CreateImage:
+      case _OrthancPluginService_CreateImageAccessor:
+      case _OrthancPluginService_DecodeDicomImage:
+        ApplyCreateImage(service, parameters);
+        return true;
+
+      case _OrthancPluginService_ComputeMd5:
+      case _OrthancPluginService_ComputeSha1:
+        ComputeHash(service, parameters);
+        return true;
+
       default:
       {
         // This service is unknown to the Orthanc plugin engine
@@ -1948,4 +2265,50 @@
   {
     return pimpl_->dictionary_;
   }
+
+
+  IWorklistRequestHandler* OrthancPlugins::ConstructWorklistRequestHandler()
+  {
+    if (HasWorklistHandler())
+    {
+      return new WorklistHandler(*this);
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  bool OrthancPlugins::HasWorklistHandler()
+  {
+    boost::mutex::scoped_lock lock(pimpl_->worklistCallbackMutex_);
+    return pimpl_->worklistCallback_ != NULL;
+  }
+
+
+  ImageAccessor*  OrthancPlugins::Decode(ParsedDicomFile& dicom, 
+                                         unsigned int frame)
+  {
+    {
+      boost::mutex::scoped_lock lock(pimpl_->decodeImageCallbackMutex_);
+      if (pimpl_->decodeImageCallback_ != NULL)
+      {
+        std::string s;
+        dicom.SaveToMemoryBuffer(s);
+
+        OrthancPluginImage* pluginImage = NULL;
+        if (pimpl_->decodeImageCallback_(&pluginImage, s.c_str(), s.size(), frame) == OrthancPluginErrorCode_Success &&
+            pluginImage != NULL)
+        {
+          return reinterpret_cast<ImageAccessor*>(pluginImage);
+        }
+
+        LOG(WARNING) << "The custom image decoder cannot handle an image, fallback to the built-in decoder";
+      }
+    }
+
+    DicomImageDecoder defaultDecoder;
+    return defaultDecoder.Decode(dicom, frame);
+  }
 }
--- a/Plugins/Engine/OrthancPlugins.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/Plugins/Engine/OrthancPlugins.h	Wed Dec 02 09:52:56 2015 +0100
@@ -50,6 +50,8 @@
 #include "../../Core/FileStorage/IStorageArea.h"
 #include "../../Core/HttpServer/IHttpHandler.h"
 #include "../../OrthancServer/IServerListener.h"
+#include "../../OrthancServer/IDicomImageDecoder.h"
+#include "../../OrthancServer/DicomProtocol/IWorklistRequestHandlerFactory.h"
 #include "OrthancPluginDatabase.h"
 #include "PluginsManager.h"
 
@@ -63,12 +65,16 @@
   class OrthancPlugins : 
     public IHttpHandler, 
     public IPluginServiceProvider, 
-    public IServerListener
+    public IServerListener,
+    public IWorklistRequestHandlerFactory,
+    public IDicomImageDecoder
   {
   private:
     struct PImpl;
     boost::shared_ptr<PImpl> pimpl_;
 
+    class WorklistHandler;
+
     void CheckContextAvailable();
 
     void RegisterRestCallback(const void* parameters,
@@ -78,6 +84,10 @@
 
     void RegisterOnChangeCallback(const void* parameters);
 
+    void RegisterWorklistCallback(const void* parameters);
+
+    void RegisterDecodeImageCallback(const void* parameters);
+
     void AnswerBuffer(const void* parameters);
 
     void Redirect(const void* parameters);
@@ -134,6 +144,15 @@
     void ApplyDicomToJson(_OrthancPluginService service,
                           const void* parameters);
 
+    void ApplyCreateDicom(_OrthancPluginService service,
+                          const void* parameters);
+
+    void ApplyCreateImage(_OrthancPluginService service,
+                          const void* parameters);
+
+    void ComputeHash(_OrthancPluginService service,
+                     const void* parameters);
+
     void SignalChangeInternal(OrthancPluginChangeType changeType,
                               OrthancPluginResourceType resourceType,
                               const char* resource);
@@ -204,6 +223,13 @@
     {
       SignalChangeInternal(OrthancPluginChangeType_OrthancStopped, OrthancPluginResourceType_None, NULL);
     }
+
+    bool HasWorklistHandler();
+
+    virtual IWorklistRequestHandler* ConstructWorklistRequestHandler();
+
+    virtual ImageAccessor* Decode(ParsedDicomFile& dicom, 
+                                  unsigned int frame);
   };
 }
 
--- a/Plugins/Engine/PluginsEnumerations.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/Plugins/Engine/PluginsEnumerations.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -279,6 +279,31 @@
     }
 
 
+    OrthancPluginInstanceOrigin Convert(RequestOrigin origin)
+    {
+      switch (origin)
+      {
+        case RequestOrigin_DicomProtocol:
+          return OrthancPluginInstanceOrigin_DicomProtocol;
+
+        case RequestOrigin_RestApi:
+          return OrthancPluginInstanceOrigin_RestApi;
+
+        case RequestOrigin_Lua:
+          return OrthancPluginInstanceOrigin_Lua;
+
+        case RequestOrigin_Plugins:
+          return OrthancPluginInstanceOrigin_Plugin;
+
+        case RequestOrigin_Unknown:
+          return OrthancPluginInstanceOrigin_Unknown;
+
+        default:
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+    }
+
+
 #if !defined(ORTHANC_ENABLE_DCMTK) || ORTHANC_ENABLE_DCMTK != 0
     DcmEVR Convert(OrthancPluginValueRepresentation vr)
     {
--- a/Plugins/Engine/PluginsEnumerations.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/Plugins/Engine/PluginsEnumerations.h	Wed Dec 02 09:52:56 2015 +0100
@@ -65,6 +65,8 @@
 
     IdentifierConstraintType Convert(OrthancPluginIdentifierConstraint constraint);
 
+    OrthancPluginInstanceOrigin Convert(RequestOrigin origin);
+
 #if !defined(ORTHANC_ENABLE_DCMTK) || ORTHANC_ENABLE_DCMTK != 0
     DcmEVR Convert(OrthancPluginValueRepresentation vr);
 #endif
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Wed Nov 18 10:16:21 2015 +0100
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Wed Dec 02 09:52:56 2015 +0100
@@ -18,6 +18,8 @@
  *    - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback().
  *    - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea().
  *    - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2().
+ *    - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback().
+ *    - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback().
  * -# <tt>void OrthancPluginFinalize()</tt>:
  *    This function is invoked by Orthanc during its shutdown. The plugin
  *    must free all its memory.
@@ -49,6 +51,9 @@
  * @defgroup Callbacks Callbacks
  * @brief Functions to register and manage callbacks by the plugins.
  *
+ * @defgroup Worklists Worklists
+ * @brief Functions to register and manage worklists.
+ *
  * @defgroup Orthanc Orthanc
  * @brief Functions to access the content of the Orthanc server.
  **/
@@ -271,6 +276,7 @@
     OrthancPluginErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
     OrthancPluginErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
     OrthancPluginErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
+    OrthancPluginErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
 
     _OrthancPluginErrorCode_INTERNAL = 0x7fffffff
   } OrthancPluginErrorCode;
@@ -391,6 +397,9 @@
     _OrthancPluginService_RegisterDictionaryTag = 20,
     _OrthancPluginService_DicomBufferToJson = 21,
     _OrthancPluginService_DicomInstanceToJson = 22,
+    _OrthancPluginService_CreateDicom = 23,
+    _OrthancPluginService_ComputeMd5 = 24,
+    _OrthancPluginService_ComputeSha1 = 25,
 
     /* Registration of callbacks */
     _OrthancPluginService_RegisterRestCallback = 1000,
@@ -398,6 +407,8 @@
     _OrthancPluginService_RegisterStorageArea = 1002,
     _OrthancPluginService_RegisterOnChangeCallback = 1003,
     _OrthancPluginService_RegisterRestCallbackNoLock = 1004,
+    _OrthancPluginService_RegisterWorklistCallback = 1005,
+    _OrthancPluginService_RegisterDecodeImageCallback = 1006,
 
     /* Sending answers to REST calls */
     _OrthancPluginService_AnswerBuffer = 2000,
@@ -439,6 +450,7 @@
     _OrthancPluginService_GetInstanceSimplifiedJson = 4004,
     _OrthancPluginService_HasInstanceMetadata = 4005,
     _OrthancPluginService_GetInstanceMetadata = 4006,
+    _OrthancPluginService_GetInstanceOrigin = 4007,
 
     /* Services for plugins implementing a database back-end */
     _OrthancPluginService_RegisterDatabaseBackend = 5000,
@@ -461,6 +473,15 @@
     _OrthancPluginService_GetFontsCount = 6009,
     _OrthancPluginService_GetFontInfo = 6010,
     _OrthancPluginService_DrawText = 6011,
+    _OrthancPluginService_CreateImage = 6012,
+    _OrthancPluginService_CreateImageAccessor = 6013,
+    _OrthancPluginService_DecodeDicomImage = 6014,
+
+    /* Primitives for handling worklists */
+    _OrthancPluginService_WorklistAddAnswer = 7000,
+    _OrthancPluginService_WorklistMarkIncomplete = 7001,
+    _OrthancPluginService_WorklistIsMatch = 7002,
+    _OrthancPluginService_WorklistGetDicomQuery = 7003,
 
     _OrthancPluginService_INTERNAL = 0x7fffffff
   } _OrthancPluginService;
@@ -606,8 +627,9 @@
    **/
   typedef enum
   {
-    OrthancPluginImageFormat_Png = 0,   /*!< Image compressed using PNG */
-    OrthancPluginImageFormat_Jpeg = 1,  /*!< Image compressed using JPEG */
+    OrthancPluginImageFormat_Png = 0,    /*!< Image compressed using PNG */
+    OrthancPluginImageFormat_Jpeg = 1,   /*!< Image compressed using JPEG */
+    OrthancPluginImageFormat_Dicom = 2,  /*!< Image compressed using DICOM */
 
     _OrthancPluginImageFormat_INTERNAL = 0x7fffffff
   } OrthancPluginImageFormat;
@@ -654,6 +676,7 @@
   /**
    * The possible output formats for a DICOM-to-JSON conversion.
    * @ingroup Toolbox
+   * @see OrthancPluginDicomToJson()
    **/
   typedef enum
   {
@@ -684,20 +707,48 @@
 
 
   /**
+   * Flags to the creation of a DICOM file.
+   * @ingroup Toolbox
+   * @see OrthancPluginCreateDicom()
+   **/
+  typedef enum
+  {
+    OrthancPluginCreateDicomFlags_DecodeDataUriScheme   = (1 << 0),  /*!< Decode fields encoded using data URI scheme */
+    OrthancPluginCreateDicomFlags_GenerateIdentifiers   = (1 << 1),  /*!< Automatically generate DICOM identifiers */
+
+    _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff
+  } OrthancPluginCreateDicomFlags;
+
+
+  /**
    * The constraints on the DICOM identifiers that must be supported
    * by the database plugins.
    **/
   typedef enum
   {
-    OrthancPluginIdentifierConstraint_Equal,           /*!< Equal */
-    OrthancPluginIdentifierConstraint_SmallerOrEqual,  /*!< Less or equal */
-    OrthancPluginIdentifierConstraint_GreaterOrEqual,  /*!< More or equal */
-    OrthancPluginIdentifierConstraint_Wildcard,        /*!< Case-sensitive wildcard matching (with * and ?) */
+    OrthancPluginIdentifierConstraint_Equal = 1,           /*!< Equal */
+    OrthancPluginIdentifierConstraint_SmallerOrEqual = 2,  /*!< Less or equal */
+    OrthancPluginIdentifierConstraint_GreaterOrEqual = 3,  /*!< More or equal */
+    OrthancPluginIdentifierConstraint_Wildcard = 4,        /*!< Case-sensitive wildcard matching (with * and ?) */
 
     _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff
   } OrthancPluginIdentifierConstraint;
 
 
+  /**
+   * The origin of a DICOM instance that has been received by Orthanc.
+   **/
+  typedef enum
+  {
+    OrthancPluginInstanceOrigin_Unknown = 1,        /*!< Unknown origin */
+    OrthancPluginInstanceOrigin_DicomProtocol = 2,  /*!< Instance received through DICOM protocol */
+    OrthancPluginInstanceOrigin_RestApi = 3,        /*!< Instance received through REST API of Orthanc */
+    OrthancPluginInstanceOrigin_Plugin = 4,         /*!< Instance added to Orthanc by a plugin */
+    OrthancPluginInstanceOrigin_Lua = 5,            /*!< Instance added to Orthanc by a Lua script */
+
+    _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff
+  } OrthancPluginInstanceOrigin;
+
 
   /**
    * @brief A memory buffer allocated by the core system of Orthanc.
@@ -754,6 +805,22 @@
 
 
   /**
+   * @brief Opaque structure to an object that represents a C-Find query.
+   * @ingroup Worklists
+   **/
+  typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents the answers to a C-Find query.
+   * @ingroup Worklists
+   **/
+  typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers;
+
+
+
+  /**
    * @brief Signature of a callback function that answers to a REST request.
    * @ingroup Callbacks
    **/
@@ -786,6 +853,18 @@
 
 
   /**
+   * @brief Signature of a callback function to decode a DICOM instance as an image.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) (
+    OrthancPluginImage** target,
+    const void* dicom,
+    const uint32_t size,
+    uint32_t frameIndex);
+
+
+
+  /**
    * @brief Signature of a function to free dynamic memory.
    **/
   typedef void (*OrthancPluginFree) (void* buffer);
@@ -849,6 +928,27 @@
 
 
   /**
+   * @brief Callback to handle the C-Find SCP requests received by Orthanc.
+   *
+   * Signature of a callback function that is triggered when Orthanc
+   * receives a C-Find SCP request against modality worklists.
+   *
+   * @param answers The target structure where answers must be stored.
+   * @param query The worklist query.
+   * @param remoteAet The Application Entity Title (AET) of the modality from which the request originates.
+   * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) (
+    OrthancPluginWorklistAnswers*     answers,
+    const OrthancPluginWorklistQuery* query,
+    const char*                       remoteAet,
+    const char*                       calledAet);
+
+
+
+  /**
    * @brief Data structure that contains information about the Orthanc core.
    **/
   typedef struct _OrthancPluginContext_t
@@ -913,7 +1013,9 @@
         sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) ||
         sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) ||
         sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) ||
-        sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint))
+        sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) ||
+        sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin))
     {
       /* Mismatch in the size of the enumerations */
       return 0;
@@ -1248,7 +1350,7 @@
    * file is stored into a newly allocated memory buffer.
    * 
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer.
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
    * @param instanceId The Orthanc identifier of the DICOM instance of interest.
    * @return 0 if success, or the error code if failure.
    * @ingroup Orthanc
@@ -1279,7 +1381,7 @@
    * the query is stored into a newly allocated memory buffer.
    * 
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer.
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
    * @param uri The URI in the built-in Orthanc API.
    * @return 0 if success, or the error code if failure.
    * @see OrthancPluginRestApiGetAfterPlugins
@@ -1308,7 +1410,7 @@
    * query is stored into a newly allocated memory buffer.
    * 
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer.
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
    * @param uri The URI in the built-in Orthanc API.
    * @return 0 if success, or the error code if failure.
    * @see OrthancPluginRestApiGet
@@ -1342,7 +1444,7 @@
    * the query is stored into a newly allocated memory buffer.
    * 
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer.
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
    * @param uri The URI in the built-in Orthanc API.
    * @param body The body of the POST request.
    * @param bodySize The size of the body.
@@ -1376,7 +1478,7 @@
    * query is stored into a newly allocated memory buffer.
    * 
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer.
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
    * @param uri The URI in the built-in Orthanc API.
    * @param body The body of the POST request.
    * @param bodySize The size of the body.
@@ -1450,7 +1552,7 @@
    * the query is stored into a newly allocated memory buffer.
    * 
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer.
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
    * @param uri The URI in the built-in Orthanc API.
    * @param body The body of the PUT request.
    * @param bodySize The size of the body.
@@ -1485,7 +1587,7 @@
    * query is stored into a newly allocated memory buffer.
    * 
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer.
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
    * @param uri The URI in the built-in Orthanc API.
    * @param body The body of the PUT request.
    * @param bodySize The size of the body.
@@ -1861,11 +1963,12 @@
 
   typedef struct
   {
-    char**                      resultStringToFree;
-    const char**                resultString;
-    int64_t*                    resultInt64;
-    const char*                 key;
-    OrthancPluginDicomInstance* instance;
+    char**                       resultStringToFree;
+    const char**                 resultString;
+    int64_t*                     resultInt64;
+    const char*                  key;
+    OrthancPluginDicomInstance*  instance;
+    OrthancPluginInstanceOrigin* resultOrigin;   /* New in Orthanc 0.9.5 SDK */
   } _OrthancPluginAccessDicomInstance;
 
 
@@ -2665,7 +2768,7 @@
    * version of the zlib library that is used by the Orthanc core.
    * 
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer.
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
    * @param source The source buffer.
    * @param size The size in bytes of the source buffer.
    * @param compression The compression algorithm.
@@ -2707,7 +2810,7 @@
    * a newly allocated memory buffer.
    * 
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer.
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
    * @param path The path of the file to be read.
    * @return 0 if success, or the error code if failure.
    **/
@@ -2848,7 +2951,7 @@
     const OrthancPluginImage*  image;
     uint32_t*                  resultUint32;
     OrthancPluginPixelFormat*  resultPixelFormat;
-    const void**               resultBuffer;
+    void**                     resultBuffer;
   } _OrthancPluginGetImageInfo;
 
 
@@ -2997,11 +3100,11 @@
    * @return The pointer.
    * @ingroup Images
    **/
-  ORTHANC_PLUGIN_INLINE const void*  OrthancPluginGetImageBuffer(
+  ORTHANC_PLUGIN_INLINE void*  OrthancPluginGetImageBuffer(
     OrthancPluginContext*      context,
     const OrthancPluginImage*  image)
   {
-    const void* target = NULL;
+    void* target = NULL;
 
     _OrthancPluginGetImageInfo params;
     memset(&params, 0, sizeof(params));
@@ -3265,7 +3368,7 @@
    * Orthanc instance that hosts this plugin.
    * 
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer.
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
    * @param url The URL of interest.
    * @param username The username (can be <tt>NULL</tt> if no password protection).
    * @param password The password (can be <tt>NULL</tt> if no password protection).
@@ -3300,7 +3403,7 @@
    * the Orthanc instance that hosts this plugin.
    * 
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer.
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
    * @param url The URL of interest.
    * @param body The content of the body of the request.
    * @param bodySize The size of the body of the request.
@@ -3341,7 +3444,7 @@
    * Orthanc instance that hosts this plugin.
    * 
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer.
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
    * @param url The URL of interest.
    * @param body The content of the body of the request.
    * @param bodySize The size of the body of the request.
@@ -3890,7 +3993,7 @@
    * @param buffer The memory buffer containing the DICOM file.
    * @param size The size of the memory buffer.
    * @param format The output format.
-   * @param flags The output flags.
+   * @param flags Flags governing the output.
    * @param maxStringLength The maximum length of a field. Too long fields will
    * be output as "null". The 0 value means no maximum length.
    * @return The NULL value if the case of an error, or the JSON
@@ -3939,7 +4042,7 @@
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
    * @param instanceId The Orthanc identifier of the instance.
    * @param format The output format.
-   * @param flags The output flags.
+   * @param flags Flags governing the output.
    * @param maxStringLength The maximum length of a field. Too long fields will
    * be output as "null". The 0 value means no maximum length.
    * @return The NULL value if the case of an error, or the JSON
@@ -3994,7 +4097,7 @@
    * allocated memory buffer.
    * 
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
-   * @param target The target memory buffer.
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
    * @param uri The URI in the built-in Orthanc API.
    * @param headersCount The number of HTTP headers.
    * @param headersKeys Array containing the keys of the HTTP headers.
@@ -4025,6 +4128,505 @@
     return context->InvokeService(context, _OrthancPluginService_RestApiGet2, &params);
   }
 
+
+
+  typedef struct
+  {
+    OrthancPluginWorklistCallback callback;
+  } _OrthancPluginWorklistCallback;
+
+  /**
+   * @brief Register a callback to handle modality worklists requests.
+   *
+   * This function registers a callback to handle C-Find SCP requests
+   * on modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistCallback  callback)
+  {
+    _OrthancPluginWorklistCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, &params);
+  }
+
+
+  
+  typedef struct
+  {
+    OrthancPluginWorklistAnswers*      answers;
+    const OrthancPluginWorklistQuery*  query;
+    const void*                        dicom;
+    uint32_t                           size;
+  } _OrthancPluginWorklistAnswersOperation;
+
+  /**
+   * @brief Add one answer to some modality worklist request.
+   *
+   * This function adds one worklist (encoded as a DICOM file) to the
+   * set of answers corresponding to some C-Find SCP request against
+   * modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @param query The worklist query, as received by the callback.
+   * @param dicom The worklist to answer, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistAddAnswer(
+    OrthancPluginContext*             context,
+    OrthancPluginWorklistAnswers*     answers,
+    const OrthancPluginWorklistQuery* query,
+    const void*                       dicom,
+    uint32_t                          size)
+  {
+    _OrthancPluginWorklistAnswersOperation params;
+    params.answers = answers;
+    params.query = query;
+    params.dicom = dicom;
+    params.size = size;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, &params);
+  }
+
+
+  /**
+   * @brief Mark the set of worklist answers as incomplete.
+   *
+   * This function marks as incomplete the set of answers
+   * corresponding to some C-Find SCP request against modality
+   * worklists. This must be used if canceling the handling of a
+   * request when too many answers are to be returned.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistMarkIncomplete(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistAnswers*  answers)
+  {
+    _OrthancPluginWorklistAnswersOperation params;
+    params.answers = answers;
+    params.query = NULL;
+    params.dicom = NULL;
+    params.size = 0;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, &params);
+  }
+
+
+  typedef struct
+  {
+    const OrthancPluginWorklistQuery*  query;
+    const void*                        dicom;
+    uint32_t                           size;
+    int32_t*                           isMatch;
+    OrthancPluginMemoryBuffer*         target;
+  } _OrthancPluginWorklistQueryOperation;
+
+  /**
+   * @brief Test whether a worklist matches the query.
+   *
+   * This function checks whether one worklist (encoded as a DICOM
+   * file) matches the C-Find SCP query against modality
+   * worklists. This function must be called before adding the
+   * worklist as an answer through OrthancPluginWorklistAddAnswer().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The worklist query, as received by the callback.
+   * @param dicom The worklist to answer, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 1 if the worklist matches the query, 0 otherwise.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE int32_t  OrthancPluginWorklistIsMatch(
+    OrthancPluginContext*              context,
+    const OrthancPluginWorklistQuery*  query,
+    const void*                        dicom,
+    uint32_t                           size)
+  {
+    int32_t isMatch = 0;
+
+    _OrthancPluginWorklistQueryOperation params;
+    params.query = query;
+    params.dicom = dicom;
+    params.size = size;
+    params.isMatch = &isMatch;
+    params.target = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, &params) == OrthancPluginErrorCode_Success)
+    {
+      return isMatch;
+    }
+    else
+    {
+      /* Error: Assume non-match */
+      return 0;
+    }
+  }
+
+
+  /**
+   * @brief Retrieve the worklist query as a DICOM file.
+   *
+   * This function retrieves the DICOM file that underlies a C-Find
+   * SCP query against modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param query The worklist query, as received by the callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistGetDicomQuery(
+    OrthancPluginContext*              context,
+    OrthancPluginMemoryBuffer*         target,
+    const OrthancPluginWorklistQuery*  query)
+  {
+    _OrthancPluginWorklistQueryOperation params;
+    params.query = query;
+    params.dicom = NULL;
+    params.size = 0;
+    params.isMatch = NULL;
+    params.target = target;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, &params);
+  }
+
+
+  /**
+   * @brief Get the origin of a DICOM file.
+   *
+   * This function returns the origin of a DICOM instance that has been received by Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The origin of the instance.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin(
+    OrthancPluginContext*       context,
+    OrthancPluginDicomInstance* instance)
+  {
+    OrthancPluginInstanceOrigin origin;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultOrigin = &origin;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return OrthancPluginInstanceOrigin_Unknown;
+    }
+    else
+    {
+      return origin;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*     target;
+    const char*                    json;
+    const OrthancPluginImage*      pixelData;
+    OrthancPluginCreateDicomFlags  flags;
+  } _OrthancPluginCreateDicom;
+
+  /**
+   * @brief Create a DICOM instance from a JSON string and an image.
+   *
+   * This function takes as input a string containing a JSON file
+   * describing the content of a DICOM instance. As an output, it
+   * writes the corresponding DICOM instance to a newly allocated
+   * memory buffer. Additionally, an image to be encoded within the
+   * DICOM instance can also be provided.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param json The input JSON file.
+   * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme.
+   * @param flags Flags governing the output.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomBufferToJson
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom(
+    OrthancPluginContext*          context,
+    OrthancPluginMemoryBuffer*     target,
+    const char*                    json,
+    const OrthancPluginImage*      pixelData,
+    OrthancPluginCreateDicomFlags  flags)
+  {
+    _OrthancPluginCreateDicom params;
+    params.target = target;
+    params.json = json;
+    params.pixelData = pixelData;
+    params.flags = flags;
+
+    return context->InvokeService(context, _OrthancPluginService_CreateDicom, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginDecodeImageCallback callback;
+  } _OrthancPluginDecodeImageCallback;
+
+  /**
+   * @brief Register a callback to handle the decoding of DICOM images.
+   *
+   * This function registers a custom callback to the decoding of
+   * DICOM images, replacing the built-in decoder of Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback(
+    OrthancPluginContext*             context,
+    OrthancPluginDecodeImageCallback  callback)
+  {
+    _OrthancPluginDecodeImageCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, &params);
+  }
+  
+
+
+  typedef struct
+  {
+    OrthancPluginImage**       target;
+    OrthancPluginPixelFormat   format;
+    uint32_t                   width;
+    uint32_t                   height;
+    uint32_t                   pitch;
+    void*                      buffer;
+    const void*                constBuffer;
+    uint32_t                   bufferSize;
+    uint32_t                   frameIndex;
+  } _OrthancPluginCreateImage;
+
+
+  /**
+   * @brief Create an image.
+   *
+   * This function creates an image of given size and format.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param format The format of the pixels.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage(
+    OrthancPluginContext*     context,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.format = format;
+    params.width = width;
+    params.height = height;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  /**
+   * @brief Create an image pointing to a memory buffer.
+   *
+   * This function creates an image whose content points to a memory
+   * buffer managed by the plugin. Note that the buffer is directly
+   * accessed, no memory is allocated and no data is copied.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param format The format of the pixels.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer.
+   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor(
+    OrthancPluginContext*     context,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    void*                     buffer)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.format = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  /**
+   * @brief Decode one frame from a DICOM instance.
+   *
+   * This function decodes one frame of a DICOM image that is stored
+   * in a memory buffer. This function will give the same result as
+   * OrthancPluginUncompressImage() for single-frame DICOM images.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer Pointer to a memory buffer containing the DICOM image.
+   * @param bufferSize Size of the memory buffer containing the DICOM image.
+   * @param frameIndex The index of the frame of interest in a multi-frame image.
+   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               bufferSize,
+    uint32_t               frameIndex)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.constBuffer = buffer;
+    params.bufferSize = bufferSize;
+    params.frameIndex = frameIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    char**       result;
+    const void*  buffer;
+    uint32_t     size;
+  } _OrthancPluginComputeHash;
+
+  /**
+   * @brief Compute an MD5 hash.
+   *
+   * This functions computes the MD5 cryptographic hash of the given memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The source memory buffer.
+   * @param size The size in bytes of the source buffer.
+   * @return The NULL value in case of error, or a string containing the cryptographic hash.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    char* result;
+
+    _OrthancPluginComputeHash params;
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Compute a SHA-1 hash.
+   *
+   * This functions computes the SHA-1 cryptographic hash of the given memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The source memory buffer.
+   * @param size The size in bytes of the source buffer.
+   * @return The NULL value in case of error, or a string containing the cryptographic hash.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    char* result;
+
+    _OrthancPluginComputeHash params;
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
 #ifdef  __cplusplus
 }
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/AutomatedJpeg2kCompression/CMakeLists.txt	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,8 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(Basic)
+
+set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..)
+include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake)
+
+add_library(AutomatedJpeg2kCompression SHARED Plugin.cpp)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/AutomatedJpeg2kCompression/Plugin.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,162 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include <orthanc/OrthancCPlugin.h>
+
+#include <string>
+
+static OrthancPluginContext* context_ = NULL;
+
+
+static bool ReadFile(std::string& result,
+                     const std::string& path)
+{
+  OrthancPluginMemoryBuffer tmp;
+  if (OrthancPluginReadFile(context_, &tmp, path.c_str()) == OrthancPluginErrorCode_Success)
+  {
+    result.assign(reinterpret_cast<const char*>(tmp.data), tmp.size);
+    OrthancPluginFreeMemoryBuffer(context_, &tmp);
+    return true;
+  }
+  else
+  {
+    return false;
+  }
+}
+
+
+OrthancPluginErrorCode OnStoredCallback(OrthancPluginDicomInstance* instance,
+                                        const char* instanceId)
+{
+  char buffer[1024];
+  sprintf(buffer, "Just received a DICOM instance of size %d and ID %s from origin %d (AET %s)", 
+          (int) OrthancPluginGetInstanceSize(context_, instance), instanceId, 
+          OrthancPluginGetInstanceOrigin(context_, instance),
+          OrthancPluginGetInstanceRemoteAet(context_, instance));
+  OrthancPluginLogInfo(context_, buffer);
+
+  if (OrthancPluginGetInstanceOrigin(context_, instance) == OrthancPluginInstanceOrigin_Plugin)
+  {
+    // Do not compress twice the same file
+    return OrthancPluginErrorCode_Success;
+  }
+
+  // Write the uncompressed DICOM content to some temporary file
+  std::string uncompressed = "uncompressed-" + std::string(instanceId) + ".dcm";
+  OrthancPluginErrorCode error = OrthancPluginWriteFile(context_, uncompressed.c_str(), 
+                                                        OrthancPluginGetInstanceData(context_, instance),
+                                                        OrthancPluginGetInstanceSize(context_, instance));
+  if (error)
+  {
+    return error;
+  }
+
+  // Remove the original DICOM instance
+  std::string uri = "/instances/" + std::string(instanceId);
+  error = OrthancPluginRestApiDelete(context_, uri.c_str());
+  if (error)
+  {
+    return error;
+  }
+
+  // Path to the temporary file that will contain the compressed DICOM content
+  std::string compressed = "compressed-" + std::string(instanceId) + ".dcm";
+
+  // Compress to JPEG2000 using gdcm
+  std::string command1 = "gdcmconv --j2k " + uncompressed + " " + compressed;
+
+  // Generate a new SOPInstanceUID for the JPEG2000 file, as gdcmconv
+  // does not do this by itself
+  std::string command2 = "dcmodify --no-backup -gin " + compressed;
+
+  // Make the required system calls
+  system(command1.c_str());
+  system(command2.c_str());
+
+  // Read the result of the JPEG2000 compression
+  std::string j2k;
+  bool ok = ReadFile(j2k, compressed);
+
+  // Remove the two temporary files
+  remove(compressed.c_str());
+  remove(uncompressed.c_str());
+
+  if (!ok)
+  {
+    return OrthancPluginErrorCode_Plugin;
+  }
+
+  // Upload the JPEG2000 file through the REST API
+  OrthancPluginMemoryBuffer tmp;
+  if (OrthancPluginRestApiPost(context_, &tmp, "/instances", j2k.c_str(), j2k.size()))
+  {
+    ok = false;
+  }
+
+  if (ok)
+  {
+    OrthancPluginFreeMemoryBuffer(context_, &tmp);
+  }
+
+  return ok ? OrthancPluginErrorCode_Success : OrthancPluginErrorCode_Plugin;
+}
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c)
+  {
+    context_ = c;
+
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(c) == 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;
+    }
+
+    OrthancPluginRegisterOnStoredInstanceCallback(context_, OnStoredCallback);
+
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return "sample-jpeg2k";
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return "0.0";
+  }
+}
--- a/Plugins/Samples/Basic/Plugin.c	Wed Nov 18 10:16:21 2015 +0100
+++ b/Plugins/Samples/Basic/Plugin.c	Wed Dec 02 09:52:56 2015 +0100
@@ -262,8 +262,9 @@
   char* json;
   static int first = 1;
 
-  sprintf(buffer, "Just received a DICOM instance of size %d and ID %s from AET %s", 
+  sprintf(buffer, "Just received a DICOM instance of size %d and ID %s from origin %d (AET %s)", 
           (int) OrthancPluginGetInstanceSize(context, instance), instanceId, 
+          OrthancPluginGetInstanceOrigin(context, instance),
           OrthancPluginGetInstanceRemoteAet(context, instance));
 
   OrthancPluginLogWarning(context, buffer);  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/CustomImageDecoder/CMakeLists.txt	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,8 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(CustomImageDecoder)
+
+set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..)
+include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake)
+
+add_library(PluginTest SHARED Plugin.cpp)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/CustomImageDecoder/Plugin.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,80 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include <orthanc/OrthancCPlugin.h>
+
+static OrthancPluginContext* context_ = NULL;
+
+
+static OrthancPluginErrorCode DecodeImageCallback(OrthancPluginImage** target,
+                                                  const void* dicom,
+                                                  const uint32_t size,
+                                                  uint32_t frameIndex)
+{
+  *target = OrthancPluginCreateImage(context_, OrthancPluginPixelFormat_RGB24, 512, 512);
+
+  memset(OrthancPluginGetImageBuffer(context_, *target), 128,
+         OrthancPluginGetImageHeight(context_, *target) * OrthancPluginGetImagePitch(context_, *target));
+
+  return OrthancPluginDrawText(context_, *target, 0, "Hello world", 100, 50, 255, 0, 0);
+}
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c)
+  {
+    context_ = c;
+
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(c) == 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;
+    }
+
+    OrthancPluginRegisterDecodeImageCallback(context_, DecodeImageCallback);
+
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return "custom-image-decoder";
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return "0.0";
+  }
+}
--- a/Plugins/Samples/DatabasePlugin/Database.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/Plugins/Samples/DatabasePlugin/Database.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -123,7 +123,8 @@
 
 public:
   SignalRemainingAncestor() : 
-    hasRemainingAncestor_(false)
+    hasRemainingAncestor_(false),
+    remainingType_(OrthancPluginResourceType_Instance)  // Some dummy value
   {
   }
 
@@ -175,7 +176,8 @@
 
 Database::Database(const std::string& path) : 
   path_(path),
-  base_(db_)
+  base_(db_),
+  signalRemainingAncestor_(NULL)
 {
 }
 
@@ -381,7 +383,7 @@
                         const std::list<I>& source)
 {
   for (typename std::list<I>::const_iterator 
-         it = source.begin(); it != source.end(); it++)
+         it = source.begin(); it != source.end(); ++it)
   {
     target.push_back(*it);
   }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/GdcmDecoder/CMakeLists.txt	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,32 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(GdcmDecoder)
+
+SET(GDCM_DECODER_VERSION "0.0" CACHE STRING "Version of the plugin")
+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")
+SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of boost")
+
+set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..)
+include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake)
+
+find_package(GDCM REQUIRED)
+if (GDCM_FOUND)
+  include(${GDCM_USE_FILE})
+  set(GDCM_LIBRARIES gdcmCommon gdcmMSFF)
+else(GDCM_FOUND)
+  message(FATAL_ERROR "Cannot find GDCM, did you set GDCM_DIR?")
+endif(GDCM_FOUND)
+
+add_definitions(-DGDCM_DECODER_VERSION="${GDCM_DECODER_VERSION}")
+
+add_library(GdcmDecoder SHARED
+  ${BOOST_SOURCES}
+  GdcmDecoderCache.cpp
+  GdcmImageDecoder.cpp
+  OrthancImageWrapper.cpp
+  Plugin.cpp
+  )
+
+target_link_libraries(GdcmDecoder ${GDCM_LIBRARIES})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/GdcmDecoder/GdcmDecoderCache.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,98 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "GdcmDecoderCache.h"
+
+#include "OrthancImageWrapper.h"
+
+namespace OrthancPlugins
+{
+  std::string GdcmDecoderCache::ComputeMd5(OrthancPluginContext* context,
+                                           const void* dicom,
+                                           size_t size)
+  {
+    std::string result;
+
+    char* md5 = OrthancPluginComputeMd5(context, dicom, size);
+
+    if (md5 == NULL)
+    {
+      throw std::runtime_error("Cannot compute MD5 hash");
+    }
+
+    bool ok = false;
+    try
+    {
+      result.assign(md5);
+      ok = true;
+    }
+    catch (...)
+    {
+    }
+
+    OrthancPluginFreeString(context, md5);
+
+    if (!ok)
+    {
+      throw std::runtime_error("Not enough memory");
+    }
+    else
+    {    
+      return result;
+    }
+  }
+
+
+  OrthancImageWrapper* GdcmDecoderCache::Decode(OrthancPluginContext* context,
+                                                const void* dicom,
+                                                const uint32_t size,
+                                                uint32_t frameIndex)
+  {
+    std::string md5 = ComputeMd5(context, dicom, size);
+
+    // First check whether the previously decoded image is the same
+    // as this one
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      if (decoder_.get() != NULL &&
+          size_ == size &&
+          md5_ == md5)
+      {
+        // This is the same image: Reuse the previous decoding
+        return new OrthancImageWrapper(context, decoder_->Decode(context, frameIndex));
+      }
+    }
+
+    // This is not the same image
+    std::auto_ptr<GdcmImageDecoder> decoder(new GdcmImageDecoder(dicom, size));
+    std::auto_ptr<OrthancImageWrapper> image(new OrthancImageWrapper(context, decoder->Decode(context, frameIndex)));
+
+    {
+      // Cache the newly created decoder for further use
+      boost::mutex::scoped_lock lock(mutex_);
+      decoder_ = decoder;
+      size_ = size;
+      md5_ = md5;
+    }
+
+    return image.release();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/GdcmDecoder/GdcmDecoderCache.h	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,53 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "GdcmImageDecoder.h"
+#include "OrthancImageWrapper.h"
+
+#include <boost/thread.hpp>
+
+
+namespace OrthancPlugins
+{
+  class GdcmDecoderCache : public boost::noncopyable
+  {
+  private:
+    boost::mutex   mutex_;
+    std::auto_ptr<OrthancPlugins::GdcmImageDecoder>  decoder_;
+    size_t       size_;
+    std::string  md5_;
+
+    static std::string ComputeMd5(OrthancPluginContext* context,
+                                  const void* dicom,
+                                  size_t size);
+
+  public:
+    GdcmDecoderCache() : size_(0)
+    {
+    }
+
+    OrthancImageWrapper* Decode(OrthancPluginContext* context,
+                                const void* dicom,
+                                const uint32_t size,
+                                uint32_t frameIndex);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/GdcmDecoder/GdcmImageDecoder.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,300 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "GdcmImageDecoder.h"
+
+#include "OrthancImageWrapper.h"
+
+#include <gdcmImageReader.h>
+#include <gdcmImageApplyLookupTable.h>
+#include <gdcmImageChangePlanarConfiguration.h>
+#include <gdcmImageChangePhotometricInterpretation.h>
+#include <stdexcept>
+#include <boost/iostreams/stream.hpp>
+#include <boost/iostreams/device/array.hpp>
+
+
+namespace OrthancPlugins
+{
+  struct GdcmImageDecoder::PImpl
+  {
+    const void*           dicom_;
+    size_t                size_;
+
+    gdcm::ImageReader reader_;
+    std::auto_ptr<gdcm::ImageApplyLookupTable> lut_;
+    std::auto_ptr<gdcm::ImageChangePhotometricInterpretation> photometric_;
+    std::auto_ptr<gdcm::ImageChangePlanarConfiguration> interleaved_;
+    std::string decoded_;
+
+    PImpl(const void* dicom,
+          size_t size) :
+      dicom_(dicom),
+      size_(size)
+    {
+    }
+
+
+    const gdcm::DataSet& GetDataSet() const
+    {
+      return reader_.GetFile().GetDataSet();
+    }
+
+
+    const gdcm::Image& GetImage() const
+    {
+      if (interleaved_.get() != NULL)
+      {
+        return interleaved_->GetOutput();
+      }
+
+      if (lut_.get() != NULL)
+      {
+        return lut_->GetOutput();
+      }
+
+      if (photometric_.get() != NULL)
+      {
+        return photometric_->GetOutput();
+      }
+
+      return reader_.GetImage();
+    }
+
+
+    void Decode()
+    {
+      // Change photometric interpretation or apply LUT, if required
+      {
+        const gdcm::Image& image = GetImage();
+        if (image.GetPixelFormat().GetSamplesPerPixel() == 1 &&
+            image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::PALETTE_COLOR)
+        {
+          lut_.reset(new gdcm::ImageApplyLookupTable());
+          lut_->SetInput(image);
+          if (!lut_->Apply())
+          {
+            throw std::runtime_error( "GDCM cannot apply the lookup table");
+          }
+        }
+        else if (image.GetPixelFormat().GetSamplesPerPixel() == 1)
+        {
+          if (image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::MONOCHROME1 &&
+              image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::MONOCHROME2)
+          {
+            photometric_.reset(new gdcm::ImageChangePhotometricInterpretation());
+            photometric_->SetInput(image);
+            photometric_->SetPhotometricInterpretation(gdcm::PhotometricInterpretation::MONOCHROME2);
+            if (!photometric_->Change() ||
+                GetImage().GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::MONOCHROME2)
+            {
+              throw std::runtime_error("GDCM cannot change the photometric interpretation");
+            }
+          }      
+        }
+        else 
+        {
+          if (image.GetPixelFormat().GetSamplesPerPixel() == 3 &&
+              image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::RGB)
+          {
+            photometric_.reset(new gdcm::ImageChangePhotometricInterpretation());
+            photometric_->SetInput(image);
+            photometric_->SetPhotometricInterpretation(gdcm::PhotometricInterpretation::RGB);
+            if (!photometric_->Change() ||
+                GetImage().GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::RGB)
+            {
+              throw std::runtime_error("GDCM cannot change the photometric interpretation");
+            }
+          }
+        }
+      }
+
+      // Possibly convert planar configuration to interleaved
+      {
+        const gdcm::Image& image = GetImage();
+        if (image.GetPlanarConfiguration() != 0 && 
+            image.GetPixelFormat().GetSamplesPerPixel() != 1)
+        {
+          interleaved_.reset(new gdcm::ImageChangePlanarConfiguration());
+          interleaved_->SetInput(image);
+          if (!interleaved_->Change() ||
+              GetImage().GetPlanarConfiguration() != 0)
+          {
+            throw std::runtime_error("GDCM cannot change the planar configuration to interleaved");
+          }
+        }
+      }
+    }
+  };
+
+  GdcmImageDecoder::GdcmImageDecoder(const void* dicom,
+                                     size_t size) :
+    pimpl_(new PImpl(dicom, size))
+  {
+    // Setup a stream to the memory buffer
+    using namespace boost::iostreams;
+    basic_array_source<char> source(reinterpret_cast<const char*>(dicom), size);
+    stream<basic_array_source<char> > stream(source);
+
+    // Parse the DICOM instance using GDCM
+    pimpl_->reader_.SetStream(stream);
+    if (!pimpl_->reader_.Read())
+    {
+      throw std::runtime_error("Bad file format");
+    }
+
+    pimpl_->Decode();
+  }
+
+
+  OrthancPluginPixelFormat GdcmImageDecoder::GetFormat() const
+  {
+    const gdcm::Image& image = pimpl_->GetImage();
+
+    if (image.GetPixelFormat().GetSamplesPerPixel() == 1 &&
+        (image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::MONOCHROME1 ||
+         image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::MONOCHROME2))
+    {
+      switch (image.GetPixelFormat())
+      {
+        case gdcm::PixelFormat::UINT16:
+          return OrthancPluginPixelFormat_Grayscale16;
+
+        case gdcm::PixelFormat::INT16:
+          return OrthancPluginPixelFormat_SignedGrayscale16;
+
+        case gdcm::PixelFormat::UINT8:
+          return OrthancPluginPixelFormat_Grayscale8;
+
+        default:
+          throw std::runtime_error("Unsupported pixel format");
+      }
+    }
+    else if (image.GetPixelFormat().GetSamplesPerPixel() == 3 &&
+             image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::RGB)
+    {
+      switch (image.GetPixelFormat())
+      {
+        case gdcm::PixelFormat::UINT8:
+          return OrthancPluginPixelFormat_RGB24;
+
+        default:
+          break;
+      }      
+    }
+
+    throw std::runtime_error("Unsupported pixel format");
+  }
+
+
+  unsigned int GdcmImageDecoder::GetWidth() const
+  {
+    return pimpl_->GetImage().GetColumns();
+  }
+
+
+  unsigned int GdcmImageDecoder::GetHeight() const
+  {
+    return pimpl_->GetImage().GetRows();
+  }
+
+  
+  unsigned int GdcmImageDecoder::GetFramesCount() const
+  {
+    return pimpl_->GetImage().GetDimension(2);
+  }
+
+
+  size_t GdcmImageDecoder::GetBytesPerPixel(OrthancPluginPixelFormat format)
+  {
+    switch (format)
+    {
+      case OrthancPluginPixelFormat_Grayscale8:
+        return 1;
+
+      case OrthancPluginPixelFormat_Grayscale16:
+      case OrthancPluginPixelFormat_SignedGrayscale16:
+        return 2;
+
+      case OrthancPluginPixelFormat_RGB24:
+        return 3;
+
+      default:
+        throw std::runtime_error("Unsupport pixel format");
+    }
+  }
+
+
+  OrthancPluginImage* GdcmImageDecoder::Decode(OrthancPluginContext* context,
+                                               unsigned int frameIndex) const
+  {
+    unsigned int frames = GetFramesCount();
+    unsigned int width = GetWidth();
+    unsigned int height = GetHeight();
+    OrthancPluginPixelFormat format = GetFormat();
+    size_t bpp = GetBytesPerPixel(format);
+
+    if (frameIndex >= frames)
+    {
+      throw std::runtime_error("Inexistent frame index");
+    }
+
+    std::string& decoded = pimpl_->decoded_;
+    OrthancImageWrapper target(context, format, width, height);
+
+    if (width == 0 ||
+        height == 0)
+    {
+      return target.Release();
+    }
+
+    if (decoded.empty())
+    {
+      decoded.resize(pimpl_->GetImage().GetBufferLength());
+      pimpl_->GetImage().GetBuffer(&decoded[0]);
+    }
+
+    const void* sourceBuffer = &decoded[0];
+
+    if (target.GetPitch() == bpp * width &&
+        frames == 1)
+    {
+      assert(decoded.size() == target.GetPitch() * target.GetHeight());      
+      memcpy(target.GetBuffer(), sourceBuffer, decoded.size());
+    }
+    else 
+    {
+      size_t targetPitch = target.GetPitch();
+      size_t sourcePitch = width * bpp;
+
+      const char* a = &decoded[sourcePitch * height * frameIndex];
+      char* b = target.GetBuffer();
+
+      for (uint32_t y = 0; y < height; y++)
+      {
+        memcpy(b, a, sourcePitch);
+        a += sourcePitch;
+        b += targetPitch;
+      }
+    }
+
+    return target.Release();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/GdcmDecoder/GdcmImageDecoder.h	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,53 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <orthanc/OrthancCPlugin.h>
+#include <stdint.h>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace OrthancPlugins
+{
+  class GdcmImageDecoder : public boost::noncopyable
+  {
+  private:
+    struct PImpl;
+    boost::shared_ptr<PImpl> pimpl_;
+  
+  public:
+    GdcmImageDecoder(const void* dicom,
+                     size_t size);
+
+    OrthancPluginPixelFormat GetFormat() const;
+
+    unsigned int GetWidth() const;
+
+    unsigned int GetHeight() const;
+
+    unsigned int GetFramesCount() const;
+
+    static size_t GetBytesPerPixel(OrthancPluginPixelFormat format);
+
+    OrthancPluginImage* Decode(OrthancPluginContext* context,
+                               unsigned int frameIndex) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/GdcmDecoder/OrthancImageWrapper.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,99 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OrthancImageWrapper.h"
+
+#include <stdexcept>
+
+namespace OrthancPlugins
+{
+  OrthancImageWrapper::OrthancImageWrapper(OrthancPluginContext* context,
+                                           OrthancPluginPixelFormat format,
+                                           uint32_t width,
+                                           uint32_t height) :
+    context_(context)
+  {
+    image_ = OrthancPluginCreateImage(context_, format, width, height);
+    if (image_ == NULL)
+    {
+      throw std::runtime_error("Cannot create an image");
+    }
+  }
+
+
+  OrthancImageWrapper::OrthancImageWrapper(OrthancPluginContext* context,
+                                           OrthancPluginImage* image) :
+    context_(context),
+    image_(image)
+  {
+    if (image_ == NULL)
+    {
+      throw std::runtime_error("Invalid image returned by the core of Orthanc");
+    }
+  }
+
+
+
+  OrthancImageWrapper::~OrthancImageWrapper()
+  {
+    if (image_ != NULL)
+    {
+      OrthancPluginFreeImage(context_, image_);
+    }
+  }
+
+
+  OrthancPluginImage* OrthancImageWrapper::Release()
+  {
+    OrthancPluginImage* tmp = image_;
+    image_ = NULL;
+    return tmp;
+  }
+
+
+  uint32_t OrthancImageWrapper::GetWidth()
+  {
+    return OrthancPluginGetImageWidth(context_, image_);
+  }
+
+
+  uint32_t OrthancImageWrapper::GetHeight()
+  {
+    return OrthancPluginGetImageHeight(context_, image_);
+  }
+
+
+  uint32_t OrthancImageWrapper::GetPitch()
+  {
+    return OrthancPluginGetImagePitch(context_, image_);
+  }
+
+
+  OrthancPluginPixelFormat OrthancImageWrapper::GetFormat()
+  {
+    return OrthancPluginGetImagePixelFormat(context_, image_);
+  }
+
+
+  char* OrthancImageWrapper::GetBuffer()
+  {
+    return reinterpret_cast<char*>(OrthancPluginGetImageBuffer(context_, image_));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/GdcmDecoder/OrthancImageWrapper.h	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,63 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <orthanc/OrthancCPlugin.h>
+
+#include "GdcmImageDecoder.h"
+
+namespace OrthancPlugins
+{
+  class OrthancImageWrapper
+  {
+  private:
+    OrthancPluginContext*  context_;
+    OrthancPluginImage*    image_;
+
+  public:
+    OrthancImageWrapper(OrthancPluginContext* context,
+                        OrthancPluginPixelFormat format,
+                        uint32_t width,
+                        uint32_t height);
+
+    OrthancImageWrapper(OrthancPluginContext* context,
+                        OrthancPluginImage* image);  // Takes ownership
+
+    ~OrthancImageWrapper();
+
+    OrthancPluginContext* GetContext()
+    {
+      return context_;
+    }
+
+    OrthancPluginImage* Release();
+
+    uint32_t GetWidth();
+
+    uint32_t GetHeight();
+
+    uint32_t GetPitch();
+
+    OrthancPluginPixelFormat GetFormat();
+
+    char* GetBuffer();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/GdcmDecoder/Plugin.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,106 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "GdcmDecoderCache.h"
+#include "OrthancImageWrapper.h"
+
+#include <orthanc/OrthancCPlugin.h>
+
+static OrthancPluginContext* context_ = NULL;
+static OrthancPlugins::GdcmDecoderCache  cache_;
+
+
+static OrthancPluginErrorCode DecodeImageCallback(OrthancPluginImage** target,
+                                                  const void* dicom,
+                                                  const uint32_t size,
+                                                  uint32_t frameIndex)
+{
+  try
+  {
+    std::auto_ptr<OrthancPlugins::OrthancImageWrapper> image;
+
+#if 0
+    // Do not use the cache
+    OrthancPlugins::GdcmImageDecoder decoder(dicom, size);
+    image.reset(new OrthancPlugins::OrthancImageWrapper(context_, decoder.Decode(context_, frameIndex)));
+#else
+    image.reset(cache_.Decode(context_, dicom, size, frameIndex));
+#endif
+
+    *target = image->Release();
+
+    return OrthancPluginErrorCode_Success;
+  }
+  catch (std::runtime_error& e)
+  {
+    *target = NULL;
+
+    std::string s = "Cannot decode image using GDCM: " + std::string(e.what());
+    OrthancPluginLogError(context_, s.c_str());
+    return OrthancPluginErrorCode_Plugin;
+  }
+}
+
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
+  {
+    context_ = context;
+    OrthancPluginLogWarning(context_, "Initializing the advanced decoder of medical images using GDCM");
+
+
+    /* 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;
+    }
+
+    OrthancPluginSetDescription(context_, "Advanced decoder of medical images using GDCM.");
+    OrthancPluginRegisterDecodeImageCallback(context_, DecodeImageCallback);
+
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return "gdcm-decoder";
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return GDCM_DECODER_VERSION;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/GdcmDecoder/README	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,6 @@
+This sample shows how to replace the decoder of DICOM images that is
+built in Orthanc, by the GDCM library.
+
+A production-ready version of this sample, is available in the
+offical Web viewer plugin:
+http://www.orthanc-server.com/static.php?page=web-viewer
--- a/Plugins/Samples/GdcmDecoding/CMakeLists.txt	Wed Nov 18 10:16:21 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-cmake_minimum_required(VERSION 2.8)
-
-project(GdcmDecoding)
-
-SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages")
-SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
-
-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(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..)
-include(${CMAKE_SOURCE_DIR}/../Common/OrthancPlugins.cmake)
-
-include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake)
-
-find_package(GDCM REQUIRED)
-if (GDCM_FOUND)
-  include(${GDCM_USE_FILE})
-  set(GDCM_LIBRARIES gdcmCommon gdcmMSFF)
-else(GDCM_FOUND)
-  message(FATAL_ERROR "Cannot find GDCM, did you set GDCM_DIR?")
-endif(GDCM_FOUND)
-
-add_library(GdcmDecoding SHARED
-  Plugin.cpp
-  OrthancContext.cpp
-
-  # Sources from Orthanc
-  ${GOOGLE_LOG_SOURCES}
-  ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp
-  ${ORTHANC_ROOT}/Core/Enumerations.cpp
-  ${ORTHANC_ROOT}/Core/Images/ImageAccessor.cpp
-  ${ORTHANC_ROOT}/Core/Images/ImageBuffer.cpp
-  ${ORTHANC_ROOT}/Core/Images/ImageProcessing.cpp
-  ${ORTHANC_ROOT}/Core/Toolbox.cpp
-  ${ORTHANC_ROOT}/Resources/ThirdParty/base64/base64.cpp
-  ${ORTHANC_ROOT}/Resources/ThirdParty/md5/md5.c
-  ${JSONCPP_SOURCES}
-  ${THIRD_PARTY_SOURCES}
-  )
-
-target_link_libraries(GdcmDecoding ${GDCM_LIBRARIES})
--- a/Plugins/Samples/GdcmDecoding/OrthancContext.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,151 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "OrthancContext.h"
-
-#include <stdexcept>
-
-
-void OrthancContext::Check()
-{
-  if (context_ == NULL)
-  {
-    throw std::runtime_error("The Orthanc plugin context is not initialized");
-  }
-}
-
-
-OrthancContext& OrthancContext::GetInstance()
-{
-  static OrthancContext instance;
-  return instance;
-}
-
-
-void OrthancContext::ExtractGetArguments(Arguments& arguments,
-                                         const OrthancPluginHttpRequest& request)
-{
-  Check();
-  arguments.clear();
-
-  for (uint32_t i = 0; i < request.getCount; i++)
-  {
-    arguments[request.getKeys[i]] = request.getValues[i];
-  }
-}
-
-
-void OrthancContext::LogError(const std::string& s)
-{
-  Check();
-  OrthancPluginLogError(context_, s.c_str());
-}
-
-
-void OrthancContext::LogWarning(const std::string& s)
-{
-  Check();
-  OrthancPluginLogWarning(context_, s.c_str());
-}
-
-
-void OrthancContext::LogInfo(const std::string& s)
-{
-  Check();
-  OrthancPluginLogInfo(context_, s.c_str());
-}
-  
-
-void OrthancContext::Register(const std::string& uri,
-                              OrthancPluginRestCallback callback)
-{
-  Check();
-  OrthancPluginRegisterRestCallback(context_, uri.c_str(), callback);
-}
-
-
-void OrthancContext::GetDicomForInstance(std::string& result,
-                                         const std::string& instanceId)
-{
-  Check();
-  OrthancPluginMemoryBuffer buffer;
-    
-  if (OrthancPluginGetDicomForInstance(context_, &buffer, instanceId.c_str()))
-  {
-    throw std::runtime_error("No DICOM instance with Orthanc ID: " + instanceId);
-  }
-
-  if (buffer.size == 0)
-  {
-    result.clear();
-  }
-  else
-  {
-    result.assign(reinterpret_cast<char*>(buffer.data), buffer.size);
-  }
-
-  OrthancPluginFreeMemoryBuffer(context_, &buffer);
-}
-
-
-void OrthancContext::CompressAndAnswerPngImage(OrthancPluginRestOutput* output,
-                                               const Orthanc::ImageAccessor& accessor)
-{
-  Check();
-
-  OrthancPluginPixelFormat format;
-  switch (accessor.GetFormat())
-  {
-    case Orthanc::PixelFormat_Grayscale8:
-      format = OrthancPluginPixelFormat_Grayscale8;
-      break;
-
-    case Orthanc::PixelFormat_Grayscale16:
-      format = OrthancPluginPixelFormat_Grayscale16;
-      break;
-
-    case Orthanc::PixelFormat_SignedGrayscale16:
-      format = OrthancPluginPixelFormat_SignedGrayscale16;
-      break;
-
-    case Orthanc::PixelFormat_RGB24:
-      format = OrthancPluginPixelFormat_RGB24;
-      break;
-
-    case Orthanc::PixelFormat_RGBA32:
-      format = OrthancPluginPixelFormat_RGBA32;
-      break;
-
-    default:
-      throw std::runtime_error("Unsupported pixel format");
-  }
-
-  OrthancPluginCompressAndAnswerPngImage(context_, output, format, accessor.GetWidth(),
-                                         accessor.GetHeight(), accessor.GetPitch(), accessor.GetConstBuffer());
-}
-
-
-
-void OrthancContext::Redirect(OrthancPluginRestOutput* output,
-                              const std::string& s)
-{
-  Check();
-  OrthancPluginRedirect(context_, output, s.c_str());
-}
--- a/Plugins/Samples/GdcmDecoding/OrthancContext.h	Wed Nov 18 10:16:21 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <orthanc/OrthancCPlugin.h>
-
-#include "../../../Core/Images/ImageBuffer.h"
-
-#include <map>
-#include <string>
-#include <boost/noncopyable.hpp>
-
-
-class OrthancContext : public boost::noncopyable
-{
-private:
-  OrthancPluginContext* context_;
-
-  OrthancContext() : context_(NULL)
-  {
-  }
-
-  void Check();
-
-public:
-  typedef std::map<std::string, std::string>  Arguments;
-
-  static OrthancContext& GetInstance();
-
-  void Initialize(OrthancPluginContext* context)
-  {
-    context_ = context;
-  }
-
-  void Finalize()
-  {
-    context_ = NULL;
-  }
-
-  void ExtractGetArguments(Arguments& arguments,
-                           const OrthancPluginHttpRequest& request);
-
-  void LogError(const std::string& s);
-
-  void LogWarning(const std::string& s);
-
-  void LogInfo(const std::string& s);
-  
-  void Register(const std::string& uri,
-                OrthancPluginRestCallback callback);
-
-  void GetDicomForInstance(std::string& result,
-                           const std::string& instanceId);
-
-  void CompressAndAnswerPngImage(OrthancPluginRestOutput* output,
-                                 const Orthanc::ImageAccessor& accessor);
-
-  void Redirect(OrthancPluginRestOutput* output,
-                const std::string& s);
-};
--- a/Plugins/Samples/GdcmDecoding/Plugin.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,264 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include <map>
-#include <string>
-#include <stdexcept>
-#include <sstream>
-#include <boost/lexical_cast.hpp>
-
-#include "OrthancContext.h"
-#include "../../../Core/Images/ImageProcessing.h"
-
-#include <gdcmReader.h>
-#include <gdcmImageReader.h>
-#include <gdcmImageChangePlanarConfiguration.h>
-
-
-static void AnswerUnsupportedImage(OrthancPluginRestOutput* output)
-{
-  OrthancContext::GetInstance().Redirect(output, "/app/images/unsupported.png");
-}
-
-
-static bool GetOrthancPixelFormat(Orthanc::PixelFormat& format,
-                                  const gdcm::Image& image)
-{
-  if (image.GetPlanarConfiguration() != 0 && 
-      image.GetPixelFormat().GetSamplesPerPixel() != 1)
-  {
-    OrthancContext::GetInstance().LogError("Planar configurations are not supported");
-    return false;
-  }
-
-  if (image.GetPixelFormat().GetSamplesPerPixel() == 1)
-  {
-    switch (image.GetPixelFormat().GetScalarType())
-    {
-      case gdcm::PixelFormat::UINT8:
-        format = Orthanc::PixelFormat_Grayscale8;
-        return true;
-
-      case gdcm::PixelFormat::UINT16:
-        format = Orthanc::PixelFormat_Grayscale16;
-        return true;
-
-      case gdcm::PixelFormat::INT16:
-        format = Orthanc::PixelFormat_SignedGrayscale16;
-        return true;
-
-      default:
-        return false;
-    }
-  }
-  else if (image.GetPixelFormat().GetSamplesPerPixel() == 3 &&
-           image.GetPixelFormat().GetScalarType() == gdcm::PixelFormat::UINT8)
-  {
-    format = Orthanc::PixelFormat_RGB24;
-    return true;
-  }
-  else if (image.GetPixelFormat().GetSamplesPerPixel() == 4 &&
-           image.GetPixelFormat().GetScalarType() == gdcm::PixelFormat::UINT8)
-  {
-    format = Orthanc::PixelFormat_RGBA32;
-    return true;
-  }
-  
-  return false;
-}
-
-
-ORTHANC_PLUGINS_API OrthancPluginErrorCode DecodeImage(OrthancPluginRestOutput* output,
-                                                       const char* url,
-                                                       const OrthancPluginHttpRequest* request)
-{
-  std::string instance(request->groups[0]);
-  std::string outputFormat(request->groups[1]);
-  OrthancContext::GetInstance().LogWarning("Using GDCM to decode instance " + instance);
-
-  // Download the request DICOM instance from Orthanc into a memory buffer
-  std::string dicom;
-  OrthancContext::GetInstance().GetDicomForInstance(dicom, instance);
-
-  // Prepare a memory stream over the DICOM instance
-  std::stringstream stream(dicom);
-
-  // Parse the DICOM instance using GDCM
-  gdcm::ImageReader imageReader;
-  imageReader.SetStream(stream);
-  if (!imageReader.Read())
-  {
-    OrthancContext::GetInstance().LogError("GDCM cannot extract an image from this DICOM instance");
-    AnswerUnsupportedImage(output);
-    return OrthancPluginErrorCode_Success;
-  }
-
-  gdcm::Image& image = imageReader.GetImage();
-
-
-  // Log information about the decoded image
-  char tmp[1024];
-  sprintf(tmp, "Image format: %dx%d %s with %d color channel(s)", image.GetRows(), image.GetColumns(), 
-          image.GetPixelFormat().GetScalarTypeAsString(), image.GetPixelFormat().GetSamplesPerPixel());
-  OrthancContext::GetInstance().LogWarning(tmp);
-
-
-  // Convert planar configuration
-  gdcm::ImageChangePlanarConfiguration planar;
-  if (image.GetPlanarConfiguration() != 0 && 
-      image.GetPixelFormat().GetSamplesPerPixel() != 1)
-  {
-    OrthancContext::GetInstance().LogWarning("Converting planar configuration to interleaved");
-    planar.SetInput(imageReader.GetImage());
-    planar.Change();
-    image = planar.GetOutput();
-  }
-
-
-  // Create a read-only accessor to the bitmap decoded by GDCM
-  Orthanc::PixelFormat format;
-  if (!GetOrthancPixelFormat(format, image))
-  {
-    OrthancContext::GetInstance().LogError("This sample plugin does not support this image format");
-    AnswerUnsupportedImage(output);
-    return OrthancPluginErrorCode_Success;
-  }
-
-  Orthanc::ImageAccessor decodedImage;
-  std::vector<char> decodedBuffer(image.GetBufferLength());
-
-  if (decodedBuffer.size())
-  {
-    image.GetBuffer(&decodedBuffer[0]);
-    unsigned int pitch = image.GetColumns() * ::Orthanc::GetBytesPerPixel(format);
-    decodedImage.AssignWritable(format, image.GetColumns(), image.GetRows(), pitch, &decodedBuffer[0]);
-  }
-  else
-  {
-    // Empty image
-    decodedImage.AssignWritable(format, 0, 0, 0, NULL);
-  }
-
-
-  // Convert the pixel format from GDCM to the format requested by the REST query
-  Orthanc::ImageBuffer converted;
-  converted.SetWidth(decodedImage.GetWidth());
-  converted.SetHeight(decodedImage.GetHeight());
-
-  if (outputFormat == "preview")
-  {
-    if (format == Orthanc::PixelFormat_RGB24 ||
-        format == Orthanc::PixelFormat_RGBA32)
-    {
-      // Do not rescale color image
-      converted.SetFormat(Orthanc::PixelFormat_RGB24);
-    }
-    else
-    {
-      converted.SetFormat(Orthanc::PixelFormat_Grayscale8);
-
-      // Rescale the image to the [0,255] range
-      int64_t a, b;
-      Orthanc::ImageProcessing::GetMinMaxValue(a, b, decodedImage);
-
-      float offset = -a;
-      float scaling = 255.0f / static_cast<float>(b - a);
-      Orthanc::ImageProcessing::ShiftScale(decodedImage, offset, scaling);
-    }
-  }
-  else
-  {
-    if (format == Orthanc::PixelFormat_RGB24 ||
-        format == Orthanc::PixelFormat_RGBA32)
-    {
-      // Do not convert color images to grayscale values (this is Orthanc convention)
-      AnswerUnsupportedImage(output);
-      return OrthancPluginErrorCode_Success;
-    }
-
-    if (outputFormat == "image-uint8")
-    {
-      converted.SetFormat(Orthanc::PixelFormat_Grayscale8);
-    }
-    else if (outputFormat == "image-uint16")
-    {
-      converted.SetFormat(Orthanc::PixelFormat_Grayscale16);
-    }
-    else if (outputFormat == "image-int16")
-    {
-      converted.SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
-    }
-    else
-    {
-      OrthancContext::GetInstance().LogError("Unknown output format: " + outputFormat);
-      AnswerUnsupportedImage(output);
-      return OrthancPluginErrorCode_Success;
-    }
-  }
-
-  Orthanc::ImageAccessor convertedAccessor(converted.GetAccessor());
-  Orthanc::ImageProcessing::Convert(convertedAccessor, decodedImage);
-
-  // Compress the converted image as a PNG file
-  OrthancContext::GetInstance().CompressAndAnswerPngImage(output, convertedAccessor);
-
-  return OrthancPluginErrorCode_Success;  // Success
-}
-
-
-extern "C"
-{
-  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
-  {
-    OrthancContext::GetInstance().Initialize(context);
-    OrthancContext::GetInstance().LogWarning("Initializing GDCM decoding");
-
-    // Check the version of the Orthanc core
-    if (OrthancPluginCheckVersion(context) == 0)
-    {
-      OrthancContext::GetInstance().LogError(
-        "Your version of Orthanc (" + std::string(context->orthancVersion) +
-        ") must be above " + boost::lexical_cast<std::string>(ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) +
-        "." + boost::lexical_cast<std::string>(ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) +
-        "." + boost::lexical_cast<std::string>(ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER) +
-        " to run this plugin");
-      return -1;
-    }
-
-    OrthancContext::GetInstance().Register("/instances/([^/]+)/(preview|image-uint8|image-uint16|image-int16)", DecodeImage);
-    return 0;
-  }
-
-  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
-  {
-    OrthancContext::GetInstance().LogWarning("Finalizing GDCM decoding");
-    OrthancContext::GetInstance().Finalize();
-  }
-
-  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
-  {
-    return "gdcm-decoding";
-  }
-
-  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
-  {
-    return "1.0";
-  }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/ModalityWorklists/CMakeLists.txt	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,36 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(ModalityWorklists)
+
+SET(MODALITY_WORKLISTS_VERSION "0.0" CACHE STRING "Version of the plugin")
+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")
+
+SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp")
+SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of boost")
+
+set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..)
+include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake)
+
+add_library(ModalityWorklists SHARED 
+  Plugin.cpp
+  ${JSONCPP_SOURCES}
+  ${BOOST_SOURCES}
+  )
+
+message("Setting the version of the plugin to ${MODALITY_WORKLISTS_VERSION}")
+add_definitions(
+  -DMODALITY_WORKLISTS_VERSION="${MODALITY_WORKLISTS_VERSION}"
+  )
+
+set_target_properties(ModalityWorklists PROPERTIES 
+  VERSION ${MODALITY_WORKLISTS_VERSION} 
+  SOVERSION ${MODALITY_WORKLISTS_VERSION})
+
+install(
+  TARGETS ModalityWorklists
+  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/Plugins/Samples/ModalityWorklists/Plugin.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,267 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include <orthanc/OrthancCPlugin.h>
+
+#include <boost/filesystem.hpp>
+#include <json/value.h>
+#include <json/reader.h>
+#include <string.h>
+#include <iostream>
+#include <algorithm>
+
+static OrthancPluginContext* context_ = NULL;
+static std::string folder_;
+
+
+static bool ReadFile(std::string& result,
+                     const std::string& path)
+{
+  OrthancPluginMemoryBuffer tmp;
+  if (OrthancPluginReadFile(context_, &tmp, path.c_str()))
+  {
+    return false;
+  }
+  else
+  {
+    result.assign(reinterpret_cast<const char*>(tmp.data), tmp.size);
+    OrthancPluginFreeMemoryBuffer(context_, &tmp);
+    return true;
+  }
+}
+
+
+/**
+ * This is the main function for matching a DICOM worklist against a query.
+ **/
+static OrthancPluginErrorCode  MatchWorklist(OrthancPluginWorklistAnswers*     answers,
+                                             const OrthancPluginWorklistQuery* query,
+                                             const std::string& path)
+{
+  std::string dicom;
+  if (!ReadFile(dicom, path))
+  {
+    // Cannot read this file, ignore this error
+    return OrthancPluginErrorCode_Success;
+  }
+
+  if (OrthancPluginWorklistIsMatch(context_, query, dicom.c_str(), dicom.size()))
+  {
+    // This DICOM file matches the worklist query, add it to the answers
+    return OrthancPluginWorklistAddAnswer
+      (context_, answers, query, dicom.c_str(), dicom.size());
+  }
+  else
+  {
+    // This DICOM file does not match
+    return OrthancPluginErrorCode_Success;
+  }
+}
+
+
+
+static bool ConvertToJson(Json::Value& result,
+                          char* content)
+{
+  if (content == NULL)
+  {
+    return false;
+  }
+  else
+  {
+    Json::Reader reader;
+    bool success = reader.parse(content, content + strlen(content), result);
+    OrthancPluginFreeString(context_, content);
+    return success;
+  }
+}
+
+
+static bool GetQueryDicom(Json::Value& value,
+                          const OrthancPluginWorklistQuery* query)
+{
+  OrthancPluginMemoryBuffer dicom;
+  if (OrthancPluginWorklistGetDicomQuery(context_, &dicom, query))
+  {
+    return false;
+  }
+
+  char* json = OrthancPluginDicomBufferToJson(context_, reinterpret_cast<const char*>(dicom.data),
+                                              dicom.size, 
+                                              OrthancPluginDicomToJsonFormat_Short, 
+                                              static_cast<OrthancPluginDicomToJsonFlags>(0), 0);
+  OrthancPluginFreeMemoryBuffer(context_, &dicom);
+
+  return ConvertToJson(value, json);
+}
+                          
+
+static void ToLowerCase(std::string& s)
+{
+  std::transform(s.begin(), s.end(), s.begin(), tolower);
+}
+
+
+OrthancPluginErrorCode Callback(OrthancPluginWorklistAnswers*     answers,
+                                const OrthancPluginWorklistQuery* query,
+                                const char*                       remoteAet,
+                                const char*                       calledAet)
+{
+  Json::Value json;
+
+  if (!GetQueryDicom(json, query))
+  {
+    return OrthancPluginErrorCode_InternalError;
+  }
+
+  std::cout << "Received worklist query from remote modality " << remoteAet 
+            << ":" << std::endl << json.toStyledString();
+
+  boost::filesystem::path source(folder_);
+  boost::filesystem::directory_iterator end;
+
+  try
+  {
+    for (boost::filesystem::directory_iterator it(source); it != end; ++it)
+    {
+      if (is_regular_file(it->status()))
+      {
+        std::string extension = boost::filesystem::extension(it->path());
+        ToLowerCase(extension);
+
+        if (extension == ".wl")
+        {
+          OrthancPluginErrorCode error = MatchWorklist(answers, query, it->path().string());
+          if (error)
+          {
+            OrthancPluginLogError(context_, "Error while adding an answer to a worklist request");
+            return error;
+          }
+        }
+      }
+    }
+  }
+  catch (boost::filesystem::filesystem_error&)
+  {
+    std::string description = std::string("Inexistent folder while scanning for worklists: ") + source.string();
+    OrthancPluginLogError(context_, description.c_str());
+    return OrthancPluginErrorCode_DirectoryExpected;
+  }
+
+  // Uncomment the following line if too many answers are to be returned
+  // OrthancPluginMarkWorklistAnswersIncomplete(context_, answers);
+
+  return OrthancPluginErrorCode_Success;
+}
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c)
+  {
+    context_ = c;
+    OrthancPluginLogWarning(context_, "Sample worklist plugin is initializing");
+    OrthancPluginSetDescription(context_, "Serve DICOM modality worklists from a folder with Orthanc.");
+
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(c) == 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;
+    }
+
+    Json::Value configuration;
+    if (!ConvertToJson(configuration, OrthancPluginGetConfiguration(context_)))
+    {
+      OrthancPluginLogError(context_, "Cannot access the configuration of the worklist server");
+      return -1;
+    }
+
+    bool enabled = false;
+
+    if (configuration.isMember("Worklists"))
+    {
+      const Json::Value& config = configuration["Worklists"];
+      if (!config.isMember("Enable") ||
+          config["Enable"].type() != Json::booleanValue)
+      {
+        OrthancPluginLogError(context_, "The configuration option \"Worklists.Enable\" must contain a Boolean");
+        return -1;
+      }
+      else
+      {
+        enabled = config["Enable"].asBool();
+        if (enabled)
+        {
+          if (!config.isMember("Database") ||
+              config["Database"].type() != Json::stringValue)
+          {
+            OrthancPluginLogError(context_, "The configuration option \"Worklists.Database\" must contain a path");
+            return -1;
+          }
+
+          folder_ = config["Database"].asString();
+        }
+        else
+        {
+          OrthancPluginLogWarning(context_, "Worklists server is disabled by the configuration file");
+        }
+      }
+    }
+    else
+    {
+      OrthancPluginLogWarning(context_, "Worklists server is disabled, no suitable configuration section was provided");
+    }
+
+    if (enabled)
+    {
+      std::string message = "The database of worklists will be read from folder: " + folder_;
+      OrthancPluginLogWarning(context_, message.c_str());
+
+      OrthancPluginRegisterWorklistCallback(context_, Callback);
+    }
+
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+    OrthancPluginLogWarning(context_, "Sample worklist plugin is finalizing");
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return "worklists";
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return MODALITY_WORKLISTS_VERSION;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/ModalityWorklists/README	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,60 @@
+Introduction
+============
+
+This sample plugin enables Orthanc to serve DICOM modality worklists
+that are read from some folder.
+
+Whenever a C-Find SCP request is issued to Orthanc, it will read the
+content of this folder to locate the worklists that match the request.
+An external application can dynamically modify the content of this
+folder while Orthanc is running to add/remove worklists.
+
+This sample mimics the behavior of the "wlmscpfs" tool from the
+DCMTK package:
+http://support.dcmtk.org/docs/wlmscpfs.html
+
+
+Compilation for Linux
+=====================
+
+# mkdir Build
+# cd Build
+# cmake ..
+# make
+
+
+Cross-compilation for Windows using MinGW
+=========================================
+
+# mkdir Build
+# cd Build
+# cmake .. -DCMAKE_TOOLCHAIN_FILE=../../../Resources/MinGWToolchain.cmake
+# make
+
+
+Configuration
+=============
+
+First, generate the default configuration of Orthanc:
+https://orthanc.chu.ulg.ac.be/book/users/configuration.html
+
+Then, modify the "Plugins" option to point to the folder containing
+the built plugins. Finally, create a section "ModalityWorklists" in
+the configuration file to configure the worklist server. If using the
+build commands above, a sample configuration would read as follows:
+
+{
+  [...]
+  "Plugins" : [ 
+    "."
+  ],
+  "Worklists" : {
+    "Enable": true,
+    "Database": "../WorklistsDatabase"
+  }
+}
+
+The folder "WorklistsDatabase" contains a database of sample
+worklists, that come from the DCMTK source distribution, as described
+in the FAQ entry #37 of the DCMTK project:
+http://forum.dcmtk.org/viewtopic.php?t=84
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/ModalityWorklists/WorklistsDatabase/Generate.py	Wed Dec 02 09:52:56 2015 +0100
@@ -0,0 +1,19 @@
+#!/usr/bin/python
+
+import os
+import subprocess
+
+SOURCE = '/home/jodogne/Downloads/dcmtk-3.6.0/dcmwlm/data/wlistdb/OFFIS/'
+TARGET = os.path.abspath(os.path.dirname(__file__))
+
+for f in os.listdir(SOURCE):
+    ext = os.path.splitext(f)
+
+    if ext[1].lower() == '.dump':
+        subprocess.check_call([
+            'dump2dcm',
+            '-g',
+            '-q',
+            os.path.join(SOURCE, f),
+            os.path.join(TARGET, ext[0].lower() + '.wl'),
+        ])
Binary file Plugins/Samples/ModalityWorklists/WorklistsDatabase/wklist1.wl has changed
Binary file Plugins/Samples/ModalityWorklists/WorklistsDatabase/wklist10.wl has changed
Binary file Plugins/Samples/ModalityWorklists/WorklistsDatabase/wklist2.wl has changed
Binary file Plugins/Samples/ModalityWorklists/WorklistsDatabase/wklist3.wl has changed
Binary file Plugins/Samples/ModalityWorklists/WorklistsDatabase/wklist4.wl has changed
Binary file Plugins/Samples/ModalityWorklists/WorklistsDatabase/wklist5.wl has changed
Binary file Plugins/Samples/ModalityWorklists/WorklistsDatabase/wklist6.wl has changed
Binary file Plugins/Samples/ModalityWorklists/WorklistsDatabase/wklist7.wl has changed
Binary file Plugins/Samples/ModalityWorklists/WorklistsDatabase/wklist8.wl has changed
Binary file Plugins/Samples/ModalityWorklists/WorklistsDatabase/wklist9.wl has changed
--- a/Plugins/Samples/ServeFolders/README	Wed Nov 18 10:16:21 2015 +0100
+++ b/Plugins/Samples/ServeFolders/README	Wed Dec 02 09:52:56 2015 +0100
@@ -27,7 +27,7 @@
 =============
 
 First, generate the default configuration of Orthanc:
-https://code.google.com/p/orthanc/wiki/OrthancConfiguration
+https://orthanc.chu.ulg.ac.be/book/users/configuration.html
 
 Then, modify the "Plugins" option to point to the folder containing
 the built plugins.
--- a/Resources/CMake/BoostConfiguration.cmake	Wed Nov 18 10:16:21 2015 +0100
+++ b/Resources/CMake/BoostConfiguration.cmake	Wed Dec 02 09:52:56 2015 +0100
@@ -39,10 +39,10 @@
 
 
 if (BOOST_STATIC)
-  # Parameters for Boost 1.58.0
-  set(BOOST_NAME boost_1_58_0)
-  set(BOOST_BCP_SUFFIX bcpdigest-0.9.2)
-  set(BOOST_MD5 "704b110917cbda903e07cb53934b47ac")
+  # 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")
   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/Resources/CMake/BoostConfiguration.sh	Wed Nov 18 10:16:21 2015 +0100
+++ b/Resources/CMake/BoostConfiguration.sh	Wed Dec 02 09:52:56 2015 +0100
@@ -13,22 +13,23 @@
 ## History:
 ##   - Orthanc between 0.6.2 and 0.7.3: Boost 1.54.0
 ##   - Orthanc between 0.7.4 and 0.9.1: Boost 1.55.0
-##   - Orthanc >= 0.9.2: Boost 1.58.0
+##   - Orthanc between 0.9.2 and 0.9.4: Boost 1.58.0
+##   - Orthanc >= 0.9.5: Boost 1.59.0
 
-rm -rf /tmp/boost_1_58_0
-rm -rf /tmp/bcp/boost_1_58_0
+rm -rf /tmp/boost_1_59_0
+rm -rf /tmp/bcp/boost_1_59_0
 
 cd /tmp
-echo "Uncompressing the sources of Boost 1.58.0..."
-tar xfz ./boost_1_58_0.tar.gz 
+echo "Uncompressing the sources of Boost 1.59.0..."
+tar xfz ./boost_1_59_0.tar.gz 
 
 echo "Generating the subset..."
-mkdir -p /tmp/bcp/boost_1_58_0
-bcp --boost=/tmp/boost_1_58_0 thread system locale date_time filesystem math/special_functions algorithm uuid atomic /tmp/bcp/boost_1_58_0
+mkdir -p /tmp/bcp/boost_1_59_0
+bcp --boost=/tmp/boost_1_59_0 thread system locale date_time filesystem math/special_functions algorithm uuid atomic iostreams /tmp/bcp/boost_1_59_0
 cd /tmp/bcp
 
 echo "Compressing the subset..."
-tar cfz boost_1_58_0_bcpdigest-0.9.2.tar.gz boost_1_58_0
-ls -l boost_1_58_0_bcpdigest-0.9.2.tar.gz
-md5sum boost_1_58_0_bcpdigest-0.9.2.tar.gz
-readlink -f boost_1_58_0_bcpdigest-0.9.2.tar.gz
+tar cfz boost_1_59_0_bcpdigest-0.9.5.tar.gz boost_1_59_0
+ls -l boost_1_59_0_bcpdigest-0.9.5.tar.gz
+md5sum boost_1_59_0_bcpdigest-0.9.5.tar.gz
+readlink -f boost_1_59_0_bcpdigest-0.9.5.tar.gz
--- a/Resources/CMake/DcmtkConfiguration.cmake	Wed Nov 18 10:16:21 2015 +0100
+++ b/Resources/CMake/DcmtkConfiguration.cmake	Wed Dec 02 09:52:56 2015 +0100
@@ -1,6 +1,6 @@
 if (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK)
   SET(DCMTK_VERSION_NUMBER 361)
-  set(DCMTK_PACKAGE_VERSION "3.6.1")
+  SET(DCMTK_PACKAGE_VERSION "3.6.1")
   SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.1_20150629)
   SET(DCMTK_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/dcmtk-3.6.1_20150629.tar.gz")
   SET(DCMTK_MD5 "2faf73786fc638ae05fef0103cce0eea")
--- a/Resources/Configuration.json	Wed Nov 18 10:16:21 2015 +0100
+++ b/Resources/Configuration.json	Wed Dec 02 09:52:56 2015 +0100
@@ -102,6 +102,10 @@
   "Mpeg2TransferSyntaxAccepted"        : true,
   "RleTransferSyntaxAccepted"          : true,
 
+  // Whether Orthanc accepts to act as C-Store SCP for unknown storage
+  // SOP classes (aka. "promiscuous mode")
+  "UnknownSopClassAccepted"            : true,
+
 
 
   /**
@@ -146,8 +150,9 @@
      * A fourth parameter is available to enable patches for a
      * specific PACS manufacturer. The allowed values are currently
      * "Generic" (default value), "StoreScp" (storescp tool from
-     * DCMTK), "ClearCanvas", "MedInria", "Dcm4Chee" and
-     * "SyngoVia". This parameter is case-sensitive.
+     * DCMTK), "ClearCanvas", "MedInria", "Dcm4Chee", "SyngoVia",
+     * "AgfaImpax" (Agfa IMPAX), "EFilm2" (eFilm version 2), and
+     * "Vitrea". This parameter is case-sensitive.
      **/
     // "clearcanvas" : [ "CLEARCANVAS", "192.168.1.1", 104, "ClearCanvas" ]
   },
--- a/Resources/DicomConformanceStatement.txt	Wed Nov 18 10:16:21 2015 +0100
+++ b/Resources/DicomConformanceStatement.txt	Wed Dec 02 09:52:56 2015 +0100
@@ -149,6 +149,7 @@
 
   FINDPatientRootQueryRetrieveInformationModel   | 1.2.840.10008.5.1.4.1.2.1.1
   FINDStudyRootQueryRetrieveInformationModel     | 1.2.840.10008.5.1.4.1.2.2.1
+  FINDModalityWorklistInformationModel           | 1.2.840.10008.5.1.4.31
 
 
 --------------------
--- a/Resources/ErrorCodes.json	Wed Nov 18 10:16:21 2015 +0100
+++ b/Resources/ErrorCodes.json	Wed Dec 02 09:52:56 2015 +0100
@@ -513,5 +513,10 @@
     "Code": 2040,
     "Name": "CannotOrderSlices",
     "Description": "Unable to order the slices of the series"
+  },
+  {
+    "Code": 2041, 
+    "Name": "NoWorklistHandler", 
+    "Description": "No request handler factory for DICOM C-Find Modality SCP"
   }
 ]
--- a/Resources/Samples/Lua/TransferSyntaxDisable.lua	Wed Nov 18 10:16:21 2015 +0100
+++ b/Resources/Samples/Lua/TransferSyntaxDisable.lua	Wed Dec 02 09:52:56 2015 +0100
@@ -26,4 +26,8 @@
    return false
 end
 
+function IsUnknownSopClassAccepted(aet, ip)
+   return false
+end
+
 print('All special transfer syntaxes are now disallowed')
--- a/Resources/Samples/Lua/TransferSyntaxEnable.lua	Wed Nov 18 10:16:21 2015 +0100
+++ b/Resources/Samples/Lua/TransferSyntaxEnable.lua	Wed Dec 02 09:52:56 2015 +0100
@@ -26,4 +26,8 @@
    return true
 end
 
+function IsUnknownSopClassAccepted(aet, ip)
+   return true
+end
+
 print('All special transfer syntaxes are now accepted')
--- a/UnitTestsSources/DicomMapTests.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/UnitTestsSources/DicomMapTests.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -151,6 +151,9 @@
 static void TestModule(ResourceType level,
                        DicomModule module)
 {
+  // REFERENCE: DICOM PS3.3 2015c - Information Object Definitions
+  // http://dicom.nema.org/medical/dicom/current/output/html/part03.html
+
   std::set<DicomTag> moduleTags, main;
   DicomTag::AddTagsForModule(moduleTags, module);
   DicomMap::GetMainDicomTags(main, level);
@@ -160,21 +163,41 @@
   {
     bool ok = moduleTags.find(*it) != moduleTags.end();
 
-    // Exceptions for the Series level
-    /*if ((//
-          *it == DicomTag(0x, 0x) && 
-          level == ResourceType_Series))
+    // Exceptions for the Study level
+    if (level == ResourceType_Study &&
+        (*it == DicomTag(0x0008, 0x0080) ||  /* InstitutionName, from Visit identification module, related to Visit */
+         *it == DicomTag(0x0032, 0x1032) ||  /* RequestingPhysician, from Imaging Service Request module, related to Study */
+         *it == DicomTag(0x0032, 0x1060)))   /* RequestedProcedureDescription, from Requested Procedure module, related to Study */
     {
       ok = true;
-      }*/
+    }
+
+    // Exceptions for the Series level
+    if (level == ResourceType_Series &&
+        (*it == DicomTag(0x0008, 0x0070) ||  /* Manufacturer, from General Equipment Module */
+         *it == DicomTag(0x0008, 0x1010) ||  /* StationName, from General Equipment Module */
+         *it == DicomTag(0x0018, 0x0024) ||  /* SequenceName, from MR Image Module (SIMPLIFICATION => Series) */
+         *it == DicomTag(0x0018, 0x1090) ||  /* CardiacNumberOfImages, from MR Image Module (SIMPLIFICATION => Series) */
+         *it == DicomTag(0x0020, 0x0037) ||  /* ImageOrientationPatient, from Image Plane Module (SIMPLIFICATION => Series) */
+         *it == DicomTag(0x0020, 0x0105) ||  /* NumberOfTemporalPositions, from MR Image Module (SIMPLIFICATION => Series) */
+         *it == DicomTag(0x0020, 0x1002) ||  /* ImagesInAcquisition, from General Image Module (SIMPLIFICATION => Series) */
+         *it == DicomTag(0x0054, 0x0081) ||  /* NumberOfSlices, from PET Series module */
+         *it == DicomTag(0x0054, 0x0101) ||  /* NumberOfTimeSlices, from PET Series module */
+         *it == DicomTag(0x0054, 0x1000) ||  /* SeriesType, from PET Series module */
+         *it == DicomTag(0x0018, 0x1400) ||  /* AcquisitionDeviceProcessingDescription, from CR/X-Ray/DX/WholeSlideMicro Image (SIMPLIFICATION => Series) */
+         *it == DicomTag(0x0018, 0x0010)))   /* ContrastBolusAgent, from Contrast/Bolus module (SIMPLIFICATION => Series) */
+    {
+      ok = true;
+    }
 
     // Exceptions for the Instance level
     if (level == ResourceType_Instance &&
-        (*it == DicomTag(0x0020, 0x0012) ||  /* Accession number, from Image module */
-         *it == DicomTag(0x0054, 0x1330) ||  /* Image Index, from PET Image module */
-         *it == DicomTag(0x0020, 0x0100) ||  /* Temporal Position Identifier, from MR Image module */
-         *it == DicomTag(0x0028, 0x0008) ||  /* Number of Frames, from Multi-frame module attributes, related to Image IOD */
-         *it == DICOM_TAG_IMAGE_POSITION_PATIENT))
+        (*it == DicomTag(0x0020, 0x0012) ||  /* AccessionNumber, from General Image module */
+         *it == DicomTag(0x0054, 0x1330) ||  /* ImageIndex, from PET Image module */
+         *it == DicomTag(0x0020, 0x0100) ||  /* TemporalPositionIdentifier, from MR Image module */
+         *it == DicomTag(0x0028, 0x0008) ||  /* NumberOfFrames, from Multi-frame module attributes, related to Image */
+         *it == DicomTag(0x0020, 0x0032) ||  /* ImagePositionPatient, from Image Plan module, related to Image */
+         *it == DicomTag(0x0020, 0x4000)))   /* ImageComments, from General Image module */
     {
       ok = true;
     }
@@ -194,6 +217,6 @@
 {
   TestModule(ResourceType_Patient, DicomModule_Patient);
   TestModule(ResourceType_Study, DicomModule_Study);
-  //TestModule(ResourceType_Series, DicomModule_Series);   // TODO
+  TestModule(ResourceType_Series, DicomModule_Series);   // TODO
   TestModule(ResourceType_Instance, DicomModule_Instance);
 }
--- a/UnitTestsSources/FromDcmtkTests.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/UnitTestsSources/FromDcmtkTests.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -43,6 +43,7 @@
 #include "../Core/Images/PngWriter.h"
 #include "../Core/Uuid.h"
 #include "../Resources/EncodingTests.h"
+#include "../OrthancServer/DicomProtocol/DicomFindAnswers.h"
 
 #include <dcmtk/dcmdata/dcelem.h>
 
@@ -74,7 +75,7 @@
   //m.Replace(DICOM_TAG_PATIENT_ID, "coucou");
   //m.Replace(DICOM_TAG_PATIENT_NAME, "coucou");
 
-  ParsedDicomFile o;
+  ParsedDicomFile o(true);
   o.SaveToFile("UnitTestsResults/anon.dcm");
 
   for (int i = 0; i < 10; i++)
@@ -96,13 +97,13 @@
 
   const DicomTag privateTag(0x0045, 0x0010);
   const DicomTag privateTag2(FromDcmtkBridge::ParseTag("0031-1020"));
-  ASSERT_TRUE(FromDcmtkBridge::IsPrivateTag(privateTag));
-  ASSERT_TRUE(FromDcmtkBridge::IsPrivateTag(privateTag2));
+  ASSERT_TRUE(privateTag.IsPrivate());
+  ASSERT_TRUE(privateTag2.IsPrivate());
   ASSERT_EQ(0x0031, privateTag2.GetGroup());
   ASSERT_EQ(0x1020, privateTag2.GetElement());
 
   std::string s;
-  ParsedDicomFile o;
+  ParsedDicomFile o(true);
   o.Replace(DICOM_TAG_PATIENT_NAME, "coucou");
   ASSERT_FALSE(o.GetTagValue(s, privateTag));
   o.Insert(privateTag, "private tag", false);
@@ -160,7 +161,7 @@
   ASSERT_EQ(5u, reader.GetWidth());
   ASSERT_EQ(PixelFormat_RGBA32, reader.GetFormat());
 
-  ParsedDicomFile o;
+  ParsedDicomFile o(true);
   o.EmbedContent(s);
   o.SaveToFile("UnitTestsResults/png1.dcm");
 
@@ -266,7 +267,7 @@
     std::string dicom;
 
     {
-      ParsedDicomFile f;
+      ParsedDicomFile f(true);
       f.SetEncoding(testEncodings[i]);
 
       std::string s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i]);
@@ -406,7 +407,7 @@
 
 TEST(ParsedDicomFile, InsertReplaceStrings)
 {
-  ParsedDicomFile f;
+  ParsedDicomFile f(true);
 
   f.Insert(DICOM_TAG_PATIENT_NAME, "World", false);
   ASSERT_THROW(f.Insert(DICOM_TAG_PATIENT_ID, "Hello", false), OrthancException);  // Already existing tag
@@ -445,7 +446,7 @@
 
 TEST(ParsedDicomFile, InsertReplaceJson)
 {
-  ParsedDicomFile f;
+  ParsedDicomFile f(true);
 
   Json::Value a;
   CreateSampleJson(a);
@@ -497,7 +498,7 @@
 
 TEST(ParsedDicomFile, JsonEncoding)
 {
-  ParsedDicomFile f;
+  ParsedDicomFile f(true);
 
   for (unsigned int i = 0; i < testEncodingsCount; i++)
   {
@@ -527,7 +528,7 @@
   FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7053, 0x1000), EVR_PN, "MyPrivateTag", 1, 1);
   FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1000), EVR_PN, "Declared public tag", 1, 1);
 
-  ParsedDicomFile f;
+  ParsedDicomFile f(true);
   f.Insert(DicomTag(0x7050, 0x1000), "Some public tag", false);  // Even group => public tag
   f.Insert(DicomTag(0x7052, 0x1000), "Some unknown tag", false);  // Even group => public, unknown tag
   f.Insert(DicomTag(0x7053, 0x1000), "Some private tag", false);  // Odd group => private tag
@@ -542,6 +543,15 @@
   ASSERT_EQ(Json::stringValue, v["7050,1000"].type());
   ASSERT_EQ("Some public tag", v["7050,1000"].asString());
 
+  f.ToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_ConvertBinaryToNull), 0);
+  ASSERT_EQ(Json::objectValue, v.type());
+  ASSERT_EQ(7, v.getMemberNames().size());
+  ASSERT_FALSE(v.isMember("7052,1000"));
+  ASSERT_TRUE(v.isMember("7050,1000"));
+  ASSERT_TRUE(v.isMember("7053,1000"));
+  ASSERT_EQ("Some public tag", v["7050,1000"].asString());
+  ASSERT_EQ(Json::nullValue, v["7053,1000"].type());
+
   f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludePrivateTags, 0);
   ASSERT_EQ(Json::objectValue, v.type());
   ASSERT_EQ(7, v.getMemberNames().size());
@@ -549,32 +559,48 @@
   ASSERT_TRUE(v.isMember("7050,1000"));
   ASSERT_TRUE(v.isMember("7053,1000"));
   ASSERT_EQ("Some public tag", v["7050,1000"].asString());
-  ASSERT_EQ(Json::nullValue, v["7053,1000"].type());  // TODO SHOULD BE STRING
+  std::string mime, content;
+  ASSERT_EQ(Json::stringValue, v["7053,1000"].type());
+  Toolbox::DecodeDataUriScheme(mime, content, v["7053,1000"].asString());
+  ASSERT_EQ("application/octet-stream", mime);
+  ASSERT_EQ("Some private tag", content);
 
-  f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludeUnknownTags, 0);
+  f.ToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_ConvertBinaryToNull), 0);
   ASSERT_EQ(Json::objectValue, v.type());
   ASSERT_EQ(7, v.getMemberNames().size());
   ASSERT_TRUE(v.isMember("7050,1000"));
   ASSERT_TRUE(v.isMember("7052,1000"));
   ASSERT_FALSE(v.isMember("7053,1000"));
   ASSERT_EQ("Some public tag", v["7050,1000"].asString());
-  ASSERT_EQ(Json::nullValue, v["7052,1000"].type());  // TODO SHOULD BE STRING
+  ASSERT_EQ(Json::nullValue, v["7052,1000"].type());
 
-  f.ToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludePrivateTags), 0);
+  f.ToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags), 0);
+  ASSERT_EQ(Json::objectValue, v.type());
+  ASSERT_EQ(7, v.getMemberNames().size());
+  ASSERT_TRUE(v.isMember("7050,1000"));
+  ASSERT_TRUE(v.isMember("7052,1000"));
+  ASSERT_FALSE(v.isMember("7053,1000"));
+  ASSERT_EQ("Some public tag", v["7050,1000"].asString());
+  ASSERT_EQ(Json::stringValue, v["7052,1000"].type());
+  Toolbox::DecodeDataUriScheme(mime, content, v["7052,1000"].asString());
+  ASSERT_EQ("application/octet-stream", mime);
+  ASSERT_EQ("Some unknown tag", content);
+
+  f.ToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_ConvertBinaryToNull), 0);
   ASSERT_EQ(Json::objectValue, v.type());
   ASSERT_EQ(8, v.getMemberNames().size());
   ASSERT_TRUE(v.isMember("7050,1000"));
   ASSERT_TRUE(v.isMember("7052,1000"));
   ASSERT_TRUE(v.isMember("7053,1000"));
   ASSERT_EQ("Some public tag", v["7050,1000"].asString());
-  ASSERT_EQ(Json::nullValue, v["7052,1000"].type());  // TODO SHOULD BE STRING
-  ASSERT_EQ(Json::nullValue, v["7053,1000"].type());  // TODO SHOULD BE STRING
+  ASSERT_EQ(Json::nullValue, v["7052,1000"].type());
+  ASSERT_EQ(Json::nullValue, v["7053,1000"].type());
 }
 
 
 TEST(ParsedDicomFile, ToJsonFlags2)
 {
-  ParsedDicomFile f;
+  ParsedDicomFile f(true);
   f.Insert(DICOM_TAG_PIXEL_DATA, "Pixels", false);
 
   Json::Value v;
@@ -606,3 +632,131 @@
   ASSERT_EQ("application/octet-stream", mime);
   ASSERT_EQ("Pixels", content);
 }
+
+
+TEST(DicomFindAnswers, Basic)
+{
+  DicomFindAnswers a;
+
+  {
+    DicomMap m;
+    m.SetValue(DICOM_TAG_PATIENT_ID, "hello");
+    a.Add(m);
+  }
+
+  {
+    ParsedDicomFile d(true);
+    d.Replace(DICOM_TAG_PATIENT_ID, "my");
+    a.Add(d);
+  }
+
+  {
+    DicomMap m;
+    m.SetValue(DICOM_TAG_PATIENT_ID, "world");
+    a.Add(m);
+  }
+
+  Json::Value j;
+  a.ToJson(j, true);
+  ASSERT_EQ(3u, j.size());
+
+  //std::cout << j;
+}
+
+
+TEST(ParsedDicomFile, FromJson)
+{
+  FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7057, 0x1000), EVR_OB, "MyPrivateTag", 1, 1);
+  FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7059, 0x1000), EVR_OB, "MyPrivateTag", 1, 1);
+  FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1000), EVR_PN, "Declared public tag", 1, 1);
+
+  Json::Value v;
+  const std::string sopClassUid = "1.2.840.10008.5.1.4.1.1.1";  // CR Image Storage:
+
+  {
+    v["SOPClassUID"] = sopClassUid;
+    v["SpecificCharacterSet"] = "ISO_IR 148";    // This is latin-5
+    v["PatientName"] = "Sébastien";
+    v["7050-1000"] = "Some public tag";  // Even group => public tag
+    v["7052-1000"] = "Some unknown tag";  // Even group => public, unknown tag
+    v["7057-1000"] = "Some private tag";  // Odd group => private tag
+    v["7059-1000"] = "Some private tag2";  // Odd group => private tag, with an odd length to test padding
+  
+    std::string s;
+    Toolbox::EncodeDataUriScheme(s, "application/octet-stream", "Sebastien");
+    v["StudyDescription"] = s;
+
+    v["PixelData"] = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";  // A red dot of 5x5 pixels
+    v["0040,0100"] = Json::arrayValue;  // ScheduledProcedureStepSequence
+
+    Json::Value vv;
+    vv["Modality"] = "MR";
+    v["0040,0100"].append(vv);
+
+    vv["Modality"] = "CT";
+    v["0040,0100"].append(vv);
+  }
+
+  const DicomToJsonFlags toJsonFlags = static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeBinary |
+                                                                     DicomToJsonFlags_IncludePixelData | 
+                                                                     DicomToJsonFlags_IncludePrivateTags | 
+                                                                     DicomToJsonFlags_IncludeUnknownTags | 
+                                                                     DicomToJsonFlags_ConvertBinaryToAscii);
+
+
+  {
+    std::auto_ptr<ParsedDicomFile> dicom
+      (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_GenerateIdentifiers)));
+
+    Json::Value vv;
+    dicom->ToJson(vv, DicomToJsonFormat_Simple, toJsonFlags, 0);
+
+    ASSERT_EQ(vv["SOPClassUID"].asString(), sopClassUid);
+    ASSERT_EQ(vv["MediaStorageSOPClassUID"].asString(), sopClassUid);
+    ASSERT_TRUE(vv.isMember("SOPInstanceUID"));
+    ASSERT_TRUE(vv.isMember("SeriesInstanceUID"));
+    ASSERT_TRUE(vv.isMember("StudyInstanceUID"));
+    ASSERT_TRUE(vv.isMember("PatientID"));
+  }
+
+
+  {
+    std::auto_ptr<ParsedDicomFile> dicom
+      (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_GenerateIdentifiers)));
+
+    Json::Value vv;
+    dicom->ToJson(vv, DicomToJsonFormat_Simple, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePixelData), 0);
+
+    std::string mime, content;
+    Toolbox::DecodeDataUriScheme(mime, content, vv["PixelData"].asString());
+    ASSERT_EQ("application/octet-stream", mime);
+    ASSERT_EQ(5u * 5u * 3u /* the red dot is 5x5 pixels in RGB24 */ + 1 /* for padding */, content.size());
+  }
+
+
+  {
+    std::auto_ptr<ParsedDicomFile> dicom
+      (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_DecodeDataUriScheme)));
+
+    Json::Value vv;
+    dicom->ToJson(vv, DicomToJsonFormat_Short, toJsonFlags, 0);
+
+    ASSERT_FALSE(vv.isMember("SOPInstanceUID"));
+    ASSERT_FALSE(vv.isMember("SeriesInstanceUID"));
+    ASSERT_FALSE(vv.isMember("StudyInstanceUID"));
+    ASSERT_FALSE(vv.isMember("PatientID"));
+    ASSERT_EQ(2u, vv["0040,0100"].size());
+    ASSERT_EQ("MR", vv["0040,0100"][0]["0008,0060"].asString());
+    ASSERT_EQ("CT", vv["0040,0100"][1]["0008,0060"].asString());
+    ASSERT_EQ("Some public tag", vv["7050,1000"].asString());
+    ASSERT_EQ("Some unknown tag", vv["7052,1000"].asString());
+    ASSERT_EQ("Some private tag", vv["7057,1000"].asString());
+    ASSERT_EQ("Some private tag2", vv["7059,1000"].asString());
+    ASSERT_EQ("Sébastien", vv["0010,0010"].asString());
+    ASSERT_EQ("Sebastien", vv["0008,1030"].asString());
+    ASSERT_EQ("ISO_IR 148", vv["0008,0005"].asString());
+    ASSERT_EQ("5", vv[DICOM_TAG_ROWS.Format()].asString());
+    ASSERT_EQ("5", vv[DICOM_TAG_COLUMNS.Format()].asString());
+    ASSERT_TRUE(vv[DICOM_TAG_PIXEL_DATA.Format()].asString().empty());
+  }
+}
--- a/UnitTestsSources/ServerIndexTests.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/UnitTestsSources/ServerIndexTests.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -796,9 +796,10 @@
     instance.SetValue(DICOM_TAG_SOP_INSTANCE_UID, "instance-" + id);
 
     std::map<MetadataType, std::string> instanceMetadata;
-    ServerIndex::MetadataMap metadata;
-    ASSERT_EQ(StoreStatus_Success, index.Store(instanceMetadata, instance, attachments, "", metadata));
-    ASSERT_EQ(2u, instanceMetadata.size());
+    DicomInstanceToStore toStore;
+    toStore.SetSummary(instance);
+    ASSERT_EQ(StoreStatus_Success, index.Store(instanceMetadata, toStore, attachments));
+    ASSERT_EQ(3u, instanceMetadata.size());
     ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_RemoteAet) != instanceMetadata.end());
     ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_ReceptionDate) != instanceMetadata.end());
 
--- a/UnitTestsSources/UnitTestsMain.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/UnitTestsSources/UnitTestsMain.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -529,6 +529,9 @@
   ASSERT_STREQ("MedInria", EnumerationToString(StringToModalityManufacturer("MedInria")));
   ASSERT_STREQ("Dcm4Chee", EnumerationToString(StringToModalityManufacturer("Dcm4Chee")));
   ASSERT_STREQ("SyngoVia", EnumerationToString(StringToModalityManufacturer("SyngoVia")));
+  ASSERT_STREQ("AgfaImpax", EnumerationToString(StringToModalityManufacturer("AgfaImpax")));
+  ASSERT_STREQ("EFilm2", EnumerationToString(StringToModalityManufacturer("EFilm2")));
+  ASSERT_STREQ("Vitrea", EnumerationToString(StringToModalityManufacturer("Vitrea")));
 }
 
 
--- a/UnitTestsSources/VersionsTests.cpp	Wed Nov 18 10:16:21 2015 +0100
+++ b/UnitTestsSources/VersionsTests.cpp	Wed Dec 02 09:52:56 2015 +0100
@@ -102,7 +102,7 @@
 
 TEST(Versions, BoostStatic)
 {
-  ASSERT_STREQ("1_58", BOOST_LIB_VERSION);
+  ASSERT_STREQ("1_59", BOOST_LIB_VERSION);
 }
 
 TEST(Versions, CurlStatic)