changeset 4494:39192eb9b43d

New metadata automatically computed at the instance level: "PixelDataOffset"
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 04 Feb 2021 15:31:00 +0100
parents b57ca702a430
children fa2311f94d9f
files NEWS OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp OrthancFramework/Sources/DicomFormat/DicomStreamReader.h OrthancFramework/Sources/DicomFormat/StreamBlockReader.cpp OrthancFramework/UnitTestsSources/DicomMapTests.cpp OrthancServer/Sources/ServerEnumerations.cpp OrthancServer/Sources/ServerEnumerations.h OrthancServer/Sources/ServerIndex.cpp OrthancServer/UnitTestsSources/ServerIndexTests.cpp
diffstat 9 files changed, 138 insertions(+), 42 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Thu Feb 04 11:42:25 2021 +0100
+++ b/NEWS	Thu Feb 04 15:31:00 2021 +0100
@@ -1,6 +1,7 @@
 Pending changes in the mainline
 ===============================
 
+* New metadata automatically computed at the instance level: "PixelDataOffset"
 * Fix build on big-endian architectures
 
 
--- a/OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp	Thu Feb 04 11:42:25 2021 +0100
+++ b/OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp	Thu Feb 04 15:31:00 2021 +0100
@@ -27,8 +27,12 @@
 
 #include <cassert>
 #include <sstream>
+#include <boost/iostreams/device/array.hpp>
+#include <boost/iostreams/stream.hpp>
 
 
+#include <iostream>
+
 namespace Orthanc
 {
   static bool IsNormalizationNeeded(const std::string& source,
@@ -619,6 +623,66 @@
     {
       return pixelDataOffset_;
     }
+
+    static bool LookupPixelDataOffset(uint64_t& offset,
+                                      std::istream& stream)
+    {
+      PixelDataVisitor visitor;
+      bool isLittleEndian;
+
+      {
+        DicomStreamReader reader(stream);
+
+        try
+        {
+          reader.Consume(visitor);
+          isLittleEndian = reader.IsLittleEndian();
+        }
+        catch (OrthancException& e)
+        {
+          // Invalid DICOM file
+          return false;
+        }
+      }
+
+      if (visitor.HasPixelData())
+      {
+        // Sanity check if we face an unsupported DICOM file: Make
+        // sure that we can read DICOM_TAG_PIXEL_DATA at the reported
+        // position in the stream
+        stream.seekg(visitor.GetPixelDataOffset(), stream.beg);
+        
+        std::string s;
+        s.resize(4);
+        stream.read(&s[0], s.size());
+
+        if (!isLittleEndian)
+        {
+          // Byte swapping if reading a file whose transfer syntax is
+          // 1.2.840.10008.1.2.2 (big endian explicit)
+          std::swap(s[0], s[1]);
+          std::swap(s[2], s[3]);          
+        }
+        
+        if (stream.gcount() == static_cast<std::streamsize>(s.size()) &&
+            s[0] == char(0xe0) &&
+            s[1] == char(0x7f) &&
+            s[2] == char(0x10) &&
+            s[3] == char(0x00))
+        {
+          offset = visitor.GetPixelDataOffset();
+          return true;
+        }
+        else
+        {
+          return false;
+        }
+      }
+      else
+      {
+        return false;
+      }
+    }
   };
 
   
@@ -626,29 +690,17 @@
                                                 const std::string& dicom)
   {
     std::stringstream stream(dicom);
-    
-    DicomStreamReader reader(stream);
-
-    PixelDataVisitor visitor;
+    return PixelDataVisitor::LookupPixelDataOffset(offset, stream);
+  }
+  
 
-    try
-    {
-      reader.Consume(visitor);
-    }
-    catch (OrthancException& e)
-    {
-      // Invalid DICOM file
-      return false;
-    }
-
-    if (visitor.HasPixelData())
-    {
-      offset = visitor.GetPixelDataOffset();
-      return true;
-    }
-    else
-    {
-      return false;
-    }
+  bool DicomStreamReader::LookupPixelDataOffset(uint64_t& offset,
+                                                const void* buffer,
+                                                size_t size)
+  {
+    boost::iostreams::array_source source(reinterpret_cast<const char*>(buffer), size);
+    boost::iostreams::stream<boost::iostreams::array_source> stream(source);
+    return PixelDataVisitor::LookupPixelDataOffset(offset, stream);
   }
 }
