changeset 4903:260377d84e9f proto-filter-instance-returning-error-code

integration mainline->proto-filter-instance-returning-error-code
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sun, 20 Feb 2022 17:04:41 +0100
parents 3ab57510f6dd (current diff) df86d2505df8 (diff)
children
files
diffstat 18 files changed, 122 insertions(+), 184 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake	Sun Feb 20 12:59:30 2022 +0100
+++ b/OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake	Sun Feb 20 17:04:41 2022 +0100
@@ -163,7 +163,7 @@
         # For "Toolbox::ReadJson()" and "Toolbox::Write{...}Json()" (pre-1.9.0)
         set(ORTHANC_FRAMEWORK_MD5 "9af92080e57c60dd288eba46ce606c00")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "b2e08d83e21d")
-        # WSI 1.1 (framework pre-1.9.8), to remove "-std=c++11"
+        # WSI 1.1 (framework pre-1.10.0), to remove "-std=c++11"
         set(ORTHANC_FRAMEWORK_MD5 "2eaa073cbb4b44ffba199ad93393b2b1")
       endif()
     endif()
--- a/OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp	Sun Feb 20 12:59:30 2022 +0100
+++ b/OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp	Sun Feb 20 17:04:41 2022 +0100
@@ -187,7 +187,7 @@
 
     if (bitsAllocated_ != 8 && bitsAllocated_ != 16 &&
         bitsAllocated_ != 24 && bitsAllocated_ != 32 &&
-        bitsAllocated_ != 1 /* new in Orthanc 1.9.8 */)
+        bitsAllocated_ != 1 /* new in Orthanc 1.10.0 */)
     {
       throw OrthancException(ErrorCode_IncompatibleImageFormat, "Image not supported: " + boost::lexical_cast<std::string>(bitsAllocated_) + " bits allocated");
     }
