changeset 676:6ae0e07512b8

Optimized the retrieval of single frame in WADO-RS when no transcoding is required
author Alain Mazy <am@orthanc.team>
date Wed, 09 Oct 2024 15:52:24 +0200 (7 months ago)
parents c302094b719e
children a5241defb36f
files NEWS Plugin/WadoRsRetrieveFrames.cpp TODO
diffstat 3 files changed, 59 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Wed Sep 04 12:56:30 2024 +0200
+++ b/NEWS	Wed Oct 09 15:52:24 2024 +0200
@@ -5,6 +5,8 @@
 * Fixed parsing of numerical values in QIDO-RS response that prevented, amongst other,
   the retrieval of NumberOfStudyRelatedInstances, NumberOfStudyRelatedSeries, ...
 * Fixed non latin PatientName values that were empty in some QIDO-RS responses.
+* Optimized the retrieval of single frame in WADO-RS when no transcoding is required.
+  This greatly improve download time of multi-frame images in OHIF.
 
 
 Version 1.17 (2024-06-05)
--- a/Plugin/WadoRsRetrieveFrames.cpp	Wed Sep 04 12:56:30 2024 +0200
+++ b/Plugin/WadoRsRetrieveFrames.cpp	Wed Oct 09 15:52:24 2024 +0200
@@ -433,6 +433,43 @@
 }
 
 
+static void AnswerFrame(OrthancPluginRestOutput* output,
+                        const OrthancPluginHttpRequest* request,
+                        const OrthancPlugins::MemoryBuffer& instanceContent,
+                        const std::string& studyInstanceUid,
+                        const std::string& seriesInstanceUid,
+                        const std::string& sopInstanceUid,
+                        unsigned int frame,
+                        Orthanc::DicomTransferSyntax outputSyntax)
+{
+  if (OrthancPluginStartMultipartAnswer(
+        OrthancPlugins::GetGlobalContext(), 
+        output, "related", GetMimeType(outputSyntax)) != OrthancPluginErrorCode_Success)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin,
+                                    "Cannot start a multipart answer");
+  }
+
+  OrthancPluginErrorCode error;
+
+#if HAS_SEND_MULTIPART_ITEM_2 == 1
+  const std::string base = OrthancPlugins::Configuration::GetBasePublicUrl(request);
+  std::string location = (
+    OrthancPlugins::Configuration::GetWadoUrl(base, studyInstanceUid, seriesInstanceUid, sopInstanceUid) +
+    "frames/" + boost::lexical_cast<std::string>(frame));
+  const char *keys[] = { "Content-Location" };
+  const char *values[] = { location.c_str() };
+  error = OrthancPluginSendMultipartItem2(OrthancPlugins::GetGlobalContext(), output, instanceContent.GetData(), instanceContent.GetSize(), 1, keys, values);
+#else
+  error = OrthancPluginSendMultipartItem(OrthancPlugins::GetGlobalContext(), instanceContent.GetData(), instanceContent.GetSize(), size);
+#endif
+
+  if (error != OrthancPluginErrorCode_Success)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);      
+  }
+}
+
 static void RetrieveFrames(OrthancPluginRestOutput* output,
                            const OrthancPluginHttpRequest* request,
                            bool allFrames,
@@ -499,9 +536,25 @@
       {
         throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "DICOMWeb: Unable to get transcoded file for instance " + orthancId);
       }
+
+      // TODO-OPTI: this takes a huge amount of time; e.g: 1.5s for a 600MB file while the DicomInstance usually already exists in the Orthanc core
+      //            call /instances/../frames/../transcoded (to be implemented in future Orthanc release)
       instance.reset(new OrthancPlugins::DicomInstance(content.GetData(), content.GetSize()));
     }
-    else // pre 1.12.2 code (or no transcoding needed)
+    else if (!allFrames && frames.size() == 1 && !transcodeThisInstance) // no transcoding needed, let's retrieve the raw frame directly from the core to avoid Orthanc to recreate a DicomInstance for every frame
+    {
+      if (!content.RestApiGet("/instances/" + orthancId + "/frames/" + boost::lexical_cast<std::string>(frames.front()) + "/raw", false))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "DICOMWeb: Unable to get file for instance " + orthancId);
+      }
+
+      LOG(INFO) << "DICOMweb RetrieveFrames, before AnswerFrame";
+      AnswerFrame(output, request, content, studyInstanceUid, seriesInstanceUid,
+                  sopInstanceUid, frames.front(), targetSyntax);
+      LOG(INFO) << "DICOMweb RetrieveFrame, leaving";
+      return;
+    }
+    else 
     {
       if (!content.RestApiGet("/instances/" + orthancId + "/file", false))
       {
--- a/TODO	Wed Sep 04 12:56:30 2024 +0200
+++ b/TODO	Wed Oct 09 15:52:24 2024 +0200
@@ -10,6 +10,8 @@
   curl -H "Accept: multipart/related; type=application/octet-stream" http://localhost:8043/dicom-web/studies/1.2.156.112536.1.2143.25015081191207.14610300430.5/series/1.2.156.112536.1.2143.25015081191207.14610300430.6/instances/1.2.156.112536.1.2143.25015081191207.14610309990.44/frames/3 --output /tmp/out.bin
   check for these logs: DICOMweb RetrieveFrames: Transcoding instance a7aec17a-e296e51f-2abe8ad8-bc95d57b-4de269d0 to transfer syntax 1.2.840.10008.1.2.1
 
+  Note: this has been partialy handled in 1.18: if no transcoding is needed, we avoid download the full instance from Orthanc
+
   We should very likely implement a cache in the DicomWEB plugin and make sure that, if 3 clients are requesting the same instance at the same time, we only
   request one transcoding.
 
@@ -18,6 +20,7 @@
   https://discourse.orthanc-server.org/t/possible-memory-leak-with-multiframe-dicom-orthanc-ohif/3988/12
 
 
+
 * Implement capabilities: https://www.dicomstandard.org/using/dicomweb/capabilities/
   from https://groups.google.com/d/msgid/orthanc-users/c60227f2-c6da-4fd9-9b03-3ce9bf7d1af5n%40googlegroups.com?utm_medium=email&utm_source=footer