changeset 6163:255fcf2f8541

fix the re-encoding of DICOM files larger than 4GB
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 11 Jun 2025 14:35:44 +0200 (8 days ago)
parents 73e9d9248cf3
children 52f87859fec2 2ab75e4e8c91
files NEWS OrthancFramework/Sources/ChunkedBuffer.cpp OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp
diffstat 3 files changed, 136 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Mon Jun 02 12:28:45 2025 +0200
+++ b/NEWS	Wed Jun 11 14:35:44 2025 +0200
@@ -16,10 +16,10 @@
     Patch provided by Yurii (George) from ivtech.dev.
     With this patch, we observed a 100 fold performance improvement when the 
     "Pending" table contains 1-2 millions files.
-      
 * Configuration options "RejectSopClasses" and "RejectedSopClasses" are taken as synonyms.
   In Orthanc 1.12.6 and 1.12.7, "RejectSopClasses" was used instead of the expected
   "RejectedSopClasses" spelling.
+* Fix the re-encoding of DICOM files larger than 4GB
 
 
 REST API
--- a/OrthancFramework/Sources/ChunkedBuffer.cpp	Mon Jun 02 12:28:45 2025 +0200
+++ b/OrthancFramework/Sources/ChunkedBuffer.cpp	Wed Jun 11 14:35:44 2025 +0200
@@ -25,6 +25,8 @@
 #include "PrecompiledHeaders.h"
 #include "ChunkedBuffer.h"
 
+#include "OrthancException.h"
+
 #include <cassert>
 #include <string.h>
 
@@ -54,7 +56,16 @@
     else
     {
       assert(chunkData != NULL);
-      chunks_.push_back(new std::string(reinterpret_cast<const char*>(chunkData), chunkSize));
+
+      try
+      {
+        chunks_.push_back(new std::string(reinterpret_cast<const char*>(chunkData), chunkSize));
+      }
+      catch (...)
+      {
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+      }
+
       numBytes_ += chunkSize;
     }
   }
@@ -172,7 +183,15 @@
   void ChunkedBuffer::Flatten(std::string& result)
   {
     FlushPendingBuffer();
-    result.resize(numBytes_);
+
+    try
+    {
+      result.resize(numBytes_);
+    }
+    catch (...)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
 
     size_t pos = 0;
     for (Chunks::iterator it = chunks_.begin(); 
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Mon Jun 02 12:28:45 2025 +0200
+++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Wed Jun 11 14:35:44 2025 +0200
@@ -38,6 +38,7 @@
 
 #include "FromDcmtkBridge.h"
 #include "ToDcmtkBridge.h"
+#include "../ChunkedBuffer.h"
 #include "../Compatibility.h"
 #include "../Logging.h"
 #include "../Toolbox.h"
@@ -167,6 +168,73 @@
 
   namespace
   {
+    class ChunkedBufferStream : public DcmOutputStream
+    {
+    private:
+      class Consumer : public DcmConsumer
+      {
+      private:
+        ChunkedBuffer  buffer_;
+
+      public:
+        void Flatten(std::string& buffer)
+        {
+          buffer_.Flatten(buffer);
+        }
+
+        OFBool good() const ORTHANC_OVERRIDE
+        {
+          return true;
+        }
+
+        OFCondition status() const ORTHANC_OVERRIDE
+        {
+          return EC_Normal;
+        }
+
+        OFBool isFlushed() const ORTHANC_OVERRIDE
+        {
+          return true;
+        }
+
+        offile_off_t avail() const ORTHANC_OVERRIDE
+        {
+          // since we cannot report "unlimited", let's claim that we can still write 10MB.
+          // Note that offile_off_t is a signed type.
+          return 10 * 1024 * 1024;
+        }
+
+        offile_off_t write(const void *buf,
+                           offile_off_t buflen) ORTHANC_OVERRIDE
+        {
+          buffer_.AddChunk(buf, buflen);
+          return buflen;
+        }
+
+        void flush() ORTHANC_OVERRIDE
+        {
+          // Nothing to flush
+        }
+      };
+
+      Consumer consumer_;
+
+    public:
+      ChunkedBufferStream() :
+        DcmOutputStream(&consumer_)
+      {
+      }
+
+      void Flatten(std::string& buffer)
+      {
+        consumer_.Flatten(buffer);
+      }
+    };
+  }
+
+
+  namespace
+  {
     class DictionaryLocker : public boost::noncopyable
     {
     private:
@@ -1572,7 +1640,12 @@
   }
 
 
-  
+#if 0
+  /**
+   * This was the implementation in Orthanc <= 1.12.7. This version
+   * uses "DcmFileFormat::calcElementLength()", which cannot handle
+   * DICOM files whose size cannot be represented on 32 bits.
+   **/
   static bool SaveToMemoryBufferInternal(std::string& buffer,
                                          DcmFileFormat& dicom,
                                          E_TransferSyntax xfer,
@@ -1619,6 +1692,46 @@
       return false;
     }
   }
+#endif
+
+
+#if 1
+  /**
+   * This is the cleaner implementation used in Orthanc >= 1.12.8,
+   * which allows to write DICOM files larger than 4GB.
+   **/
+  static bool SaveToMemoryBufferInternal(std::string& buffer,
+                                         DcmFileFormat& dicom,
+                                         E_TransferSyntax xfer,
+                                         std::string& errorMessage)
+  {
+    ChunkedBufferStream ob;
+
+    // Fill the (chunked) memory buffer with the meta-header and the dataset
+    dicom.transferInit();
+    OFCondition c = dicom.write(ob, xfer, /*opt_sequenceType*/ EET_ExplicitLength, NULL,
+                                /*opt_groupLength*/ EGL_recalcGL,
+                                /*opt_paddingType*/ EPD_noChange,
+                                /*padlen*/ 0, /*subPadlen*/ 0, /*instanceLength*/ 0,
+                                EWM_updateMeta /* creates new SOP instance UID on lossy */);
+    dicom.transferEnd();
+
+    if (c.good())
+    {
+      ob.flush();
+      ob.Flatten(buffer);
+      return true;
+    }
+    else
+    {
+      // Error
+      buffer.clear();
+      errorMessage = std::string(c.text());
+      return false;
+    }
+  }
+#endif
+
 
   bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
                                            DcmDataset& dataSet)