@@ -207,7 +207,7 @@
 
     if (bitsStored_ == 1)
     {
-      // This is the case of DICOM SEG, new in Orthanc 1.9.8
+      // This is the case of DICOM SEG, new in Orthanc 1.10.0
       if (bitsAllocated_ != 1)
       {
         throw OrthancException(ErrorCode_BadFileFormat);
@@ -365,7 +365,7 @@
 
       if (GetBitsStored() == 1 && GetChannelCount() == 1 && !IsSigned())
       {
-        // This is the case of DICOM SEG, new in Orthanc 1.9.8
+        // This is the case of DICOM SEG, new in Orthanc 1.10.0
         format = PixelFormat_Grayscale8;
         return true;
       }
--- a/OrthancFramework/Sources/DicomFormat/DicomIntegerPixelAccessor.cpp	Sun Feb 20 12:59:30 2022 +0100
+++ b/OrthancFramework/Sources/DicomFormat/DicomIntegerPixelAccessor.cpp	Sun Feb 20 17:04:41 2022 +0100
@@ -157,7 +157,7 @@
     
     if (information_.GetBitsStored() == 1)
     {
-      // New in Orthanc 1.9.8, notably for DICOM SEG
+      // New in Orthanc 1.10.0, notably for DICOM SEG
       assert(information_.GetBitsAllocated() == 1 &&
              information_.GetChannelCount() == 1 &&
              !information_.IsPlanar());
--- a/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp	Sun Feb 20 12:59:30 2022 +0100
+++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp	Sun Feb 20 17:04:41 2022 +0100
@@ -543,7 +543,7 @@
     bool fastVersionSuccess = false;
     PixelFormat sourceFormat;
     if (!info.IsPlanar() &&
-        info.GetBitsStored() != 1 &&  // Black-and-white image, notably DICOM SEG (new in Orthanc 1.9.8)
+        info.GetBitsStored() != 1 &&  // Black-and-white image, notably DICOM SEG (new in Orthanc 1.10.0)
         info.ExtractPixelFormat(sourceFormat, false))
     {
       try
--- a/OrthancFramework/Sources/FileStorage/StorageAccessor.cpp	Sun Feb 20 12:59:30 2022 +0100
+++ b/OrthancFramework/Sources/FileStorage/StorageAccessor.cpp	Sun Feb 20 17:04:41 2022 +0100
@@ -133,7 +133,7 @@
           }
         }
 
-        cache_.Add(uuid, type, data, size);
+        cache_.Add(uuid, type, compressed);
         return FileInfo(uuid, type, size, md5,
                         CompressionType_ZlibWithSize, compressed.size(), compressedMD5);
       }
@@ -156,23 +156,17 @@
   void StorageAccessor::Read(std::string& content,
                              const FileInfo& info)
   {
-    if (cache_.Fetch(content, info.GetUuid(), info.GetContentType()))
-    {
-      LOG(INFO) << "Read attachment \"" << info.GetUuid() << "\" "
-                << "content type from cache";
-      return;
-    }
-
     switch (info.GetCompressionType())
     {
       case CompressionType_None:
       {
-        MetricsTimer timer(*this, METRICS_READ);
+        if (!cache_.Fetch(content, info.GetUuid(), info.GetContentType()))
+        {
+          MetricsTimer timer(*this, METRICS_READ);
+          std::unique_ptr<IMemoryBuffer> buffer(area_.Read(info.GetUuid(), info.GetContentType()));
+          buffer->MoveToString(content);
+        }
 
-        std::unique_ptr<IMemoryBuffer> buffer(area_.Read(info.GetUuid(), info.GetContentType()));
-        buffer->MoveToString(content);
-
-        cache_.Add(info.GetUuid(), info.GetContentType(), content);
         break;
       }
 
@@ -180,16 +174,23 @@
       {
         ZlibCompressor zlib;
 
-        std::unique_ptr<IMemoryBuffer> compressed;
-
+        std::string cached;
+        if (cache_.Fetch(cached, info.GetUuid(), info.GetContentType()))
+        {
+          zlib.Uncompress(content, cached.empty() ? NULL : cached.c_str(), cached.size());
+        }
+        else
         {
-          MetricsTimer timer(*this, METRICS_READ);
-          compressed.reset(area_.Read(info.GetUuid(), info.GetContentType()));
+          std::unique_ptr<IMemoryBuffer> compressed;
+          
+          {
+            MetricsTimer timer(*this, METRICS_READ);
+            compressed.reset(area_.Read(info.GetUuid(), info.GetContentType()));
+          }
+          
+          zlib.Uncompress(content, compressed->GetData(), compressed->GetSize());
         }
 
-        zlib.Uncompress(content, compressed->GetData(), compressed->GetSize());
-
-        cache_.Add(info.GetUuid(), info.GetContentType(), content);
         break;
       }
 
@@ -206,63 +207,56 @@
   void StorageAccessor::ReadRaw(std::string& content,
                                 const FileInfo& info)
   {
-    MetricsTimer timer(*this, METRICS_READ);
-
-    std::unique_ptr<IMemoryBuffer> buffer(area_.Read(info.GetUuid(), info.GetContentType()));
-    buffer->MoveToString(content);        
+    if (!cache_.Fetch(content, info.GetUuid(), info.GetContentType()))
+    {
+      MetricsTimer timer(*this, METRICS_READ);
+      std::unique_ptr<IMemoryBuffer> buffer(area_.Read(info.GetUuid(), info.GetContentType()));
+      buffer->MoveToString(content);
+    }
   }
 
 
   void StorageAccessor::Remove(const std::string& fileUuid,
                                FileContentType type)
   {
-    MetricsTimer timer(*this, METRICS_REMOVE);
-    area_.Remove(fileUuid, type);
+    cache_.Invalidate(fileUuid, type);
 
-    cache_.Invalidate(fileUuid, type);
-    
-    // in ReadStartRange, we might have cached only the start of the file -> try to remove it
-    if (type == FileContentType_Dicom)
     {
-      cache_.Invalidate(fileUuid, FileContentType_DicomUntilPixelData);
+      MetricsTimer timer(*this, METRICS_REMOVE);
+      area_.Remove(fileUuid, type);
     }
   }
+  
 
   void StorageAccessor::Remove(const FileInfo &info)
   {
     Remove(info.GetUuid(), info.GetContentType());
   }
 
-  IMemoryBuffer* StorageAccessor::ReadStartRange(const std::string& fileUuid,
-                                                 FileContentType contentType,
-                                                 uint64_t end /* exclusive */,
-                                                 FileContentType startFileContentType)
+
+  void StorageAccessor::ReadStartRange(std::string& target,
+                                       const std::string& fileUuid,
+                                       FileContentType contentType,
+                                       uint64_t end /* exclusive */)
   {
-    std::string content;
-    if (cache_.Fetch(content, fileUuid, contentType))
+    if (cache_.Fetch(target, fileUuid, contentType))
     {
-      LOG(INFO) << "Read attachment \"" << fileUuid << "\" "
-                << "(range from " << 0 << " to " << end << ") from cache";
-
-      return StringMemoryBuffer::CreateFromCopy(content, 0, end);
+      if (target.size() < end)
+      {
+        throw OrthancException(ErrorCode_CorruptedFile);
+      }
+      else
+      {
+        target.resize(end);
+      }
     }
-
-    if (cache_.Fetch(content, fileUuid, startFileContentType))
+    else
     {
-      LOG(INFO) << "Read attachment \"" << fileUuid << "\" "
-                << "(range from " << 0 << " to " << end << ") from cache";
-
-      assert(content.size() == end);
-      return StringMemoryBuffer::CreateFromCopy(content);
+      MetricsTimer timer(*this, METRICS_READ);
+      std::unique_ptr<IMemoryBuffer> buffer(area_.ReadRange(fileUuid, contentType, 0, end));
+      assert(buffer->GetSize() == end);
+      buffer->MoveToString(target);
     }
-
-    std::unique_ptr<IMemoryBuffer> buffer(area_.ReadRange(fileUuid, contentType, 0, end));
-
-    // we've read only the first part of the file -> add an entry in the cache
-    // note the uuid is still the uuid of the full file but the type is the type of the start of the file !
-    assert(buffer->GetSize() == end);
-    cache_.Add(fileUuid, startFileContentType, buffer->GetData(), buffer->GetSize());
-    return buffer.release();
   }
 
 
@@ -271,18 +265,11 @@
                                     const FileInfo& info,
                                     const std::string& mime)
   {
-    if (cache_.Fetch(sender.GetBuffer(), info.GetUuid(), info.GetContentType()))
-    {
-      LOG(INFO) << "Read attachment \"" << info.GetUuid() << "\" "
-                << "content type from cache";
-    }
-    else
+    if (!cache_.Fetch(sender.GetBuffer(), info.GetUuid(), info.GetContentType()))
     {
       MetricsTimer timer(*this, METRICS_READ);
       std::unique_ptr<IMemoryBuffer> buffer(area_.Read(info.GetUuid(), info.GetContentType()));
       buffer->MoveToString(sender.GetBuffer());
-
-      cache_.Add(info.GetUuid(), info.GetContentType(), sender.GetBuffer());
     }
 
     sender.SetContentType(mime);
--- a/OrthancFramework/Sources/FileStorage/StorageAccessor.h	Sun Feb 20 12:59:30 2022 +0100
+++ b/OrthancFramework/Sources/FileStorage/StorageAccessor.h	Sun Feb 20 17:04:41 2022 +0100
@@ -102,10 +102,10 @@
     void ReadRaw(std::string& content,
                  const FileInfo& info);
 
-    IMemoryBuffer* ReadStartRange(const std::string& fileUuid,
-                                  FileContentType fullFileContentType,
-                                  uint64_t end /* exclusive */,
-                                  FileContentType startFileContentType);
+    void ReadStartRange(std::string& target,
+                        const std::string& fileUuid,
+                        FileContentType fullFileContentType,
+                        uint64_t end /* exclusive */);
 
     void Remove(const std::string& fileUuid,
                 FileContentType type);
--- a/OrthancFramework/Sources/FileStorage/StorageCache.cpp	Sun Feb 20 12:59:30 2022 +0100
+++ b/OrthancFramework/Sources/FileStorage/StorageCache.cpp	Sun Feb 20 17:04:41 2022 +0100
@@ -25,114 +25,68 @@
 #include "StorageCache.h"
 
 #include "../Compatibility.h"
+#include "../Logging.h"
 #include "../OrthancException.h"
 
+#include <boost/lexical_cast.hpp>
 
 
 namespace Orthanc
 {
-  bool IsAcceptedContentType(FileContentType contentType)
-  {
-    return contentType == FileContentType_Dicom ||
-      contentType == FileContentType_DicomUntilPixelData ||
-      contentType == FileContentType_DicomAsJson;
-  }
-
-  const char* ToString(FileContentType contentType)
+  static std::string GetCacheKey(const std::string& uuid,
+                                 FileContentType contentType)
   {
-    switch (contentType)
-    {
-      case FileContentType_Dicom:
-        return "dicom";
-      case FileContentType_DicomUntilPixelData:
-        return "dicom-header";
-      case FileContentType_DicomAsJson:
-        return "dicom-json";
-      default:
-        throw OrthancException(ErrorCode_InternalError,
-                               "ContentType not supported in StorageCache");
-    }
+    return uuid + ":" + boost::lexical_cast<std::string>(contentType);
   }
-
-  bool GetCacheKey(std::string& key, const std::string& uuid, FileContentType contentType)
-  {
-    if (contentType == FileContentType_Unknown || contentType >= FileContentType_StartUser)
-    {
-      return false;
-    }
-
-    key = uuid + ":" + std::string(ToString(contentType));
-
-    return true;
-  }
+  
   
   void StorageCache::SetMaximumSize(size_t size)
   {
     cache_.SetMaximumSize(size);
   }
+  
 
   void StorageCache::Add(const std::string& uuid, 
                          FileContentType contentType,
                          const std::string& value)
   {
-    if (!IsAcceptedContentType(contentType))
-    {
-      return;
-    }
-
-    std::string key;
-
-    if (GetCacheKey(key, uuid, contentType))
-    {
-      cache_.Add(key, value);
-    }
+    const std::string key = GetCacheKey(uuid, contentType);
+    cache_.Add(key, value);
   }
+  
 
   void StorageCache::Add(const std::string& uuid, 
                          FileContentType contentType,
                          const void* buffer,
                          size_t size)
   {
-    if (!IsAcceptedContentType(contentType))
-    {
-      return;
-    }
-    
-    std::string key;
+    const std::string key = GetCacheKey(uuid, contentType);
+    cache_.Add(key, buffer, size);
+  }
+  
 
-    if (GetCacheKey(key, uuid, contentType))
-    {
-      cache_.Add(key, buffer, size);
-    }
-  }
-
-  void StorageCache::Invalidate(const std::string& uuid, FileContentType contentType)
+  void StorageCache::Invalidate(const std::string& uuid,
+                                FileContentType contentType)
   {
-    std::string key;
-    
-    if (GetCacheKey(key, uuid, contentType))
-    {
-      cache_.Invalidate(key);
-    }
+    const std::string key = GetCacheKey(uuid, contentType);
+    cache_.Invalidate(key);
   }
+  
 
   bool StorageCache::Fetch(std::string& value, 
                            const std::string& uuid,
                            FileContentType contentType)
   {
-    if (!IsAcceptedContentType(contentType))
+    const std::string key = GetCacheKey(uuid, contentType);
+    if (cache_.Fetch(value, key))
+    {
+      LOG(INFO) << "Read attachment \"" << uuid << "\" with content type "
+                << boost::lexical_cast<std::string>(contentType) << " from cache";
+      return true;
+    }
+    else
     {
       return false;
     }
-
-    std::string key;
-    if (GetCacheKey(key, uuid, contentType))
-    {
-      return cache_.Fetch(value, key);
-    }
-
-    return false;
   }
-
-
-}
\ No newline at end of file
+}
--- a/OrthancFramework/Sources/FileStorage/StorageCache.h	Sun Feb 20 12:59:30 2022 +0100
+++ b/OrthancFramework/Sources/FileStorage/StorageCache.h	Sun Feb 20 17:04:41 2022 +0100
@@ -37,7 +37,9 @@
    **/
    class ORTHANC_PUBLIC StorageCache : public boost::noncopyable
     {
+    private:
       MemoryStringCache   cache_;
+      
     public:
       void SetMaximumSize(size_t size);
 
@@ -50,7 +52,8 @@
                const void* buffer,
                size_t size);
 
-      void Invalidate(const std::string& uuid, FileContentType contentType);
+      void Invalidate(const std::string& uuid,
+                      FileContentType contentType);
 
       bool Fetch(std::string& value, 
                  const std::string& uuid,
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Sun Feb 20 12:59:30 2022 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Sun Feb 20 17:04:41 2022 +0100
@@ -1179,8 +1179,8 @@
     IncomingHttpRequestFilters  incomingHttpRequestFilters_;
     IncomingHttpRequestFilters2 incomingHttpRequestFilters2_;
     IncomingDicomInstanceFilters  incomingDicomInstanceFilters_;
-    IncomingCStoreInstanceFilters  incomingCStoreInstanceFilters_;  // New in Orthanc 1.9.8
-    ReceivedInstanceCallbacks  receivedInstanceCallbacks_;  // New in Orthanc 1.9.8
+    IncomingCStoreInstanceFilters  incomingCStoreInstanceFilters_;  // New in Orthanc 1.10.0
+    ReceivedInstanceCallbacks  receivedInstanceCallbacks_;  // New in Orthanc 1.10.0
     RefreshMetricsCallbacks refreshMetricsCallbacks_;
     StorageCommitmentScpCallbacks storageCommitmentScpCallbacks_;
     std::unique_ptr<StorageAreaFactory>  storageArea_;
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Sun Feb 20 12:59:30 2022 +0100
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Sun Feb 20 17:04:41 2022 +0100
@@ -117,8 +117,8 @@
 #endif
 
 #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
-#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     9
-#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  2
+#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     10
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  0
 
 
 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
@@ -462,8 +462,8 @@
     _OrthancPluginService_RegisterIncomingDicomInstanceFilter = 1014,
     _OrthancPluginService_RegisterTranscoderCallback = 1015,   /* New in Orthanc 1.7.0 */
     _OrthancPluginService_RegisterStorageArea2 = 1016,         /* New in Orthanc 1.9.0 */
-    _OrthancPluginService_RegisterIncomingCStoreInstanceFilter = 1017,  /* New in Orthanc 1.9.8 */
-    _OrthancPluginService_RegisterReceivedInstanceCallback = 1018,  /* New in Orthanc 1.9.8 */
+    _OrthancPluginService_RegisterIncomingCStoreInstanceFilter = 1017,  /* New in Orthanc 1.10.0 */
+    _OrthancPluginService_RegisterReceivedInstanceCallback = 1018,  /* New in Orthanc 1.10.0 */
 
     /* Sending answers to REST calls */
     _OrthancPluginService_AnswerBuffer = 2000,
--- a/OrthancServer/Resources/Configuration.json	Sun Feb 20 12:59:30 2022 +0100
+++ b/OrthancServer/Resources/Configuration.json	Sun Feb 20 17:04:41 2022 +0100
@@ -44,7 +44,7 @@
   // Maximum size of the storage cache in MB.  The storage cache
   // is stored in RAM and contains a copy of recently accessed
   // files (written or read).  A value of "0" indicates the cache
-  // is disabled.  (new in Orthanc 1.9.8)
+  // is disabled.  (new in Orthanc 1.10.0)
   "MaximumStorageCacheSize" : 128,
 
   // List of paths to the custom Lua scripts that are to be loaded
@@ -441,7 +441,7 @@
   // connections.  With a single thread, if a C-Find is received
   // during e.g the transcoding of an incoming C-Store, it will
   // have to wait until the end of the C-Store before being processed.
-  // (new in Orthanc 1.9.8, before this version, the value was fixed to 4)
+  // (new in Orthanc 1.10.0, before this version, the value was fixed to 4)
   "DicomThreadsCount" : 4,
 
   // The list of the known Orthanc peers. This option is ignored if
@@ -862,6 +862,6 @@
   // A value of 0 means reading and writing are performed in sequence
   // (default behaviour).  A value > 1 is meaningful only if the storage
   // is a distributed network storage (e.g object storage plugin).
-  // (new in Orthanc 1.9.8)
+  // (new in Orthanc 1.10.0)
   "ZipLoaderThreads": 0
 }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp	Sun Feb 20 12:59:30 2022 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp	Sun Feb 20 17:04:41 2022 +0100
@@ -146,7 +146,7 @@
 
     {
       OrthancConfiguration::ReaderLock lock;
-      loaderThreads = lock.GetConfiguration().GetUnsignedIntegerParameter(CONFIG_LOADER_THREADS, 0);  // New in Orthanc 1.9.8
+      loaderThreads = lock.GetConfiguration().GetUnsignedIntegerParameter(CONFIG_LOADER_THREADS, 0);  // New in Orthanc 1.10.0
     }
    
   }
