changeset 679:280980f6dda3

integration find-refactoring->mainline
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 16 Dec 2024 16:31:37 +0100
parents a5241defb36f (current diff) 9e43494cb054 (diff)
children bdcea1ec1683
files
diffstat 8 files changed, 136 insertions(+), 38 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Wed Oct 09 16:04:41 2024 +0200
+++ b/NEWS	Mon Dec 16 16:31:37 2024 +0100
@@ -1,12 +1,14 @@
 Pending changes in the mainline
 ===============================
 
-* Added a "Server" entry in the DICOMWeb job content.
-* 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.
+* Added a "Server" entry in the DICOMweb job content.
+* Fixed parsing of numerical values in QIDO-RS response that prevented, among 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 improves the download time of multi-frame images in OHIF.
+* Optimization when running with an Orthanc that supports the "ExtendedFind" primitive.
+* Added support for Orthanc running in "ReadOnly" mode.
 
 
 Version 1.17 (2024-06-05)
--- a/Plugin/Configuration.cpp	Wed Oct 09 16:04:41 2024 +0200
+++ b/Plugin/Configuration.cpp	Mon Dec 16 16:31:37 2024 +0100
@@ -278,6 +278,8 @@
         Orthanc::ResourceType level = Orthanc::StringToResourceType(levels[i].c_str());
 
         const Json::Value& content = configuration[EXTRA_MAIN_DICOM_TAGS][levels[i]];
+        std::set<Orthanc::DicomTag> defaultTags;
+        Orthanc::DicomMap::GetMainDicomTags(defaultTags, level);
 
         if (content.size() > 0)
         {
@@ -286,7 +288,10 @@
             const std::string& tagName = content[t].asString();
             Orthanc::DicomTag tag(0, 0);
             OrthancPlugins::ParseTag(tag, tagName);
-            Orthanc::DicomMap::AddMainDicomTag(tag, level);
+            if (defaultTags.find(tag) == defaultTags.end()) // don't generate an error if the ExtraMainDicomTags is now a default one
+            {
+              Orthanc::DicomMap::AddMainDicomTag(tag, level);
+            }
           }
         }
       }
@@ -682,6 +687,10 @@
       return GetBooleanValue("EnableMetadataCache", true);
     }
 
+    bool IsReadOnly()
+    {
+      return globalConfiguration_->GetBooleanValue("ReadOnly", false);
+    }
     
     MetadataMode GetMetadataMode(Orthanc::ResourceType level)
     {
--- a/Plugin/Configuration.h	Wed Oct 09 16:04:41 2024 +0200
+++ b/Plugin/Configuration.h	Mon Dec 16 16:31:37 2024 +0100
@@ -142,5 +142,7 @@
     unsigned int GetMetadataWorkerThreadsCount();
 
     bool IsMetadataCacheEnabled();
+
+    bool IsReadOnly();
   }
 }
--- a/Plugin/Plugin.cpp	Wed Oct 09 16:04:41 2024 +0200
+++ b/Plugin/Plugin.cpp	Mon Dec 16 16:31:37 2024 +0100
@@ -42,7 +42,10 @@
 #define ORTHANC_CORE_MINIMAL_REVISION  0
 
 static const char* const HAS_DELETE = "HasDelete";
-
+static const char* const SYSTEM_CAPABILITIES = "Capabilities";
+static const char* const SYSTEM_CAPABILITIES_HAS_EXTENDED_FIND = "HasExtendedFind";
+static const char* const READ_ONLY = "ReadOnly";
+bool isReadOnly_ = false;
 
 
 bool RequestHasKey(const OrthancPluginHttpRequest* request, const char* key)
