changeset 1999:364cc624eb65

New URI "/modalities/.../move" to issue C-Move SCU requests
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 26 May 2016 14:24:56 +0200
parents 9b61701c35f2
children 39329372b667
files NEWS OrthancServer/main.cpp Plugins/Engine/OrthancPlugins.cpp Plugins/Engine/OrthancPlugins.h Plugins/Include/orthanc/OrthancCPlugin.h
diffstat 5 files changed, 383 insertions(+), 38 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Wed May 25 15:16:17 2016 +0200
+++ b/NEWS	Thu May 26 14:24:56 2016 +0200
@@ -2,6 +2,13 @@
 ===============================
 
 
+REST API
+--------
+
+* New URI: "/instances/.../frames/.../raw" to access the raw frames (bypass image decoding)
+* New URI "/modalities/.../move" to issue C-Move SCU requests
+* "MoveOriginatorID" can be specified for "/modalities/.../store"
+
 Dicom protocol
 --------------
 
@@ -9,20 +16,12 @@
   0008-0061, 0008-0062, 0020-1200, 0020-1202, 0020-1204, 0020-1206, 0020-1208, 0020-1209
 * Support of Move Originator Message ID (0000,1031) in C-Store responses driven by C-Move
 
-Image decoding
---------------
-
-* New URI: "/instances/.../frames/.../raw" to access the raw frames (bypass image decoding)
-* Huge speedup if decoding the family of JPEG transfer syntaxes
-* Refactoring leading to speedups with custom image decoders (including Web viewer plugin)
-* Support decoding of RLE Lossless transfer syntax
-* Support of signed 16bpp images in ParsedDicomFile
-
 Plugins
 -------
 
 * New callback to filter incoming HTTP requests: OrthancPluginRegisterIncomingHttpRequestFilter()
-* New callback to handle non-worklists C-Find requests: OrthancPluginRegisterCFindCallback()
+* New callback to handle non-worklists C-Find requests: OrthancPluginRegisterFindCallback()
+* New callback to handle C-Move requests: OrthancPluginRegisterMoveCallback()
 * New function: "OrthancPluginHttpClient()" to do HTTP requests with full control
 * New function: "OrthancPluginGenerateUuid()" to generate a UUID
 
@@ -31,11 +30,13 @@
 
 * Access to the HTTP headers in the "IncomingHttpRequestFilter()" callback
 
-REST API
---------
+Image decoding
+--------------
 
-* New URI "/modalities/.../move" to issue C-Move SCU requests
-* "MoveOriginatorID" can be specified for "/modalities/.../store"
+* Huge speedup if decoding the family of JPEG transfer syntaxes
+* Refactoring leading to speedups with custom image decoders (including Web viewer plugin)
+* Support decoding of RLE Lossless transfer syntax
+* Support of signed 16bpp images in ParsedDicomFile
 
 Maintenance
 -----------
--- a/OrthancServer/main.cpp	Wed May 25 15:16:17 2016 +0200
+++ b/OrthancServer/main.cpp	Thu May 26 14:24:56 2016 +0200
@@ -767,6 +767,11 @@
     {
       dicomServer.SetFindRequestHandlerFactory(*plugins);
     }
+
+    if (plugins->HasMoveHandler())
+    {
+      dicomServer.SetMoveRequestHandlerFactory(*plugins);
+    }
   }
 #endif
 
--- a/Plugins/Engine/OrthancPlugins.cpp	Wed May 25 15:16:17 2016 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Thu May 26 14:24:56 2016 +0200
@@ -316,12 +316,14 @@
     OrthancPluginFindCallback  findCallback_;
     OrthancPluginWorklistCallback  worklistCallback_;
     OrthancPluginDecodeImageCallback  decodeImageCallback_;
+    _OrthancPluginMoveCallback moveCallbacks_;
     IncomingHttpRequestFilters  incomingHttpRequestFilters_;
     std::auto_ptr<StorageAreaFactory>  storageArea_;
     boost::recursive_mutex restCallbackMutex_;
     boost::recursive_mutex storedCallbackMutex_;
     boost::recursive_mutex changeCallbackMutex_;
     boost::mutex findCallbackMutex_;