@@ -631,7 +631,7 @@
 
     {
       OrthancConfiguration::ReaderLock lock;
-      unsigned int loaderThreads = lock.GetConfiguration().GetUnsignedIntegerParameter(CONFIG_LOADER_THREADS, 0);  // New in Orthanc 1.9.8
+      unsigned int loaderThreads = lock.GetConfiguration().GetUnsignedIntegerParameter(CONFIG_LOADER_THREADS, 0);  // New in Orthanc 1.10.0
       job->SetLoaderThreads(loaderThreads);
     }
 
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Sun Feb 20 12:59:30 2022 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Sun Feb 20 17:04:41 2022 +0100
@@ -3677,7 +3677,7 @@
     Register("/instances/{id}/frames/{frame}/matlab", GetMatlabImage);
     Register("/instances/{id}/frames/{frame}/raw", GetRawFrame<false>);
     Register("/instances/{id}/frames/{frame}/raw.gz", GetRawFrame<true>);
-    Register("/instances/{id}/frames/{frame}/numpy", GetNumpyFrame);  // New in Orthanc 1.9.8
+    Register("/instances/{id}/frames/{frame}/numpy", GetNumpyFrame);  // New in Orthanc 1.10.0
     Register("/instances/{id}/pdf", ExtractPdf);
     Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>);
     Register("/instances/{id}/rendered", GetRenderedFrame);