@@ -471,11 +474,41 @@
     switch (changeType)
     {
       case OrthancPluginChangeType_OrthancStarted:
+      {
         OrthancPlugins::Configuration::LoadDicomWebServers();
-        break;
+
+        Json::Value system;
+        if (OrthancPlugins::RestApiGet(system, "/system", false))
+        {
+          bool hasExtendedFind = system.isMember(SYSTEM_CAPABILITIES) 
+                                      && system[SYSTEM_CAPABILITIES].isMember(SYSTEM_CAPABILITIES_HAS_EXTENDED_FIND)
+                                      && system[SYSTEM_CAPABILITIES][SYSTEM_CAPABILITIES_HAS_EXTENDED_FIND].asBool();
+          if (hasExtendedFind)
+          {
+            LOG(INFO) << "Orthanc supports ExtendedFind.";
+            SetPluginCanUseExtendedFile(true);
+          }
+          else
+          {
+            LOG(WARNING) << "Orthanc does not support ExtendedFind.  The DICOMWeb plugin will not benefit from some optimizations.";
+          }
+
+          bool isReadOnly = system.isMember(READ_ONLY) && system[READ_ONLY].asBool();
+
+          if (isReadOnly)
+          {
+            LOG(INFO) << "Orthanc is ReadOnly.";
+            SetSystemIsReadOnly(true);
+          }
+        }
+
+      }; break;
 
       case OrthancPluginChangeType_StableSeries:
-        CacheSeriesMetadata(resourceId);
+        if (!OrthancPlugins::Configuration::IsReadOnly())
+        {
+          CacheSeriesMetadata(resourceId);
+        }
         break;
 
       default:
@@ -568,13 +601,27 @@
 
         LOG(WARNING) << "URI to the DICOMweb REST API: " << root;
 
-        OrthancPlugins::ChunkedRestRegistration<
-          SearchForStudies /* TODO => Rename as QIDO-RS */,
-          OrthancPlugins::StowServer::PostCallback>::Apply(root + "studies");
+
+        if (!OrthancPlugins::Configuration::IsReadOnly())
+        {
+          OrthancPlugins::ChunkedRestRegistration<
+            SearchForStudies /* TODO => Rename as QIDO-RS */,
+            OrthancPlugins::StowServer::PostCallback>::Apply(root + "studies");
 
-        OrthancPlugins::ChunkedRestRegistration<
-          RetrieveDicomStudy /* TODO => Rename as WADO-RS */,
-          OrthancPlugins::StowServer::PostCallback>::Apply(root + "studies/([^/]*)");
+          OrthancPlugins::ChunkedRestRegistration<
+            RetrieveDicomStudy /* TODO => Rename as WADO-RS */,
+            OrthancPlugins::StowServer::PostCallback>::Apply(root + "studies/([^/]*)");
+        }
+        else
+        {
+          LOG(WARNING) << "READ-ONLY SYSTEM: deactivating STOW-RS routes";
+          
+          OrthancPlugins::ChunkedRestRegistration<
+            SearchForStudies /* TODO => Rename as QIDO-RS */>::Apply(root + "studies");
+
+          OrthancPlugins::ChunkedRestRegistration<
+            RetrieveDicomStudy /* TODO => Rename as WADO-RS */>::Apply(root + "studies/([^/]*)");
+        }
 
         OrthancPlugins::RegisterRestCallback<SearchForInstances>(root + "instances", true);
         OrthancPlugins::RegisterRestCallback<SearchForSeries>(root + "series", true);    
@@ -592,10 +639,19 @@
 
         OrthancPlugins::RegisterRestCallback<ListServers>(root + "servers", true);
         OrthancPlugins::RegisterRestCallback<ListServerOperations>(root + "servers/([^/]*)", true);
-        OrthancPlugins::RegisterRestCallback<StowClient>(root + "servers/([^/]*)/stow", true);
+        
+        if (!OrthancPlugins::Configuration::IsReadOnly())
+        {
+          OrthancPlugins::RegisterRestCallback<RetrieveFromServer>(root + "servers/([^/]*)/retrieve", true);
+        }
+        else
+        {
+          LOG(WARNING) << "READ-ONLY SYSTEM: deactivating 'servers/../retrieve' route";
+        }
+
         OrthancPlugins::RegisterRestCallback<WadoRetrieveClient>(root + "servers/([^/]*)/wado", true);
+        OrthancPlugins::RegisterRestCallback<StowClient>(root + "servers/([^/]*)/stow", true);
         OrthancPlugins::RegisterRestCallback<GetFromServer>(root + "servers/([^/]*)/get", true);
-        OrthancPlugins::RegisterRestCallback<RetrieveFromServer>(root + "servers/([^/]*)/retrieve", true);
         OrthancPlugins::RegisterRestCallback<QidoClient>(root + "servers/([^/]*)/qido", true);
         OrthancPlugins::RegisterRestCallback<DeleteClient>(root + "servers/([^/]*)/delete", true);
 
@@ -610,7 +666,10 @@
         OrthancPlugins::RegisterRestCallback<RetrieveInstanceRendered>(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/rendered", true);
         OrthancPlugins::RegisterRestCallback<RetrieveFrameRendered>(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/frames/([^/]*)/rendered", true);
 
-        OrthancPlugins::RegisterRestCallback<UpdateSeriesMetadataCache>("/studies/([^/]*)/update-dicomweb-cache", true);
+        if (!OrthancPlugins::Configuration::IsReadOnly())
+        {
+          OrthancPlugins::RegisterRestCallback<UpdateSeriesMetadataCache>("/studies/([^/]*)/update-dicomweb-cache", true);
+        }
 
         OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback);
 
--- a/Plugin/WadoRs.cpp	Wed Oct 09 16:04:41 2024 +0200
+++ b/Plugin/WadoRs.cpp	Mon Dec 16 16:31:37 2024 +0100
@@ -50,6 +50,28 @@
 static std::string instancesMainDicomTagsList;
 static boost::mutex mainDicomTagsListMutex;
 
+static bool pluginCanUseExtendedFind_ = false;
+static bool isSystemReadOnly_ = false;
+
+void SetPluginCanUseExtendedFile(bool enable)
+{
+  pluginCanUseExtendedFind_ = enable;
+}
+
+bool CanUseExtendedFile()
+{
+  return pluginCanUseExtendedFind_;
+}
+
+void SetSystemIsReadOnly(bool isReadOnly)
+{
+  isSystemReadOnly_ = isReadOnly;
+}
+
+bool IsSystemReadOnly()
+{
+  return isSystemReadOnly_;
+}
 
 static std::string GetResourceUri(Orthanc::ResourceType level,
                                   const std::string& publicId)
@@ -1348,15 +1370,13 @@
                                     const std::string& wadoBase)
 {
   const unsigned int workersCount =  OrthancPlugins::Configuration::GetMetadataWorkerThreadsCount();
-  const bool oneLargeQuery = false;  // we keep this code here for future use once we'll have optimized Orthanc API /series/.../instances?full to minimize the SQL queries
-                                // right now, it is faster to call /instances/..?full in each worker but, later, it should be more efficient with a large SQL query in Orthanc
 
   if (workersCount > 1 && mode == OrthancPlugins::MetadataMode_Full)
   {
     ChildrenMainDicomMaps instancesDicomMaps;
     std::string seriesDicomUid;
 
-    if (oneLargeQuery)
+    if (CanUseExtendedFile()) // in this case, /series/.../instances?full has been optimized to minimize the SQL queries
     {
       GetChildrenMainDicomTags(instancesDicomMaps, seriesDicomUid, Orthanc::ResourceType_Series, seriesOrthancId);
       for (ChildrenMainDicomMaps::const_iterator it = instancesDicomMaps.begin(); it != instancesDicomMaps.end(); ++it)
@@ -1382,7 +1402,7 @@
       instancesWorkers.push_back(boost::shared_ptr<boost::thread>(new boost::thread(InstanceWorkerThread, threadData)));
     }
 
-    if (oneLargeQuery)
+    if (CanUseExtendedFile())  // we must correct the bulkRoot
     {
       for (ChildrenMainDicomMaps::const_iterator i = instancesDicomMaps.begin(); i != instancesDicomMaps.end(); ++i)
       {
@@ -1448,20 +1468,22 @@
   RetrieveSeriesMetadataInternal(instancesIds, writer, cache, OrthancPlugins::MetadataMode_Full, false /* isXml */, seriesOrthancId, studyInstanceUid, seriesInstanceUid, WADO_BASE_PLACEHOLDER);
   writer.CloseAndGetJsonOutput(serializedSeriesMetadata);
 
-  // save in attachments for future use
-  Orthanc::IBufferCompressor::Compress(compressedSeriesMetadata, compressor, serializedSeriesMetadata);
-  std::string instancesMd5;
-  Orthanc::Toolbox::ComputeMD5(instancesMd5, instancesIds);
-
-  std::string cacheContent = "2;" + instancesMd5 + ";" + compressedSeriesMetadata; 
+  if (!IsSystemReadOnly())
+  {
+    // save in attachments for future use
+    Orthanc::IBufferCompressor::Compress(compressedSeriesMetadata, compressor, serializedSeriesMetadata);
+    std::string instancesMd5;
+    Orthanc::Toolbox::ComputeMD5(instancesMd5, instancesIds);
 
-  Json::Value putResult;
-  std::string attachmentUrl = "/series/" + seriesOrthancId + "/attachments/" + SERIES_METADATA_ATTACHMENT_ID;
-  if (!OrthancPlugins::RestApiPut(putResult, attachmentUrl, cacheContent, false))
-  {
-    LOG(WARNING) << "DicomWEB: failed to write series metadata attachment";
+    std::string cacheContent = "2;" + instancesMd5 + ";" + compressedSeriesMetadata; 
+
+    Json::Value putResult;
+    std::string attachmentUrl = "/series/" + seriesOrthancId + "/attachments/" + SERIES_METADATA_ATTACHMENT_ID;
+    if (!OrthancPlugins::RestApiPut(putResult, attachmentUrl, cacheContent, false))
+    {
+      LOG(WARNING) << "DicomWEB: failed to write series metadata attachment";
+    }
   }
-
 }
 
 void CacheSeriesMetadata(const std::string& seriesOrthancId)
--- a/Plugin/WadoRs.h	Wed Oct 09 16:04:41 2024 +0200
+++ b/Plugin/WadoRs.h	Mon Dec 16 16:31:37 2024 +0100
@@ -102,4 +102,8 @@
                            const char* url,
                            const OrthancPluginHttpRequest* request);
 
-void SetPluginCanDownloadTranscodedFile(bool enable);
\ No newline at end of file
+void SetPluginCanDownloadTranscodedFile(bool enable);
+
+void SetPluginCanUseExtendedFile(bool enable);
+
+void SetSystemIsReadOnly(bool isReadOnly);
\ No newline at end of file
--- a/Plugin/WadoRsRetrieveFrames.cpp	Wed Oct 09 16:04:41 2024 +0200
+++ b/Plugin/WadoRsRetrieveFrames.cpp	Mon Dec 16 16:31:37 2024 +0100
@@ -443,7 +443,7 @@
                         Orthanc::DicomTransferSyntax outputSyntax)
 {
   if (OrthancPluginStartMultipartAnswer(
-        OrthancPlugins::GetGlobalContext(), 
+        OrthancPlugins::GetGlobalContext(),
         output, "related", GetMimeType(outputSyntax)) != OrthancPluginErrorCode_Success)
   {
     throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin,
@@ -466,7 +466,7 @@
 
   if (error != OrthancPluginErrorCode_Success)
   {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);      
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
   }
 }
 
@@ -552,7 +552,7 @@
                   sopInstanceUid, frames.front(), targetSyntax);
       return;
     }
-    else 
+    else
     {
       if (!content.RestApiGet("/instances/" + orthancId + "/file", false))
       {
--- a/TODO	Wed Oct 09 16:04:41 2024 +0200
+++ b/TODO	Mon Dec 16 16:31:37 2024 +0100
@@ -10,7 +10,7 @@
   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
+  Note: This has been partially handled in 1.18: Tf no transcoding is needed, we avoid downloading 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.