+    boost::mutex moveCallbackMutex_;
     boost::mutex worklistCallbackMutex_;
     boost::mutex decodeImageCallbackMutex_;
     boost::recursive_mutex invokeServiceMutex_;
@@ -339,6 +341,7 @@
       argc_(1),
       argv_(NULL)
     {
+      memset(&moveCallbacks_, 0, sizeof(moveCallbacks_));
     }
   };
 
@@ -540,6 +543,155 @@
   
 
 
+  class OrthancPlugins::MoveHandler : public IMoveRequestHandler
+  {
+  private:
+    class Driver : public IMoveRequestIterator
+    {
+    private:
+      void*                   driver_;
+      unsigned int            count_;
+      unsigned int            pos_;
+      OrthancPluginApplyMove  apply_;
+      OrthancPluginFreeMove   free_;
+
+    public:
+      Driver(void* driver,
+             unsigned int count,
+             OrthancPluginApplyMove apply,
+             OrthancPluginFreeMove free) :
+        driver_(driver),
+        count_(count),
+        pos_(0),
+        apply_(apply),
+        free_(free)
+      {
+        if (driver_ == NULL)
+        {
+          throw OrthancException(ErrorCode_Plugin);
+        }
+      }
+
+      virtual ~Driver()
+      {
+        if (driver_ != NULL)
+        {
+          free_(driver_);
+          driver_ = NULL;
+        }
+      }
+
+      virtual unsigned int GetSubOperationCount() const
+      {
+        return count_;
+      }
+
+      virtual Status DoNext()
+      {
+        if (pos_ >= count_)
+        {
+          throw OrthancException(ErrorCode_BadSequenceOfCalls);
+        }
+        else
+        {
+          OrthancPluginErrorCode error = apply_(driver_);
+          if (error != OrthancPluginErrorCode_Success)
+          {
+            LOG(ERROR) << "Error while doing C-Move from plugin: " << EnumerationToString(static_cast<ErrorCode>(error));
+            return Status_Failure;
+          }
+          else
+          {
+            pos_++;
+            return Status_Success;
+          }
+        }
+      }
+    };
+
+
+    _OrthancPluginMoveCallback  params_;
+
+
+    static std::string ReadTag(const DicomMap& input,
+                               const DicomTag& tag)
+    {
+      const DicomValue* value = input.TestAndGetValue(tag);
+      if (value != NULL &&
+          !value->IsBinary() &&
+          !value->IsNull())
+      {
+        return value->GetContent();
+      }
+      else
+      {
+        return std::string();
+      }
+    }
+                        
+
+
+  public:
+    MoveHandler(OrthancPlugins& that)
+    {
+      boost::mutex::scoped_lock lock(that.pimpl_->moveCallbackMutex_);
+      params_ = that.pimpl_->moveCallbacks_;
+      
+      if (params_.callback == NULL ||
+          params_.getMoveSize == NULL ||
+          params_.applyMove == NULL ||
+          params_.freeMove == NULL)
+      {
+        throw OrthancException(ErrorCode_Plugin);
+      }
+    }
+
+    virtual IMoveRequestIterator* Handle(const std::string& targetAet,
+                                         const DicomMap& input,
+                                         const std::string& remoteIp,
+                                         const std::string& remoteAet,
+                                         const std::string& calledAet,
+                                         uint16_t messageId)
+    {
+      std::string levelString = ReadTag(input, DICOM_TAG_QUERY_RETRIEVE_LEVEL);
+      std::string patientId = ReadTag(input, DICOM_TAG_PATIENT_ID);
+      std::string accessionNumber = ReadTag(input, DICOM_TAG_ACCESSION_NUMBER);
+      std::string studyInstanceUid = ReadTag(input, DICOM_TAG_STUDY_INSTANCE_UID);
+      std::string seriesInstanceUid = ReadTag(input, DICOM_TAG_SERIES_INSTANCE_UID);
+      std::string sopInstanceUid = ReadTag(input, DICOM_TAG_SOP_INSTANCE_UID);
+
+      OrthancPluginResourceType level = OrthancPluginResourceType_None;
+
+      if (!levelString.empty())
+      {
+        level = Plugins::Convert(StringToResourceType(levelString.c_str()));
+      }
+
+      void* driver = params_.callback(level,
+                                      patientId.empty() ? NULL : patientId.c_str(),
+                                      accessionNumber.empty() ? NULL : accessionNumber.c_str(),
+                                      studyInstanceUid.empty() ? NULL : studyInstanceUid.c_str(),
+                                      seriesInstanceUid.empty() ? NULL : seriesInstanceUid.c_str(),
+                                      sopInstanceUid.empty() ? NULL : sopInstanceUid.c_str(),
+                                      remoteAet.c_str(),
+                                      calledAet.c_str(),
+                                      targetAet.c_str(),
+                                      messageId);
+
+      if (driver == NULL)
+      {
+        LOG(ERROR) << "Plugin cannot create a driver for an incoming C-MOVE request";
+        throw OrthancException(ErrorCode_Plugin);
+      }
+
+      unsigned int size = params_.getMoveSize(driver);
+
+      return new Driver(driver, size, params_.applyMove, params_.freeMove);
+    }
+  };
+  
+
+
   OrthancPlugins::OrthancPlugins()
   {
     /* Sanity check of the compiler */
@@ -881,6 +1033,26 @@
   }
 
 