@@ -3686,7 +3686,7 @@
     Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>);
     Register("/instances/{id}/matlab", GetMatlabImage);
     Register("/instances/{id}/header", GetInstanceHeader);
-    Register("/instances/{id}/numpy", GetNumpyInstance);  // New in Orthanc 1.9.8
+    Register("/instances/{id}/numpy", GetNumpyInstance);  // New in Orthanc 1.10.0
 
     Register("/patients/{id}/protected", IsProtectedPatient);
     Register("/patients/{id}/protected", SetPatientProtection);
@@ -3746,7 +3746,7 @@
     Register("/instances/{id}/content/*", GetRawContent);
 
     Register("/series/{id}/ordered-slices", OrderSlices);
-    Register("/series/{id}/numpy", GetNumpySeries);  // New in Orthanc 1.9.8
+    Register("/series/{id}/numpy", GetNumpySeries);  // New in Orthanc 1.10.0
 
     Register("/patients/{id}/reconstruct", ReconstructResource<ResourceType_Patient>);
     Register("/studies/{id}/reconstruct", ReconstructResource<ResourceType_Study>);
--- a/OrthancServer/Sources/Search/ISqlLookupFormatter.h	Sun Feb 20 12:59:30 2022 +0100
+++ b/OrthancServer/Sources/Search/ISqlLookupFormatter.h	Sun Feb 20 17:04:41 2022 +0100
@@ -51,7 +51,7 @@
 
     /**
      * Whether to escape '[' and ']', which is only needed for
-     * MSSQL. New in Orthanc 1.9.8, from the following changeset:
+     * MSSQL. New in Orthanc 1.10.0, from the following changeset:
      * https://hg.orthanc-server.com/orthanc-databases/rev/389c037387ea
      **/
     virtual bool IsEscapeBrackets() const = 0;