+
--- a/OrthancFramework/Sources/DicomFormat/DicomStreamReader.h	Thu Feb 04 11:42:25 2021 +0100
+++ b/OrthancFramework/Sources/DicomFormat/DicomStreamReader.h	Thu Feb 04 15:31:00 2021 +0100
@@ -129,5 +129,9 @@
 
     static bool LookupPixelDataOffset(uint64_t& offset,
                                       const std::string& dicom);
+
+    static bool LookupPixelDataOffset(uint64_t& offset,
+                                      const void* buffer,
+                                      size_t size);
   };
 }
--- a/OrthancFramework/Sources/DicomFormat/StreamBlockReader.cpp	Thu Feb 04 11:42:25 2021 +0100
+++ b/OrthancFramework/Sources/DicomFormat/StreamBlockReader.cpp	Thu Feb 04 15:31:00 2021 +0100
@@ -66,8 +66,16 @@
     {
       while (blockPos_ < block_.size())
       {
+        /**
+         * WARNING: Do NOT use "stream_.readsome()", as it does not
+         * work properly on non-buffered stream (which is the case in
+         * "DicomStreamReader::LookupPixelDataOffset()" for buffers)
+         **/
+        
         size_t remainingBytes = block_.size() - blockPos_;
-        std::streamsize r = stream_.readsome(&block_[blockPos_], remainingBytes);
+        stream_.read(&block_[blockPos_], remainingBytes);
+        
+        std::streamsize r = stream_.gcount();
         if (r == 0)
         {
           return false;
--- a/OrthancFramework/UnitTestsSources/DicomMapTests.cpp	Thu Feb 04 11:42:25 2021 +0100
+++ b/OrthancFramework/UnitTestsSources/DicomMapTests.cpp	Thu Feb 04 15:31:00 2021 +0100
@@ -876,17 +876,17 @@
 
   Sources sources;
   sources.push_back(std::make_pair(PATH + "../ColorTestMalaterre.dcm", 0x03a0u));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.1.dcm", 0x037c));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.2.dcm", 0x03e8));  // Big Endian
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.50.dcm", 0x04ac));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.51.dcm", 0x072c));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.57.dcm", 0x0620));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.70.dcm", 0x065a));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.80.dcm", 0x0b46));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.81.dcm", 0x073e));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.90.dcm", 0x0b66));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.91.dcm", 0x19b8));
-  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.5.dcm", 0x0b0a));
+  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.1.dcm", 0x037cu));
+  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.2.dcm", 0x03e8u));  // Big Endian
+  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.50.dcm", 0x04acu));
+  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.51.dcm", 0x072cu));
+  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.57.dcm", 0x0620u));
+  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.70.dcm", 0x065au));
+  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.80.dcm", 0x0b46u));
+  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.81.dcm", 0x073eu));
+  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.90.dcm", 0x0b66u));
+  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.4.91.dcm", 0x19b8u));
+  sources.push_back(std::make_pair(PATH + "1.2.840.10008.1.2.5.dcm", 0x0b0au));
 
   {
     std::string dicom;
@@ -912,6 +912,12 @@
       ASSERT_EQ(it->second, offset);
     }
     
+    {
+      uint64_t offset;
+      ASSERT_TRUE(DicomStreamReader::LookupPixelDataOffset(offset, dicom.c_str(), dicom.size()));
+      ASSERT_EQ(it->second, offset);
+    }
+    
     ParsedDicomFile a(dicom);
     Json::Value aa;
     a.DatasetToJson(aa, DicomToJsonFormat_Short, DicomToJsonFlags_Default, 0);
--- a/OrthancServer/Sources/ServerEnumerations.cpp	Thu Feb 04 11:42:25 2021 +0100
+++ b/OrthancServer/Sources/ServerEnumerations.cpp	Thu Feb 04 15:31:00 2021 +0100
@@ -70,6 +70,7 @@
     dictMetadataType_.Add(MetadataType_Instance_RemoteIp, "RemoteIP");
     dictMetadataType_.Add(MetadataType_Instance_CalledAet, "CalledAET");
     dictMetadataType_.Add(MetadataType_Instance_HttpUsername, "HttpUsername");
+    dictMetadataType_.Add(MetadataType_Instance_PixelDataOffset, "PixelDataOffset");
 
     dictContentType_.Add(FileContentType_Dicom, "dicom");
     dictContentType_.Add(FileContentType_DicomAsJson, "dicom-as-json");