+  void OrthancPlugins::RegisterMoveCallback(const void* parameters)
+  {
+    const _OrthancPluginMoveCallback& p = 
+      *reinterpret_cast<const _OrthancPluginMoveCallback*>(parameters);
+
+    boost::mutex::scoped_lock lock(pimpl_->moveCallbackMutex_);
+
+    if (pimpl_->moveCallbacks_.callback != NULL)
+    {
+      LOG(ERROR) << "Can only register one plugin to handle C-MOVE requests";
+      throw OrthancException(ErrorCode_Plugin);
+    }
+    else
+    {
+      LOG(INFO) << "Plugin has registered a callback to handle C-MOVE requests";
+      pimpl_->moveCallbacks_ = p;
+    }
+  }
+
+
   void OrthancPlugins::RegisterDecodeImageCallback(const void* parameters)
   {
     const _OrthancPluginDecodeImageCallback& p = 
@@ -1982,6 +2154,10 @@
         RegisterFindCallback(parameters);
         return true;
 
+      case _OrthancPluginService_RegisterMoveCallback:
+        RegisterMoveCallback(parameters);
+        return true;
+
       case _OrthancPluginService_RegisterDecodeImageCallback:
         RegisterDecodeImageCallback(parameters);
         return true;
@@ -2666,6 +2842,26 @@
   }
 
 
