changeset 5426:c65e036d649b

StorageCache is now storing transcoded instances + added ?transcode=... option to the /file route.
author Alain Mazy <am@osimis.io>
date Thu, 16 Nov 2023 16:09:04 +0100
parents e2c9f9d9700e
children 111e21b4f8bc
files NEWS OrthancFramework/Sources/Cache/MemoryStringCache.cpp OrthancFramework/Sources/Cache/MemoryStringCache.h OrthancFramework/Sources/Enumerations.cpp OrthancFramework/Sources/Enumerations.h OrthancFramework/Sources/FileStorage/StorageCache.cpp OrthancFramework/Sources/FileStorage/StorageCache.h OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp OrthancServer/Sources/ServerContext.cpp OrthancServer/Sources/ServerContext.h
diffstat 11 files changed, 158 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Wed Nov 15 08:58:45 2023 +0100
+++ b/NEWS	Thu Nov 16 16:09:04 2023 +0100
@@ -10,8 +10,10 @@
     This can bring some significant improvements particularly in viewers.
   - Optimized the StorageCache to prevent loading the same file multiple times if
     multiple users request the same file at the same time.
+  - The StorageCache is now also storing transcoded instances that have been requested by /file?transcode=...
+    that is now used by the DICOMweb plugin.  This speeds up retrieval of transcoded frames through WADO-RS.
 * Housekeeper plugin:
-  - Update to rebuild the cache of the DicomWeb plugin when updating to DicomWeb 1.15.
+  - Update to rebuild the cache of the DICOMweb plugin when updating to DICOMweb 1.15.
   - New trigger configuration: "DicomWebCacheChange"
   - Fixed reading the triggers configuration.
 * HTTP Compression:
@@ -37,6 +39,8 @@
 
 * API version upgraded to 22
 * Added a route to delete completed jobs from history: DELETE /jobs/{id}
+* added a "transcode" option to the /file route:
+  e.g: /instances/../file?transcode=1.2.840.10008.1.2.4.80
 * All 'expand' GET arguments now accepts expand=true and expand=false values.
   The /studies/../instances and sibling routes are the only whose expand is true if not specified.
   These routes now accepts expand=false to simply list the child resources ids.
--- a/OrthancFramework/Sources/Cache/MemoryStringCache.cpp	Wed Nov 15 08:58:45 2023 +0100
+++ b/OrthancFramework/Sources/Cache/MemoryStringCache.cpp	Thu Nov 16 16:09:04 2023 +0100
@@ -206,6 +206,24 @@
   }
 
 
+  void MemoryStringCache::InvalidateByPrefix(const std::string& keyPrefix)
+  {
+    std::vector<std::string> allKeys;
+
+    {
+      boost::mutex::scoped_lock cacheLock(cacheMutex_);
+      content_.GetAllKeys(allKeys);
+    }
+
+    for (std::vector<std::string>::const_iterator it = allKeys.begin(); it != allKeys.end(); ++it)
+    {
+      if (it->find(keyPrefix) == 0)
+      {
+        Invalidate(*it);
+      }
+    }
+  }
+
   bool MemoryStringCache::Fetch(std::string& value,
                                 const std::string& key)
   {
--- a/OrthancFramework/Sources/Cache/MemoryStringCache.h	Wed Nov 15 08:58:45 2023 +0100
+++ b/OrthancFramework/Sources/Cache/MemoryStringCache.h	Thu Nov 16 16:09:04 2023 +0100
@@ -86,6 +86,7 @@
 
     void Invalidate(const std::string& key);
 
+    void InvalidateByPrefix(const std::string& keyPrefix);
 
   private:
     void Add(const std::string& key,
--- a/OrthancFramework/Sources/Enumerations.cpp	Wed Nov 15 08:58:45 2023 +0100
+++ b/OrthancFramework/Sources/Enumerations.cpp	Thu Nov 16 16:09:04 2023 +0100
@@ -2468,6 +2468,21 @@
       throw OrthancException(ErrorCode_InternalError);
     }
   }
+
+  DicomTransferSyntax GetTransferSyntax(const std::string& value)
+  {
+    DicomTransferSyntax syntax;
+    if (LookupTransferSyntax(syntax, value))
+    {
+      return syntax;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange,
+                             "Unknown transfer syntax: " + value);
+    }
+  }
+
 }
 
 
--- a/OrthancFramework/Sources/Enumerations.h	Wed Nov 15 08:58:45 2023 +0100
+++ b/OrthancFramework/Sources/Enumerations.h	Thu Nov 16 16:09:04 2023 +0100
@@ -935,6 +935,9 @@
                             const std::string& uid);
 
   ORTHANC_PUBLIC
+  DicomTransferSyntax GetTransferSyntax(const std::string& uid);
+
+  ORTHANC_PUBLIC
   const char* GetResourceTypeText(ResourceType type,
                                   bool isPlural,
                                   bool isUpperCase);