--- a/OrthancServer/Sources/ServerContext.cpp	Sun Feb 20 12:59:30 2022 +0100
+++ b/OrthancServer/Sources/ServerContext.cpp	Sun Feb 20 17:04:41 2022 +0100
@@ -953,23 +953,17 @@
          * "true".
          **/
       
-        std::unique_ptr<IMemoryBuffer> dicom;
+        std::string dicom;
+        
         {
           StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry());
-          dicom.reset(accessor.ReadStartRange(attachment.GetUuid(), FileContentType_Dicom, pixelDataOffset, FileContentType_DicomUntilPixelData));
-        }
-
-        if (dicom.get() == NULL)
-        {
-          throw OrthancException(ErrorCode_InternalError);
+          accessor.ReadStartRange(dicom, attachment.GetUuid(), FileContentType_Dicom, pixelDataOffset);
         }
-        else
-        {
-          assert(dicom->GetSize() == pixelDataOffset);
-          ParsedDicomFile parsed(dicom->GetData(), dicom->GetSize());
-          OrthancConfiguration::DefaultDicomDatasetToJson(result, parsed, ignoreTagLength);
-          InjectEmptyPixelData(result);
-        }
+        
+        assert(dicom.size() == pixelDataOffset);
+        ParsedDicomFile parsed(dicom);
+        OrthancConfiguration::DefaultDicomDatasetToJson(result, parsed, ignoreTagLength);
+        InjectEmptyPixelData(result);
       }
       else if (ignoreTagLength.empty() &&
                index_.LookupAttachment(attachment, revision, instancePublicId, FileContentType_DicomAsJson))
