changeset 1907:5011a597b6ce

Support of Move Originator Message ID (0000,1031) in C-Store responses driven by C-Move
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 07 Jan 2016 11:28:19 +0100
parents d7c1cb559431
children 5096681efce6
files NEWS OrthancServer/DicomProtocol/DicomUserConnection.cpp OrthancServer/DicomProtocol/DicomUserConnection.h OrthancServer/LuaScripting.cpp OrthancServer/OrthancMoveRequestHandler.cpp OrthancServer/OrthancRestApi/OrthancRestModalities.cpp OrthancServer/Scheduler/StoreScuCommand.cpp OrthancServer/Scheduler/StoreScuCommand.h UnitTestsSources/MultiThreadingTests.cpp
diffstat 9 files changed, 99 insertions(+), 38 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Tue Jan 05 17:45:27 2016 +0100
+++ b/NEWS	Thu Jan 07 11:28:19 2016 +0100
@@ -3,6 +3,7 @@
 
 * Support of optional tags for counting resources in C-Find:
   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
 * 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
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Tue Jan 05 17:45:27 2016 +0100
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Thu Jan 07 11:28:19 2016 +0100
@@ -148,7 +148,9 @@
 
     void CheckIsOpen() const;
 
-    void Store(DcmInputStream& is, DicomUserConnection& connection);
+    void Store(DcmInputStream& is, 
+               DicomUserConnection& connection,
+               uint16_t moveMessageID);
   };
 
 
@@ -251,7 +253,9 @@
   }
 
 
-  void DicomUserConnection::PImpl::Store(DcmInputStream& is, DicomUserConnection& connection)
+  void DicomUserConnection::PImpl::Store(DcmInputStream& is, 
+                                         DicomUserConnection& connection,
+                                         uint16_t moveMessageID)
   {
     CheckIsOpen();
 
@@ -325,18 +329,28 @@
     }
 
     // Prepare the transmission of data
-    T_DIMSE_C_StoreRQ req;
-    memset(&req, 0, sizeof(req));
-    req.MessageID = assoc_->nextMsgID++;
-    strcpy(req.AffectedSOPClassUID, sopClass);
-    strcpy(req.AffectedSOPInstanceUID, sopInstance);
-    req.DataSetType = DIMSE_DATASET_PRESENT;
-    req.Priority = DIMSE_PRIORITY_MEDIUM;
+    T_DIMSE_C_StoreRQ request;
+    memset(&request, 0, sizeof(request));
+    request.MessageID = assoc_->nextMsgID++;
+    strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN);
+    request.Priority = DIMSE_PRIORITY_MEDIUM;
+    request.DataSetType = DIMSE_DATASET_PRESENT;
+    strncpy(request.AffectedSOPInstanceUID, sopInstance, DIC_UI_LEN);
+
+    strncpy(request.MoveOriginatorApplicationEntityTitle, 
+            connection.GetLocalApplicationEntityTitle().c_str(), DIC_AE_LEN);
+    request.opts = O_STORE_MOVEORIGINATORAETITLE;
+
+    if (moveMessageID != 0)
+    {
+      request.MoveOriginatorID = moveMessageID;  // The type DIC_US is an alias for uint16_t
+      request.opts |= O_STORE_MOVEORIGINATORID;
+    }
 
     // Finally conduct transmission of data
     T_DIMSE_C_StoreRSP rsp;
     DcmDataset* statusDetail = NULL;
-    Check(DIMSE_storeUser(assoc_, presID, &req,
+    Check(DIMSE_storeUser(assoc_, presID, &request,
                           NULL, dcmff.getDataset(), /*progressCallback*/ NULL, NULL,
                           /*opt_blockMode*/ DIMSE_BLOCKING, /*opt_dimse_timeout*/ dimseTimeout_,
                           &rsp, &statusDetail, NULL));
@@ -511,9 +525,9 @@
     T_DIMSE_C_FindRQ request;
     memset(&request, 0, sizeof(request));
     request.MessageID = association->nextMsgID++;
-    strcpy(request.AffectedSOPClassUID, sopClass);
+    strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN);
+    request.Priority = DIMSE_PRIORITY_MEDIUM;
     request.DataSetType = DIMSE_DATASET_PRESENT;
-    request.Priority = DIMSE_PRIORITY_MEDIUM;
 
     T_DIMSE_C_FindRSP response;
     DcmDataset* statusDetail = NULL;
@@ -678,10 +692,10 @@
     T_DIMSE_C_MoveRQ request;
     memset(&request, 0, sizeof(request));
     request.MessageID = pimpl_->assoc_->nextMsgID++;
-    strcpy(request.AffectedSOPClassUID, sopClass);
+    strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN);
+    request.Priority = DIMSE_PRIORITY_MEDIUM;
     request.DataSetType = DIMSE_DATASET_PRESENT;
