changeset 5905:dad78aa9141e get-scu

move/get jobs: progress
author Alain Mazy <am@orthanc.team>
date Fri, 06 Dec 2024 12:06:05 +0100
parents bed6a8ba5431
children 573c538a2405
files NEWS OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.h OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.h
diffstat 8 files changed, 103 insertions(+), 73 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Thu Dec 05 12:02:33 2024 +0100
+++ b/NEWS	Fri Dec 06 12:06:05 2024 +0100
@@ -27,6 +27,7 @@
   https://discourse.orthanc-server.org/t/dicomwebplugin-does-not-return-series-metadata-properly/5195
 * /queries/../retrieve now accepts a new field in the payload: "RetrieveMethod" to define wheter 
   Orthanc uses C-MOVE or C-GET to retrieve the resource.
+* improved progress reporting of DicomMoveScu jobs.
 
 Maintenance
 -----------
--- a/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp	Thu Dec 05 12:02:33 2024 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp	Fri Dec 06 12:06:05 2024 +0100
@@ -393,6 +393,22 @@
     }
   }
 
+  void MoveProgressCallback(void *callbackData,
+                            T_DIMSE_C_MoveRQ *request,
+                            int responseCount, 
+                            T_DIMSE_C_MoveRSP *response)
+  {
+    DicomControlUserConnection::IProgressListener* listener = reinterpret_cast<DicomControlUserConnection::IProgressListener*>(callbackData);
+    if (listener)
+    {
+      LOG(INFO) << "---------" << response->NumberOfRemainingSubOperations << "  " << response->NumberOfCompletedSubOperations;
+      listener->OnProgressUpdated(response->NumberOfRemainingSubOperations,
+                                  response->NumberOfCompletedSubOperations,
+                                  response->NumberOfFailedSubOperations,
+                                  response->NumberOfWarningSubOperations);
+    }
+  }
+
     
   void DicomControlUserConnection::MoveInternal(const std::string& targetAet,
                                                 ResourceType level,
@@ -434,7 +450,8 @@
     DcmDataset* statusDetail = NULL;
     DcmDataset* responseIdentifiers = NULL;
     OFCondition cond = DIMSE_moveUser(
-      &association_->GetDcmtkAssociation(), presID, &request, dataset, /*moveCallback*/ NULL, NULL,
+      &association_->GetDcmtkAssociation(), presID, &request, dataset, 
+      (progressListener_ != NULL ? MoveProgressCallback : NULL), progressListener_,
       /*opt_blockMode*/ (parameters_.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
       /*opt_dimse_timeout*/ parameters_.GetTimeout(),
       &association_->GetDcmtkNetwork(), /*subOpCallback*/ NULL, NULL,
@@ -456,6 +473,14 @@
       OFString str;
       CLOG(TRACE, DICOM) << "Received Final Move Response:" << std::endl
                          << DIMSE_dumpMessage(str, response, DIMSE_INCOMING);
+
+      if (progressListener_ != NULL)
+      {
+        progressListener_->OnProgressUpdated(response.NumberOfRemainingSubOperations,
+                                             response.NumberOfCompletedSubOperations,
+                                             response.NumberOfFailedSubOperations,
+                                             response.NumberOfWarningSubOperations);
+      }
     }
     
     /**
@@ -495,8 +520,6 @@
     assert(association_.get() != NULL);
     association_->Open(parameters_);
 
-    // TODO-GET: if findResults is the result of a C-Find, we can use the SopClassUIDs for the negotiation
-
     std::unique_ptr<ParsedDicomFile> query(
       ConvertQueryFields(findResult, parameters_.GetRemoteModality().GetManufacturer()));
     DcmDataset* queryDataset = query->GetDcmtkObject().getDataset();
@@ -550,7 +573,7 @@
     
     OFCondition cond = DIMSE_sendMessageUsingMemoryData(
           &(association_->GetDcmtkAssociation()), cgetPresID, &msgGetRequest, NULL /* statusDetail */, queryDataset,
-          NULL /* progress callback TODO-GET */, NULL /* callback context */, NULL /* commandSet */);
+          NULL, NULL, NULL /* commandSet */);
       
     if (cond.bad())
     {
@@ -597,17 +620,18 @@
               << DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, cmdPresId);
           }
 
-          // TODO-GET: for progress handler
-          // OFunique_ptr<RetrieveResponse> getRSP(new RetrieveResponse());
-          // getRSP->m_affectedSOPClassUID     = rsp.msg.CGetRSP.AffectedSOPClassUID;
-          // getRSP->m_messageIDRespondedTo    = rsp.msg.CGetRSP.MessageIDBeingRespondedTo;
-          // getRSP->m_status                  = rsp.msg.CGetRSP.DimseStatus;
-          // getRSP->m_numberOfRemainingSubops = rsp.msg.CGetRSP.NumberOfRemainingSubOperations;
-          // getRSP->m_numberOfCompletedSubops = rsp.msg.CGetRSP.NumberOfCompletedSubOperations;
-          // getRSP->m_numberOfFailedSubops    = rsp.msg.CGetRSP.NumberOfFailedSubOperations;
-          // getRSP->m_numberOfWarningSubops   = rsp.msg.CGetRSP.NumberOfWarningSubOperations;
-          // getRSP->m_statusDetail            = statusDetail;
+          if (progressListener_ != NULL)
+          {
+            progressListener_->OnProgressUpdated(rsp.msg.CGetRSP.NumberOfRemainingSubOperations,
+                                                  rsp.msg.CGetRSP.NumberOfCompletedSubOperations,
+                                                  rsp.msg.CGetRSP.NumberOfFailedSubOperations,
+                                                  rsp.msg.CGetRSP.NumberOfWarningSubOperations);
+          }
 