@@ -1093,9 +1087,9 @@
 
         StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry());
 
-        std::unique_ptr<IMemoryBuffer> buffer(
-          accessor.ReadStartRange(attachment.GetUuid(), attachment.GetContentType(), pixelDataOffset, FileContentType_DicomUntilPixelData));
-        buffer->MoveToString(dicom);
+        accessor.ReadStartRange(dicom, attachment.GetUuid(), attachment.GetContentType(), pixelDataOffset);
+        assert(dicom.size() == pixelDataOffset);
+        
         return true;   // Success
       }
       catch (boost::bad_lexical_cast&)
--- a/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp	Sun Feb 20 12:59:30 2022 +0100
+++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp	Sun Feb 20 17:04:41 2022 +0100
@@ -1194,7 +1194,7 @@
   {
     if (loaderThreads_ == 0)
     {
-      // default behaviour before loaderThreads was introducted in 1.9.8
+      // default behaviour before loaderThreads was introducted in 1.10.0
       instanceLoader_.reset(new SynchronousInstanceLoader(context_));
     }
     else
--- a/OrthancServer/Sources/ServerJobs/ArchiveJob.h	Sun Feb 20 12:59:30 2022 +0100
+++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.h	Sun Feb 20 17:04:41 2022 +0100
@@ -68,7 +68,7 @@
     bool                 transcode_;
     DicomTransferSyntax  transferSyntax_;
 
-    // New in Orthanc 1.9.8
+    // New in Orthanc 1.10.0
     unsigned int         loaderThreads_;
 
     void FinalizeTarget();
--- a/OrthancServer/UnitTestsSources/PluginsTests.cpp	Sun Feb 20 12:59:30 2022 +0100
+++ b/OrthancServer/UnitTestsSources/PluginsTests.cpp	Sun Feb 20 17:04:41 2022 +0100
@@ -77,7 +77,7 @@
   
 #elif defined(__linux__) || defined(__FreeBSD_kernel__)
   /**
-   * Since Orthanc 1.9.8, we test the "libdl.so.2" instead of the
+   * Since Orthanc 1.10.0, we test the "libdl.so.2" instead of the
    * "libdl.so", as discussed here:
    * https://groups.google.com/g/orthanc-users/c/I5g1fN6MCvg/m/JVdvRyjJAAAJ
    * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1001305