+  IMoveRequestHandler* OrthancPlugins::ConstructMoveRequestHandler()
+  {
+    if (HasMoveHandler())
+    {
+      return new MoveHandler(*this);
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  bool OrthancPlugins::HasMoveHandler()
+  {
+    boost::mutex::scoped_lock lock(pimpl_->moveCallbackMutex_);
+    return pimpl_->moveCallbacks_.callback != NULL;
+  }
+
+
   bool OrthancPlugins::HasCustomImageDecoder()
   {
     boost::mutex::scoped_lock lock(pimpl_->decodeImageCallbackMutex_);
--- a/Plugins/Engine/OrthancPlugins.h	Wed May 25 15:16:17 2016 +0200
+++ b/Plugins/Engine/OrthancPlugins.h	Thu May 26 14:24:56 2016 +0200
@@ -54,6 +54,7 @@
 #include "../../OrthancServer/IDicomImageDecoder.h"
 #include "../../OrthancServer/DicomProtocol/IWorklistRequestHandlerFactory.h"
 #include "../../OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h"
+#include "../../OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h"
 #include "OrthancPluginDatabase.h"
 #include "PluginsManager.h"
 
@@ -71,7 +72,8 @@
     public IWorklistRequestHandlerFactory,
     public IDicomImageDecoder,
     public IIncomingHttpRequestFilter,
-    public IFindRequestHandlerFactory
+    public IFindRequestHandlerFactory,
+    public IMoveRequestHandlerFactory
   {
   private:
     struct PImpl;
@@ -79,6 +81,7 @@
 
     class WorklistHandler;
     class FindHandler;
+    class MoveHandler;
 
     void CheckContextAvailable();
 
@@ -93,6 +96,8 @@
 
     void RegisterFindCallback(const void* parameters);
 
+    void RegisterMoveCallback(const void* parameters);
+
     void RegisterDecodeImageCallback(const void* parameters);
 
     void RegisterIncomingHttpRequestFilter(const void* parameters);
@@ -260,6 +265,10 @@
     bool HasFindHandler();
 
     virtual IFindRequestHandler* ConstructFindRequestHandler();
+
+    bool HasMoveHandler();
+
+    virtual IMoveRequestHandler* ConstructMoveRequestHandler();
   };
 }
 
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Wed May 25 15:16:17 2016 +0200
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Thu May 26 14:24:56 2016 +0200
@@ -20,6 +20,7 @@
  *    - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2().
  *    - Possibly register a handler for C-Find SCP using OrthancPluginRegisterFindCallback().
  *    - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback().
+ *    - Possibly register a handler for C-Move SCP using OrthancPluginRegisterMoveCallback().
  *    - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback().
  *    - Possibly register a callback to filter incoming HTTP requests using OrthancPluginRegisterIncomingHttpRequestFilter().
  * -# <tt>void OrthancPluginFinalize()</tt>:
@@ -417,6 +418,7 @@
     _OrthancPluginService_RegisterDecodeImageCallback = 1006,
     _OrthancPluginService_RegisterIncomingHttpRequestFilter = 1007,
     _OrthancPluginService_RegisterFindCallback = 1008,
+    _OrthancPluginService_RegisterMoveCallback = 1009,
 
     /* Sending answers to REST calls */
     _OrthancPluginService_AnswerBuffer = 2000,
@@ -966,7 +968,7 @@
    *
    * @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 issuerAet 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 DicomCallbacks
@@ -974,33 +976,12 @@
   typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) (
     OrthancPluginWorklistAnswers*     answers,
     const OrthancPluginWorklistQuery* query,
-    const char*                       remoteAet,
+    const char*                       issuerAet,
     const char*                       calledAet);
 
 
 
   /**
-   * @brief Callback to handle the C-Find SCP requests.
-   *
-   * Signature of a callback function that is triggered when Orthanc
-   * receives a C-Find SCP request not concerning 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 DicomCallbacks
-   **/
-  typedef OrthancPluginErrorCode (*OrthancPluginFindCallback) (
-    OrthancPluginFindAnswers*     answers,
-    const OrthancPluginFindQuery* query,
-    const char*                   remoteAet,
-    const char*                   calledAet);
-
-
-
-  /**
    * @brief Callback to filter incoming HTTP requests received by Orthanc.
    *
    * Signature of a callback function that is triggered whenever
@@ -1029,6 +1010,116 @@
 
 
   /**
+   * @brief Callback to handle incoming C-Find SCP requests.
+   *
+   * Signature of a callback function that is triggered whenever
+   * Orthanc receives a C-Find SCP request not concerning modality
+   * worklists.
+   *
+   * @param answers The target structure where answers must be stored.
+   * @param query The worklist query.
+   * @param issuerAet 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 DicomCallbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginFindCallback) (
+    OrthancPluginFindAnswers*     answers,
+    const OrthancPluginFindQuery* query,
+    const char*                   issuerAet,
+    const char*                   calledAet);
+
+
+
+  /**
+   * @brief Callback to handle incoming C-Move SCP requests.
+   *
+   * Signature of a callback function that is triggered whenever
+   * Orthanc receives a C-Move SCP request. The callback receives the
+   * type of the resource of interest (study, series, instance...)
+   * together with the DICOM tags containing its identifiers. In turn,
+   * the plugin must create a driver object that will be responsible
+   * for driving the successive move suboperations.
+   *
+   * @param resourceType The type of the resource of interest. Note
+   * that this might be set to ResourceType_None if the
+   * QueryRetrieveLevel (0008,0052) tag was not provided by the
+   * issuer.
+   * @param patientId Content of the PatientID (0x0010, 0x0020) tag of the resource of interest. Might be NULL.
+   * @param accessionNumber Content of the AccessionNumber (0x0008, 0x0050) tag. Might be NULL.
+   * @param studyInstanceUid Content of the StudyInstanceUID (0x0020, 0x000d) tag. Might be NULL.
+   * @param seriesInstanceUid Content of the SeriesInstanceUID (0x0020, 0x000e) tag. Might be NULL.
+   * @param sopInstanceUid Content of the SOPInstanceUID (0x0008, 0x0018) tag. Might be NULL.
+   * @param issuerAet The Application Entity Title (AET) of the
+   * modality from which the request originates.
+   * @param sourceAet The Application Entity Title (AET) of the
+   * modality that should send its DICOM files to another modality.
+   * @param targetAet The Application Entity Title (AET) of the
+   * modality that should receive the DICOM files.
+   *
+   * @return The NULL value if the plugin cannot deal with this query,
+   * or a pointer to the driver object that is responsible for
+   * handling the successive move suboperations.
+   * 
+   * @note If targetAet equals sourceAet, this is actually a query/retrieve operation.
+   * @ingroup DicomCallbacks
+   **/
+  typedef void* (*OrthancPluginMoveCallback) (
+    OrthancPluginResourceType  resourceType,
+    const char*                patientId,
+    const char*                accessionNumber,
+    const char*                studyInstanceUid,
+    const char*                seriesInstanceUid,
+    const char*                sopInstanceUid,
+    const char*                issuerAet,
+    const char*                sourceAet,
+    const char*                targetAet,
+    uint16_t                   moveOriginatorId);
+    
+
+  /**
+   * @brief Callback to read the size of a C-Move driver.
+   * 
+   * Signature of a callback function that returns the number of
+   * C-Move suboperations that are to be achieved by the given C-Move
+   * driver. This driver is the return value of a previous call to the
+   * OrthancPluginMoveCallback() callback.
+   *
+   * @param moveDriver The C-Move driver of interest.
+   * @return The number of suboperations. 
+   **/
+  typedef uint32_t (*OrthancPluginGetMoveSize) (void* moveDriver);
+
+
+  /**
+   * @brief Callback to apply one C-Move suboperation.
+   * 
+   * Signature of a callback function that applies the next C-Move
+   * suboperation that os to be achieved by the given C-Move
+   * driver. This driver is the return value of a previous call to the
+   * OrthancPluginMoveCallback() callback.
+   *
+   * @param moveDriver The C-Move driver of interest.
+   * @return 0 if success, or the error code if failure.
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginApplyMove) (void* moveDriver);
+
+
+  /**
+   * @brief Callback to free one C-Move driver.
+   * 
+   * Signature of a callback function that releases the resources
+   * allocated by the given C-Move driver. This driver is the return
+   * value of a previous call to the OrthancPluginMoveCallback()
+   * callback.
+   *
+   * @param moveDriver The C-Move driver of interest.
+   **/
+  typedef void (*OrthancPluginFreeMove) (void* moveDriver);
+
+
+
+  /**
    * @brief Data structure that contains information about the Orthanc core.
    **/
   typedef struct _OrthancPluginContext_t
@@ -5183,6 +5274,49 @@
     }
   }
  
+
+
+
+  typedef struct
+  {
+    OrthancPluginMoveCallback   callback;
+    OrthancPluginGetMoveSize    getMoveSize;
+    OrthancPluginApplyMove      applyMove;
+    OrthancPluginFreeMove       freeMove;
+  } _OrthancPluginMoveCallback;
+
+  /**
+   * @brief Register a callback to handle C-Move requests.
+   *
+   * This function registers a callback to handle C-Move SCP requests.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The main callback.
+   * @param getMoveSize Callback to read the number of C-Move suboperations.
+   * @param applyMove Callback to apply one C-Move suboperations.
+   * @param freeMove Callback to free the C-Move driver.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterMoveCallback(
+    OrthancPluginContext*       context,
+    OrthancPluginMoveCallback   callback,
+    OrthancPluginGetMoveSize    getMoveSize,
+    OrthancPluginApplyMove      applyMove,
+    OrthancPluginFreeMove       freeMove)
+  {
+    _OrthancPluginMoveCallback params;
+    params.callback = callback;
+    params.getMoveSize = getMoveSize;
+    params.applyMove = applyMove;
+    params.freeMove = freeMove;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterMoveCallback, &params);
+  }
+
+
+
+
 #ifdef  __cplusplus
 }
 #endif