+          if (rsp.msg.CGetRSP.DimseStatus == 0x0000)  // final success message
+          {
+            continueSession = false;
+          }
         }
         // Handle C-STORE Request
         else if (rsp.CommandField == DIMSE_C_STORE_RQ)
@@ -669,7 +693,7 @@
             result = DIMSE_sendMessageUsingMemoryData(&(association_->GetDcmtkAssociation()), 
                                                       cmdPresId, 
                                                       &storeResponse, NULL /* statusDetail */, NULL /* dataObject */,
-                                                      NULL /* progress callback TODO-GET */, NULL /* callback context */, NULL /* commandSet */);
+                                                      NULL, NULL, NULL /* commandSet */);
             if (result.bad())
             {
               continueSession = false;
@@ -704,7 +728,8 @@
 
   DicomControlUserConnection::DicomControlUserConnection(const DicomAssociationParameters& params, ScuOperationFlags scuOperation) :
     parameters_(params),
-    association_(new DicomAssociation)
+    association_(new DicomAssociation),
+    progressListener_(NULL)
   {
     assert((scuOperation & ScuOperationFlags_Get) == 0);  // you must provide acceptedStorageSopClassUids for Get SCU
     std::set<std::string> emptyStorageSopClasses;
@@ -718,7 +743,8 @@
                                                          const std::set<std::string>& acceptedStorageSopClasses,
                                                          const std::list<DicomTransferSyntax>& proposedStorageTransferSyntaxes) :
     parameters_(params),
-    association_(new DicomAssociation)
+    association_(new DicomAssociation),
+    progressListener_(NULL)
   {
     SetupPresentationContexts(scuOperation, acceptedStorageSopClasses, proposedStorageTransferSyntaxes);
   }
--- a/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.h	Thu Dec 05 12:02:33 2024 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.h	Fri Dec 06 12:06:05 2024 +0100
@@ -63,9 +63,20 @@
 
   class DicomControlUserConnection : public boost::noncopyable
   {
+  public:
+    class IProgressListener
+    {
+    public:
+      virtual void OnProgressUpdated(uint16_t nbRemainingSubOperations,
+                                     uint16_t nbCompletedSubOperations,
+                                     uint16_t nbFailedSubOperations,
+                                     uint16_t nbWarningSubOperations) = 0;
+    };
+
   private:
     DicomAssociationParameters           parameters_;
     boost::shared_ptr<DicomAssociation>  association_;
+    IProgressListener*                   progressListener_;
 
     void SetupPresentationContexts(ScuOperationFlags scuOperation,
                                    const std::set<std::string>& acceptedStorageSopClasses,
@@ -99,6 +110,11 @@
 
     bool Echo();
 
+    void SetProgressListener(IProgressListener* progressListener)
+    {
+      progressListener_ = progressListener;
+    }
+
     void Find(DicomFindAnswers& result,
               ResourceType level,
               const DicomMap& originalFields,
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp	Thu Dec 05 12:02:33 2024 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp	Fri Dec 06 12:06:05 2024 +0100
@@ -915,59 +915,6 @@
   }
 
 
-  // static void SubmitGetScuJob(RestApiPostCall& call,
-  //                             bool allAnswers,
-  //                             size_t index)
-  // {
-  //   ServerContext& context = OrthancRestApi::GetContext(call);
-
-  //   int timeout = -1;
-  //   Json::Value body;
-
-  //   if (call.ParseJsonRequest(body))
-  //   {
-  //     timeout = Toolbox::GetJsonIntegerField(body, KEY_TIMEOUT, -1);
-  //   }
-    
-  //   std::unique_ptr<DicomGetScuJob> job(new DicomGetScuJob(context));
-  //   job->SetQueryFormat(OrthancRestApi::GetDicomFormat(body, DicomToJsonFormat_Short));
-    
-  //   {
-  //     QueryAccessor query(call);
-  //     job->SetRemoteModality(query.GetHandler().GetRemoteModality());
-
-  //     if (timeout >= 0)
-  //     {
-  //       // New in Orthanc 1.7.0
-  //       job->SetTimeout(static_cast<uint32_t>(timeout));
-  //     }
-  //     else if (query.GetHandler().HasTimeout())
-  //     {
-  //       // New in Orthanc 1.9.1
-  //       job->SetTimeout(query.GetHandler().GetTimeout());
-  //     }
-
-  //     LOG(WARNING) << "Driving C-Get SCU on remote modality "
-  //                  << query.GetHandler().GetRemoteModality().GetApplicationEntityTitle();
-
-  //     if (allAnswers)
-  //     {
-  //       for (size_t i = 0; i < query.GetHandler().GetAnswersCount(); i++)
-  //       {
-  //         job->AddFindAnswer(query.GetHandler(), i);
-  //       }
-  //     }
-  //     else
-  //     {
-  //       job->AddFindAnswer(query.GetHandler(), index);
-  //     }
-  //   }
-
-  //   OrthancRestApi::GetApi(call).SubmitCommandsJob
-  //     (call, job.release(), true /* synchronous by default */, body);
-  // }
-
-
   static void SubmitRetrieveJob(RestApiPostCall& call,
                                 bool allAnswers,
                                 size_t index)
--- a/OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp	Thu Dec 05 12:02:33 2024 +0100
+++ b/OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp	Fri Dec 06 12:06:05 2024 +0100
@@ -99,7 +99,8 @@
                                                        sopClassesToPropose,
                                                        proposedTransferSyntaxes));
     }
