changeset 5029:6fd815fae50f delayed-deletion

merge
author Alain Mazy <am@osimis.io>
date Tue, 21 Jun 2022 11:06:41 +0200
parents c2ebc47f4f18 (current diff) 1ff06e0ea532 (diff)
children d6ed4c73c719
files NEWS
diffstat 9 files changed, 77 insertions(+), 54 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Mon Jun 20 16:53:21 2022 +0200
+++ b/NEWS	Tue Jun 21 11:06:41 2022 +0200
@@ -23,6 +23,7 @@
 
 * API version upgraded to 18
 * /system is now reporting "DatabaseServerIdentifier"
+* Added an Asynchronous mode to /modalities/../move.
 
 
 Version 1.11.0 (2022-05-09)
--- a/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp	Mon Jun 20 16:53:21 2022 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp	Tue Jun 21 11:06:41 2022 +0200
@@ -362,27 +362,7 @@
     DcmDataset* dataset = query->GetDcmtkObject().getDataset();
 
     const char* sopClass = UID_MOVEStudyRootQueryRetrieveInformationModel;
-    switch (level)
-    {
-      case ResourceType_Patient:
-        DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "PATIENT");
-        break;
-
-      case ResourceType_Study:
-        DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "STUDY");
-        break;
-
-      case ResourceType_Series:
-        DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "SERIES");
-        break;
-
-      case ResourceType_Instance:
-        DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE");
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
+    DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, ResourceTypeToDicomQueryRetrieveLevel(level));
 
     // Figure out which of the accepted presentation contexts should be used
     int presID = ASC_findAcceptedPresentationContextID(&association_->GetDcmtkAssociation(), sopClass);
@@ -519,32 +499,26 @@
     DcmDataset* dataset = query->GetDcmtkObject().getDataset();
     assert(dataset != NULL);
 
-    const char* clevel = NULL;
+    const char* clevel = ResourceTypeToDicomQueryRetrieveLevel(level);
     const char* sopClass = NULL;
 
+    DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, clevel);
+
     switch (level)
     {
       case ResourceType_Patient:
-        clevel = "PATIENT";
-        DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "PATIENT");
         sopClass = UID_FINDPatientRootQueryRetrieveInformationModel;
         break;
 
       case ResourceType_Study:
-        clevel = "STUDY";
-        DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "STUDY");
         sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
         break;
 
       case ResourceType_Series:
-        clevel = "SERIES";
-        DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "SERIES");
         sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
         break;
 
       case ResourceType_Instance:
-        clevel = "IMAGE";
-        DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE");
         sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
         break;
 
--- a/OrthancFramework/Sources/Enumerations.cpp	Mon Jun 20 16:53:21 2022 +0200
+++ b/OrthancFramework/Sources/Enumerations.cpp	Tue Jun 21 11:06:41 2022 +0200
@@ -1314,6 +1314,27 @@
     throw OrthancException(ErrorCode_ParameterOutOfRange);
   }
 