--- a/OrthancFramework/Sources/FileStorage/StorageCache.cpp	Wed Nov 15 08:58:45 2023 +0100
+++ b/OrthancFramework/Sources/FileStorage/StorageCache.cpp	Thu Nov 16 16:09:04 2023 +0100
@@ -38,13 +38,22 @@
   {
     return uuid + ":" + boost::lexical_cast<std::string>(contentType) + ":1";
   }
-  
+
+
   static std::string GetCacheKeyStartRange(const std::string& uuid,
                                            FileContentType contentType)
   {
     return uuid + ":" + boost::lexical_cast<std::string>(contentType) + ":0";
   }
 
+
+  static std::string GetCacheKeyTranscodedInstance(const std::string& uuid,
+                                                   DicomTransferSyntax transferSyntax)
+  {
+    return uuid + ":" + GetTransferSyntaxUid(transferSyntax) + ":1";
+  }
+
+
   void StorageCache::SetMaximumSize(size_t size)
   {
     cache_.SetMaximumSize(size);
@@ -54,12 +63,8 @@
   void StorageCache::Invalidate(const std::string& uuid,
                                 FileContentType contentType)
   {
-    // invalidate both full file + start range file
-    const std::string keyFullFile = GetCacheKeyFullFile(uuid, contentType);
-    cache_.Invalidate(keyFullFile);
-
-    const std::string keyPartialFile = GetCacheKeyStartRange(uuid, contentType);
-    cache_.Invalidate(keyPartialFile);
+    // invalidate full file, start range file and possible transcoded instances
+    cache_.InvalidateByPrefix(uuid);
   }
 
 
@@ -111,6 +116,32 @@
     }
   }
 
+  bool StorageCache::Accessor::FetchTranscodedInstance(std::string& value, 
+                                                       const std::string& uuid,
+                                                       DicomTransferSyntax targetSyntax)
+  {
+    const std::string key = GetCacheKeyTranscodedInstance(uuid, targetSyntax);
+    if (MemoryStringCache::Accessor::Fetch(value, key))
+    {
+      LOG(INFO) << "Read instance \"" << uuid << "\" transcoded to "
+                << GetTransferSyntaxUid(targetSyntax) << " from cache";
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  void StorageCache::Accessor::AddTranscodedInstance(const std::string& uuid,
+                                                     DicomTransferSyntax targetSyntax,
+                                                     const void* buffer,
+                                                     size_t size)
+  {
+    const std::string key = GetCacheKeyTranscodedInstance(uuid, targetSyntax);
+    MemoryStringCache::Accessor::Add(key, reinterpret_cast<const char*>(buffer), size);
+  }
+
   bool StorageCache::Accessor::FetchStartRange(std::string& value, 
                                                const std::string& uuid,
                                                FileContentType contentType,
--- a/OrthancFramework/Sources/FileStorage/StorageCache.h	Wed Nov 15 08:58:45 2023 +0100
+++ b/OrthancFramework/Sources/FileStorage/StorageCache.h	Thu Nov 16 16:09:04 2023 +0100
@@ -50,26 +50,35 @@
         Accessor(StorageCache& cache);
 
         void Add(const std::string& uuid, 
-                FileContentType contentType,
-                const std::string& value);
+                 FileContentType contentType,
+                 const std::string& value);
 
         void AddStartRange(const std::string& uuid, 
-                          FileContentType contentType,
-                          const std::string& value);
+                           FileContentType contentType,
+                           const std::string& value);
 
         void Add(const std::string& uuid, 
-                FileContentType contentType,
-                const void* buffer,
-                size_t size);
+                 FileContentType contentType,
+                 const void* buffer,
+                 size_t size);
 
         bool Fetch(std::string& value, 
-                  const std::string& uuid,
-                  FileContentType contentType);
+                   const std::string& uuid,
+                   FileContentType contentType);
 
         bool FetchStartRange(std::string& value, 
-                            const std::string& uuid,
-                            FileContentType contentType,
-                            uint64_t end /* exclusive */);
+                             const std::string& uuid,
+                             FileContentType contentType,
+                             uint64_t end /* exclusive */);
+
+        bool FetchTranscodedInstance(std::string& value, 
+                                     const std::string& uuid,
+                                     DicomTransferSyntax targetSyntax);
+
+        void AddTranscodedInstance(const std::string& uuid,
+                                   DicomTransferSyntax targetSyntax,
+                                   const void* buffer,
+                                   size_t size);
       };
 
     private:
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp	Wed Nov 15 08:58:45 2023 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp	Thu Nov 16 16:09:04 2023 +0100
@@ -93,21 +93,6 @@
     }
   }
 