-    request.Priority = DIMSE_PRIORITY_MEDIUM;
-    strncpy(request.MoveDestination, targetAet.c_str(), sizeof(DIC_AE) / sizeof(char));
+    strncpy(request.MoveDestination, targetAet.c_str(), DIC_AE_LEN);
 
     T_DIMSE_C_MoveRSP response;
     DcmDataset* statusDetail = NULL;
@@ -920,7 +934,9 @@
     return pimpl_->IsOpen();
   }
 
-  void DicomUserConnection::Store(const char* buffer, size_t size)
+  void DicomUserConnection::Store(const char* buffer, 
+                                  size_t size,
+                                  uint16_t moveMessageID)
   {
     // Prepare an input stream for the memory buffer
     DcmInputBufferStream is;
@@ -928,22 +944,24 @@
       is.setBuffer(buffer, size);
     is.setEos();
       
-    pimpl_->Store(is, *this);
+    pimpl_->Store(is, *this, moveMessageID);
   }
 
-  void DicomUserConnection::Store(const std::string& buffer)
+  void DicomUserConnection::Store(const std::string& buffer,
+                                  uint16_t moveMessageID)
   {
     if (buffer.size() > 0)
-      Store(reinterpret_cast<const char*>(&buffer[0]), buffer.size());
+      Store(reinterpret_cast<const char*>(&buffer[0]), buffer.size(), moveMessageID);
     else
-      Store(NULL, 0);
+      Store(NULL, 0, moveMessageID);
   }
 
-  void DicomUserConnection::StoreFile(const std::string& path)
+  void DicomUserConnection::StoreFile(const std::string& path,
+                                      uint16_t moveMessageID)
   {
     // Prepare an input stream for the file
     DcmInputFileStream is(path.c_str());
-    pimpl_->Store(is, *this);
+    pimpl_->Store(is, *this, moveMessageID);
   }
 
   bool DicomUserConnection::Echo()
--- a/OrthancServer/DicomProtocol/DicomUserConnection.h	Tue Jan 05 17:45:27 2016 +0100
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.h	Thu Jan 07 11:28:19 2016 +0100
@@ -133,11 +133,15 @@
 
     bool Echo();
 
-    void Store(const char* buffer, size_t size);
+    void Store(const char* buffer, 
+               size_t size,
+               uint16_t moveMessageID);
 
-    void Store(const std::string& buffer);
+    void Store(const std::string& buffer,
+               uint16_t moveMessageID);
 
-    void StoreFile(const std::string& path);
+    void StoreFile(const std::string& path,
+                   uint16_t moveMessageID);
 
     void Find(DicomFindAnswers& result,
               ResourceType level,
--- a/OrthancServer/LuaScripting.cpp	Tue Jan 05 17:45:27 2016 +0100
+++ b/OrthancServer/LuaScripting.cpp	Thu Jan 07 11:28:19 2016 +0100
@@ -253,7 +253,8 @@
       LOG(INFO) << "Lua script to send resource " << parameters["Resource"].asString()
                 << " to modality " << modality << " using Store-SCU";
       return new StoreScuCommand(context_, localAet,
-                                 Configuration::GetModalityUsingSymbolicName(modality), true);
+                                 Configuration::GetModalityUsingSymbolicName(modality), 
+                                 true, 0 /* not a C-MOVE */);
     }
 
     if (operation == "store-peer")
--- a/OrthancServer/OrthancMoveRequestHandler.cpp	Tue Jan 05 17:45:27 2016 +0100
+++ b/OrthancServer/OrthancMoveRequestHandler.cpp	Thu Jan 07 11:28:19 2016 +0100
@@ -52,14 +52,17 @@
       std::vector<std::string> instances_;
       size_t position_;
       RemoteModalityParameters remote_;
+      uint16_t moveRequestID_;
 
     public:
       OrthancMoveRequestIterator(ServerContext& context,
                                  const std::string& aet,
-                                 const std::string& publicId) :
+                                 const std::string& publicId,
+                                 uint16_t moveRequestID) :
         context_(context),
         localAet_(context.GetDefaultLocalApplicationEntityTitle()),
-        position_(0)
+        position_(0),
+        moveRequestID_(moveRequestID)
       {
         LOG(INFO) << "Sending resource " << publicId << " to modality \"" << aet << "\"";
 
@@ -95,7 +98,7 @@
         {
           ReusableDicomUserConnection::Locker locker
             (context_.GetReusableDicomUserConnection(), localAet_, remote_);
-          locker.GetConnection().Store(dicom);
+          locker.GetConnection().Store(dicom, moveRequestID_);
         }
 
         return Status_Success;
@@ -184,6 +187,33 @@
     }
 
 