+  const char* ResourceTypeToDicomQueryRetrieveLevel(ResourceType type)
+  {
+    if (type == ResourceType_Patient)
+    {
+      return "PATIENT";
+    }
+    else if (type == ResourceType_Study)
+    {
+      return "STUDY";
+    }
+    else if (type == ResourceType_Series)
+    {
+      return "SERIES";
+    }
+    else if (type == ResourceType_Instance)
+    {
+      return "IMAGE";
+    }
+
+    throw OrthancException(ErrorCode_ParameterOutOfRange);
+  }
 
   ImageFormat StringToImageFormat(const char* format)
   {
--- a/OrthancFramework/Sources/Enumerations.h	Mon Jun 20 16:53:21 2022 +0200
+++ b/OrthancFramework/Sources/Enumerations.h	Tue Jun 21 11:06:41 2022 +0200
@@ -862,6 +862,9 @@
   bool IsResourceLevelAboveOrEqual(ResourceType level,
                                    ResourceType reference);
 
+ORTHANC_PUBLIC
+  const char* ResourceTypeToDicomQueryRetrieveLevel(ResourceType type);
+
   ORTHANC_PUBLIC
   DicomModule GetModule(ResourceType type);
 
--- a/OrthancServer/Sources/LuaScripting.cpp	Mon Jun 20 16:53:21 2022 +0200
+++ b/OrthancServer/Sources/LuaScripting.cpp	Tue Jun 21 11:06:41 2022 +0200
@@ -34,6 +34,8 @@
 
 #include <OrthancServerResources.h>
 
+static const char* ON_HEART_BEAT = "OnHeartBeat";
+
 
 namespace Orthanc
 {
@@ -785,11 +787,11 @@
 
   void LuaScripting::HeartBeatThread(LuaScripting* that)
   {
-    static const boost::posix_time::time_duration PERIODICITY =
+    static const unsigned int GRANULARITY = 100;  // In milliseconds
+    
+    const boost::posix_time::time_duration PERIODICITY =
       boost::posix_time::seconds(that->heartBeatPeriod_);
     
-    unsigned int sleepDelay = 100;
-    
     boost::posix_time::ptime next =
       boost::posix_time::microsec_clock::universal_time() + PERIODICITY;
     
@@ -797,25 +799,26 @@
 
     while (!shouldStop)
     {
-      boost::this_thread::sleep(boost::posix_time::milliseconds(sleepDelay));
+      boost::this_thread::sleep(boost::posix_time::milliseconds(GRANULARITY));
 
       if (boost::posix_time::microsec_clock::universal_time() >= next)
       {
         LuaScripting::Lock lock(*that);
 
-        if (lock.GetLua().IsExistingFunction("OnHeartBeat"))
+        if (lock.GetLua().IsExistingFunction(ON_HEART_BEAT))
         {
-          LuaFunctionCall call(lock.GetLua(), "OnHeartBeat");
+          LuaFunctionCall call(lock.GetLua(), ON_HEART_BEAT);
           call.Execute();
         }
 
         next = boost::posix_time::microsec_clock::universal_time() + PERIODICITY;
       }
 
-      boost::recursive_mutex::scoped_lock lock(that->mutex_);
-      shouldStop = that->state_ == State_Done;
+      {
+        boost::recursive_mutex::scoped_lock lock(that->mutex_);
+        shouldStop = (that->state_ == State_Done);
+      }
     }
-
   }
 
   void LuaScripting::EventThread(LuaScripting* that)
@@ -865,9 +868,9 @@
       LOG(INFO) << "Starting the Lua engine";
       eventThread_ = boost::thread(EventThread, this);
       
-      LuaScripting::Lock lock(*this);
+      LuaScripting::Lock luaLock(*this);
 
-      if (heartBeatPeriod_ > 0 && lock.GetLua().IsExistingFunction("OnHeartBeat"))
+      if (heartBeatPeriod_ > 0 && luaLock.GetLua().IsExistingFunction(ON_HEART_BEAT))
       {
         LOG(INFO) << "Starting the Lua HeartBeat thread with a period of " << heartBeatPeriod_ << " seconds";
         heartBeatThread_ = boost::thread(HeartBeatThread, this);
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp	Mon Jun 20 16:53:21 2022 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp	Tue Jun 21 11:06:41 2022 +0200
@@ -1517,6 +1517,7 @@
   {
     if (call.IsDocumentation())
     {
+      OrthancRestApi::DocumentSubmitCommandsJob(call);
       call.GetDocumentation()
         .SetTag("Networking")
         .SetSummary("Trigger C-MOVE SCU")
@@ -1534,12 +1535,12 @@
                          "Target AET that will be used by the remote DICOM modality as a target for its C-STORE SCU "
                          "commands, defaults to `DicomAet` configuration option in order to do a simple query/retrieve", false)
         .SetRequestField(KEY_TIMEOUT, RestApiCallDocumentation::Type_Number,
-                         "Timeout for the C-STORE command, in seconds", false)
+                         "Timeout for the C-MOVE command, in seconds", false)
         .SetUriArgument("id", "Identifier of the modality of interest");
       return;
     }
 
-    const ServerContext& context = OrthancRestApi::GetContext(call);
+    ServerContext& context = OrthancRestApi::GetContext(call);
 
     Json::Value request;
 
@@ -1564,21 +1565,33 @@
     const RemoteModalityParameters source =
       MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
 
-    DicomAssociationParameters params(localAet, source);
-    InjectAssociationTimeout(params, request);  // Handles KEY_TIMEOUT
+    std::unique_ptr<DicomMoveScuJob> job(new DicomMoveScuJob(context));
 
-    DicomControlUserConnection connection(params);
+    job->SetQueryFormat(DicomToJsonFormat_Short);
+    
+    // QueryAccessor query(call);
+    job->SetTargetAet(targetAet);
+    job->SetLocalAet(localAet);
+    job->SetRemoteModality(source);
+
+    if (request.isMember(KEY_TIMEOUT))
+    {
+      job->SetTimeout(SerializationToolbox::ReadUnsignedInteger(request, KEY_TIMEOUT));
+    }
 
     for (Json::Value::ArrayIndex i = 0; i < request[KEY_RESOURCES].size(); i++)
     {
       DicomMap resource;
       FromDcmtkBridge::FromJson(resource, request[KEY_RESOURCES][i], "Resources elements");
-      
-      connection.Move(targetAet, level, resource);
+
+      resource.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, std::string(ResourceTypeToDicomQueryRetrieveLevel(level)), false);
+
+      job->AddQuery(resource);      
     }
 
-    // Move has succeeded
-    call.GetOutput().AnswerBuffer("{}", MimeType_Json);
+    OrthancRestApi::GetApi(call).SubmitCommandsJob
+      (call, job.release(), true /* synchronous by default */, request);
+    return;
   }
 
 