--- a/OrthancServer/Sources/ServerEnumerations.h	Thu Feb 04 11:42:25 2021 +0100
+++ b/OrthancServer/Sources/ServerEnumerations.h	Thu Feb 04 15:31:00 2021 +0100
@@ -149,12 +149,13 @@
     MetadataType_ModifiedFrom = 5,
     MetadataType_AnonymizedFrom = 6,
     MetadataType_LastUpdate = 7,
-    MetadataType_Instance_Origin = 8,          // New in Orthanc 0.9.5
-    MetadataType_Instance_TransferSyntax = 9,  // New in Orthanc 1.2.0
-    MetadataType_Instance_SopClassUid = 10,    // New in Orthanc 1.2.0
-    MetadataType_Instance_RemoteIp = 11,       // New in Orthanc 1.4.0
-    MetadataType_Instance_CalledAet = 12,      // New in Orthanc 1.4.0
-    MetadataType_Instance_HttpUsername = 13,   // New in Orthanc 1.4.0
+    MetadataType_Instance_Origin = 8,            // New in Orthanc 0.9.5
+    MetadataType_Instance_TransferSyntax = 9,    // New in Orthanc 1.2.0
+    MetadataType_Instance_SopClassUid = 10,      // New in Orthanc 1.2.0
+    MetadataType_Instance_RemoteIp = 11,         // New in Orthanc 1.4.0
+    MetadataType_Instance_CalledAet = 12,        // New in Orthanc 1.4.0
+    MetadataType_Instance_HttpUsername = 13,     // New in Orthanc 1.4.0
+    MetadataType_Instance_PixelDataOffset = 14,  // New in Orthanc 1.9.0
 
     // Make sure that the value "65535" can be stored into this enumeration
     MetadataType_StartUser = 1024,
--- a/OrthancServer/Sources/ServerIndex.cpp	Thu Feb 04 11:42:25 2021 +0100
+++ b/OrthancServer/Sources/ServerIndex.cpp	Thu Feb 04 15:31:00 2021 +0100
@@ -39,6 +39,7 @@
 #endif
 
 #include "../../OrthancFramework/Sources/DicomFormat/DicomArray.h"
+#include "../../OrthancFramework/Sources/DicomFormat/DicomStreamReader.h"
 #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
 #include "../../OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h"
 #include "../../OrthancFramework/Sources/Logging.h"
@@ -760,6 +761,23 @@
                                  const Attachments& attachments,
                                  bool overwrite)
   {
+    std::string pixelDataOffset;
+
+    {
+      // Determining the pixel data offset is costly, don't do it
+      // within the mutex (new in Orthanc 1.9.1)
+      uint64_t offset;
+      if (DicomStreamReader::LookupPixelDataOffset(offset, instanceToStore.GetBufferData(),
+                                                   instanceToStore.GetBufferSize()))
+      {
+        pixelDataOffset = boost::lexical_cast<std::string>(offset);
+      }
+      else
+      {
+        pixelDataOffset.clear();
+      }
+    }
+    
     boost::mutex::scoped_lock lock(mutex_);
 
     const DicomMap& dicomSummary = instanceToStore.GetSummary();
@@ -971,6 +989,10 @@
           }
         }
 
+        // New in Orthanc 1.9.1
+        SetInstanceMetadata(content, instanceMetadata, instanceId,
+                            MetadataType_Instance_PixelDataOffset, pixelDataOffset);
+
         
         const DicomValue* value;
         if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_SOP_CLASS_UID)) != NULL &&
--- a/OrthancServer/UnitTestsSources/ServerIndexTests.cpp	Thu Feb 04 11:42:25 2021 +0100
+++ b/OrthancServer/UnitTestsSources/ServerIndexTests.cpp	Thu Feb 04 15:31:00 2021 +0100
@@ -729,11 +729,12 @@
     toStore.SetSummary(instance);
     ASSERT_EQ(StoreStatus_Success, index.Store(instanceMetadata, toStore, attachments,
                                                false /* don't overwrite */));
-    ASSERT_EQ(5u, instanceMetadata.size());
+    ASSERT_EQ(6u, instanceMetadata.size());
     ASSERT_TRUE(instanceMetadata.find(MetadataType_RemoteAet) != instanceMetadata.end());
     ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_ReceptionDate) != instanceMetadata.end());
     ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_TransferSyntax) != instanceMetadata.end());
     ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_SopClassUid) != instanceMetadata.end());
+    ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_PixelDataOffset) != instanceMetadata.end());
 
     // The default transfer syntax depends on the OS endianness
     std::string s = instanceMetadata[MetadataType_Instance_TransferSyntax];