-
-  static DicomTransferSyntax GetTransferSyntax(const std::string& value)
-  {
-    DicomTransferSyntax syntax;
-    if (LookupTransferSyntax(syntax, value))
-    {
-      return syntax;
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange,
-                             "Unknown transfer syntax: " + value);
-    }
-  }
-  
   
   static void GetJobParameters(bool& synchronous,            /* out */
                                bool& extended,               /* out */
@@ -137,7 +122,7 @@
         body.isMember(KEY_TRANSCODE))
     {
       transcode = true;
-      syntax = GetTransferSyntax(SerializationToolbox::ReadString(body, KEY_TRANSCODE));
+      syntax = Orthanc::GetTransferSyntax(SerializationToolbox::ReadString(body, KEY_TRANSCODE));
     }
     else
     {
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Wed Nov 15 08:58:45 2023 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Thu Nov 16 16:09:04 2023 +0100
@@ -361,6 +361,8 @@
  
   static void GetInstanceFile(RestApiGetCall& call)
   {
+    static const char* const TRANSCODE = "transcode";
+
     if (call.IsDocumentation())
     {
       call.GetDocumentation()
@@ -369,6 +371,9 @@
         .SetDescription("Download one DICOM instance")
         .SetUriArgument("id", "Orthanc identifier of the DICOM instance of interest")
         .SetHttpHeader("Accept", "This HTTP header can be set to retrieve the DICOM instance in DICOMweb format")
+        .SetHttpGetArgument(TRANSCODE, RestApiCallDocumentation::Type_String,
+                            "If present, the DICOM file will be transcoded to the provided "
+                            "transfer syntax: https://book.orthanc-server.com/faq/transcoding.html", false)
         .AddAnswerType(MimeType_Dicom, "The DICOM instance")
         .AddAnswerType(MimeType_DicomWebJson, "The DICOM instance, in DICOMweb JSON format")
         .AddAnswerType(MimeType_DicomWebXml, "The DICOM instance, in DICOMweb XML format");
@@ -417,7 +422,22 @@
       }
     }
 
-    context.AnswerAttachment(call.GetOutput(), publicId, FileContentType_Dicom);
+    if (call.HasArgument(TRANSCODE))
+    {
+      std::string source;
+      std::string transcoded;
+      context.ReadDicom(source, publicId);
+
+      if (context.TranscodeWithCache(transcoded, source, publicId, GetTransferSyntax(call.GetArgument(TRANSCODE, ""))))
+      {
+        call.GetOutput().AnswerBuffer(transcoded, MimeType_Dicom);
+      }
+    }
+    else
+    {
+      // return the attachment without any transcoding
+      context.AnswerAttachment(call.GetOutput(), publicId, FileContentType_Dicom);
+    }
   }
 
 
--- a/OrthancServer/Sources/ServerContext.cpp	Wed Nov 15 08:58:45 2023 +0100
+++ b/OrthancServer/Sources/ServerContext.cpp	Thu Nov 16 16:09:04 2023 +0100
@@ -1948,6 +1948,36 @@
   }
 
 
+  bool ServerContext::TranscodeWithCache(std::string& target,
+                                         const std::string& source,
+                                         const std::string& sourceInstanceId,
+                                         DicomTransferSyntax targetSyntax)
+  {
+    StorageCache::Accessor cacheAccessor(storageCache_);
+
+    if (!cacheAccessor.FetchTranscodedInstance(target, sourceInstanceId, targetSyntax))
+    {
+      IDicomTranscoder::DicomImage sourceDicom;
+      sourceDicom.SetExternalBuffer(source);
+
+      IDicomTranscoder::DicomImage targetDicom;
+      std::set<DicomTransferSyntax> syntaxes;
+      syntaxes.insert(targetSyntax);
+
+      if (Transcode(targetDicom, sourceDicom, syntaxes, true))
+      {
+        cacheAccessor.AddTranscodedInstance(sourceInstanceId, targetSyntax, reinterpret_cast<const char*>(targetDicom.GetBufferData()), targetDicom.GetBufferSize());
+        target = std::string(reinterpret_cast<const char*>(targetDicom.GetBufferData()), targetDicom.GetBufferSize());
+        return true;
+      }
+
+      return false;
+    }
+
+    return true;
+  }
+
+
   bool ServerContext::Transcode(DicomImage& target,
                                 DicomImage& source /* in, "GetParsed()" possibly modified */,
                                 const std::set<DicomTransferSyntax>& allowedSyntaxes,
--- a/OrthancServer/Sources/ServerContext.h	Wed Nov 15 08:58:45 2023 +0100
+++ b/OrthancServer/Sources/ServerContext.h	Thu Nov 16 16:09:04 2023 +0100
@@ -556,6 +556,11 @@
                            const std::set<DicomTransferSyntax>& allowedSyntaxes,
                            bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
 
+    virtual bool TranscodeWithCache(std::string& target,
+                                    const std::string& source,
+                                    const std::string& sourceInstanceId,
+                                    DicomTransferSyntax targetSyntax);
+
     bool IsTranscodeDicomProtocol() const
     {
       return transcodeDicomProtocol_;