+    /**
+     * Retrieve the Message ID (0000,0110) for this C-MOVE request, if
+     * any. If present, this Message ID will be stored in the Move
+     * Originator Message ID (0000,1031) field of the C-MOVE response.
+     * http://dicom.nema.org/medical/dicom/current/output/html/part07.html#sect_9.3.1
+     **/
+
+    static const DicomTag MESSAGE_ID(0x0000, 0x0110);
+    const DicomValue* messageIdTmp = input.TestAndGetValue(MESSAGE_ID);
+
+    uint16_t messageId = 0;
+
+    if (messageIdTmp != NULL &&
+        !messageIdTmp->IsNull() &&
+        !messageIdTmp->IsBinary())
+    {
+      try
+      {
+        messageId = boost::lexical_cast<uint16_t>(messageIdTmp->GetContent());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        LOG(WARNING) << "Cannot convert the Message ID (\"" << messageIdTmp ->GetContent()
+                     << "\") of an incoming C-MOVE request to an integer, assuming zero";
+      }
+    }
+
 
     /**
      * Retrieve the query level.
@@ -208,7 +238,7 @@
           LookupIdentifier(publicId, ResourceType_Study, input) ||
           LookupIdentifier(publicId, ResourceType_Patient, input))
       {
-        return new OrthancMoveRequestIterator(context_, targetAet, publicId);
+        return new OrthancMoveRequestIterator(context_, targetAet, publicId, messageId);
       }
       else
       {
@@ -229,7 +259,7 @@
 
     if (LookupIdentifier(publicId, level, input))
     {
-      return new OrthancMoveRequestIterator(context_, targetAet, publicId);
+      return new OrthancMoveRequestIterator(context_, targetAet, publicId, messageId);
     }
     else
     {
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Tue Jan 05 17:45:27 2016 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Thu Jan 07 11:28:19 2016 +0100
@@ -695,7 +695,8 @@
     for (std::list<std::string>::const_iterator 
            it = instances.begin(); it != instances.end(); ++it)
     {
-      job.AddCommand(new StoreScuCommand(context, localAet, p, false)).AddInput(*it);
+      job.AddCommand(new StoreScuCommand(context, localAet, p, false,
+                                         0 /* not a C-MOVE */)).AddInput(*it);
     }
 
     job.SetDescription("HTTP request: Store-SCU to peer \"" + remote + "\"");
--- a/OrthancServer/Scheduler/StoreScuCommand.cpp	Tue Jan 05 17:45:27 2016 +0100
+++ b/OrthancServer/Scheduler/StoreScuCommand.cpp	Thu Jan 07 11:28:19 2016 +0100
@@ -40,11 +40,13 @@
   StoreScuCommand::StoreScuCommand(ServerContext& context,
                                    const std::string& localAet,
                                    const RemoteModalityParameters& modality,
-                                   bool ignoreExceptions) : 
+                                   bool ignoreExceptions,
+                                   uint16_t moveMessageID) : 
     context_(context),
     modality_(modality),
     ignoreExceptions_(ignoreExceptions),
-    localAet_(localAet)
+    localAet_(localAet),
+    moveMessageID_(moveMessageID)
   {
   }
 
@@ -63,7 +65,8 @@
       {
         std::string dicom;
         context_.ReadFile(dicom, *it, FileContentType_Dicom);
-        locker.GetConnection().Store(dicom);
+
+        locker.GetConnection().Store(dicom, moveMessageID_);
 
         // Only chain with other commands if this command succeeds
         outputs.push_back(*it);
--- a/OrthancServer/Scheduler/StoreScuCommand.h	Tue Jan 05 17:45:27 2016 +0100
+++ b/OrthancServer/Scheduler/StoreScuCommand.h	Thu Jan 07 11:28:19 2016 +0100
@@ -44,12 +44,15 @@
     RemoteModalityParameters modality_;
     bool ignoreExceptions_;
     std::string localAet_;
+    uint16_t moveMessageID_;
 
   public:
     StoreScuCommand(ServerContext& context,
                     const std::string& localAet,
                     const RemoteModalityParameters& modality,
-                    bool ignoreExceptions);
+                    bool ignoreExceptions,
+                    uint16_t moveMessageID /* only makes sense if this 
+                                              command results from a C-MOVE */);
 
     virtual bool Apply(ListOfStrings& outputs,
                        const ListOfStrings& inputs);
--- a/UnitTestsSources/MultiThreadingTests.cpp	Tue Jan 05 17:45:27 2016 +0100
+++ b/UnitTestsSources/MultiThreadingTests.cpp	Thu Jan 07 11:28:19 2016 +0100
@@ -138,7 +138,7 @@
   {
     RemoteModalityParameters remote("STORESCP", "localhost", 2000, ModalityManufacturer_Generic);
     ReusableDicomUserConnection::Locker lock(c, "ORTHANC", remote);
-    lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676281");
+    lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676281", 0);
   }
 
   printf("**\n"); fflush(stdout);
@@ -148,7 +148,7 @@
   {
     RemoteModalityParameters remote("STORESCP", "localhost", 2000, ModalityManufacturer_Generic);
     ReusableDicomUserConnection::Locker lock(c, "ORTHANC", remote);
-    lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676277");
+    lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676277", 0);
   }
 
   Toolbox::ServerBarrier();