--- a/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp	Mon Jun 20 16:53:21 2022 +0200
+++ b/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp	Tue Jun 21 11:06:41 2022 +0200
@@ -107,7 +107,8 @@
     query.GetAnswer(query.GetSize() - 1).Remove(DICOM_TAG_SPECIFIC_CHARACTER_SET);
   }
 
-
+  // this method is used to implement the retrieve part of a Q&R 
+  // it keeps only the main dicom tags from the C-Find answer
   void DicomMoveScuJob::AddFindAnswer(const DicomMap& answer)
   {
     DicomMap item;
@@ -122,6 +123,13 @@
     AddCommand(new Command(*this, answer));
   }
 
+  // this method is used to implement a C-Move
+  // it keeps all tags from the C-Move query
+  void DicomMoveScuJob::AddQuery(const DicomMap& query)
+  {
+    AddToQuery(query_, query);
+    AddCommand(new Command(*this, query));
+  }
   
   void DicomMoveScuJob::AddFindAnswer(QueryRetrieveHandler& query,
                                       size_t i)
--- a/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.h	Mon Jun 20 16:53:21 2022 +0200
+++ b/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.h	Tue Jun 21 11:06:41 2022 +0200
@@ -51,7 +51,7 @@
   public:
     explicit DicomMoveScuJob(ServerContext& context) :
       context_(context),
-      query_(true /* this is for worklists */),
+      query_(false  /* this is not for worklists */),
       queryFormat_(DicomToJsonFormat_Short)
     {
     }
@@ -61,6 +61,8 @@
 
     void AddFindAnswer(const DicomMap& answer);
     
+    void AddQuery(const DicomMap& query);
+
     void AddFindAnswer(QueryRetrieveHandler& query,
                        size_t i);
 
--- a/TODO	Mon Jun 20 16:53:21 2022 +0200
+++ b/TODO	Tue Jun 21 11:06:41 2022 +0200
@@ -108,8 +108,6 @@
 * Support "/preview" and "/matlab" for LUT color images
 * Try to transcode files if a simple decoding fails:
   https://groups.google.com/g/orthanc-users/c/b8168-NkAhA/m/Df3j-CO9CgAJ
-* (1) Add asynchronous mode in "/modalitities/.../move" for C-MOVE SCU:
-  https://groups.google.com/g/orthanc-users/c/G3_jBy4X4NQ/m/8BanTsdMBQAJ
 * (2) Ranges of DICOM tags for "Keep" and "Remove" in ".../modify" and ".../anonymize": 
   https://groups.google.com/g/orthanc-users/c/6dETktKo9v8/m/b0LUvSfwAgAJ
 * return error code/reason in HTTP response if resubmit/cancel/pause fails ...