-    
+
+    connection_->SetProgressListener(this);
     connection_->Get(findAnswer, InstanceReceivedHandler, &context_);
   }
 
--- a/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp	Thu Dec 05 12:02:33 2024 +0100
+++ b/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp	Fri Dec 06 12:06:05 2024 +0100
@@ -46,6 +46,7 @@
       connection_.reset(new DicomControlUserConnection(parameters_, ScuOperationFlags_Move));
     }
     
+    connection_->SetProgressListener(this);
     connection_->Move(targetAet_, findAnswer);
   }
 
--- a/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp	Thu Dec 05 12:02:33 2024 +0100
+++ b/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp	Fri Dec 06 12:06:05 2024 +0100
@@ -160,7 +160,11 @@
     context_(context),
     parameters_(DicomAssociationParameters::UnserializeJob(serialized)),
     query_(true),
-    queryFormat_(DicomToJsonFormat_Short)
+    queryFormat_(DicomToJsonFormat_Short),
+    nbRemainingSubOperations_(0),
+    nbCompletedSubOperations_(0),
+    nbFailedSubOperations_(0),
+    nbWarningSubOperations_(0)  
   {
     if (serialized.isMember(QUERY))
     {
@@ -202,4 +206,26 @@
       return true;
     }
   }
+
+  void DicomRetrieveScuBaseJob::OnProgressUpdated(uint16_t nbRemainingSubOperations,
+                                                  uint16_t nbCompletedSubOperations,
+                                                  uint16_t nbFailedSubOperations,
+                                                  uint16_t nbWarningSubOperations)
+  {
+    nbRemainingSubOperations_ = nbRemainingSubOperations;
+    nbCompletedSubOperations_ = nbCompletedSubOperations;
+    nbFailedSubOperations_ = nbFailedSubOperations;
+    nbWarningSubOperations_ = nbWarningSubOperations;
+  }
+
+  float DicomRetrieveScuBaseJob::GetProgress()
+  {
+    uint32_t totalOperations = nbRemainingSubOperations_ + nbCompletedSubOperations_ + nbFailedSubOperations_ + nbWarningSubOperations_;
+    if (totalOperations == 0)
+    {
+      return 0.0f;
+    }
+
+    return float(nbCompletedSubOperations_ + nbFailedSubOperations_ + nbWarningSubOperations_) / float(totalOperations);
+  }
 }
--- a/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.h	Thu Dec 05 12:02:33 2024 +0100
+++ b/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.h	Fri Dec 06 12:06:05 2024 +0100
@@ -32,7 +32,7 @@
 {
   class ServerContext;
 
-  class DicomRetrieveScuBaseJob : public SetOfCommandsJob
+  class DicomRetrieveScuBaseJob : public SetOfCommandsJob, public DicomControlUserConnection::IProgressListener
   {
   protected:
     class Command : public SetOfCommandsJob::ICommand
@@ -87,6 +87,11 @@
 
     std::unique_ptr<DicomControlUserConnection> connection_;
 
+    uint16_t nbRemainingSubOperations_;
+    uint16_t nbCompletedSubOperations_;
+    uint16_t nbFailedSubOperations_;
+    uint16_t nbWarningSubOperations_;
+
     virtual void Retrieve(const DicomMap &findAnswer) = 0;
 
     explicit DicomRetrieveScuBaseJob(ServerContext &context) : 
@@ -130,5 +135,12 @@
     virtual void GetPublicContent(Json::Value &value) ORTHANC_OVERRIDE;
 
     virtual bool Serialize(Json::Value &target) ORTHANC_OVERRIDE;
+
+    virtual void OnProgressUpdated(uint16_t nbRemainingSubOperations,
+                                    uint16_t nbCompletedSubOperations,
+                                    uint16_t nbFailedSubOperations,
+                                    uint16_t nbWarningSubOperations) ORTHANC_OVERRIDE;
+
+    virtual float GetProgress() ORTHANC_OVERRIDE;
   };
 }