changeset 4493:b57ca702a430

DicomStreamReader::LookupPixelDataOffset()
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 04 Feb 2021 11:42:25 +0100
parents 0b2484663233
children 39192eb9b43d
files OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp OrthancFramework/Sources/DicomFormat/DicomStreamReader.h OrthancFramework/UnitTestsSources/DicomMapTests.cpp
diffstat 3 files changed, 202 insertions(+), 40 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp	Sat Jan 30 18:19:11 2021 +0100
+++ b/OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp	Thu Feb 04 11:42:25 2021 +0100
@@ -26,6 +26,8 @@
 #include "../OrthancException.h"
 
 #include <cassert>
+#include <sstream>
+
 
 namespace Orthanc
 {
@@ -323,6 +325,9 @@
     }
     else
     {
+      assert(reader_.GetProcessedBytes() >= block.size());
+      const uint64_t tagOffset = reader_.GetProcessedBytes() - block.size();
+        
       ValueRepresentation vr = ValueRepresentation_Unknown;
         
       if (transferSyntax_ == DicomTransferSyntax_LittleEndianImplicit)
@@ -331,6 +336,7 @@
         {
           danglingTag_ = tag;
           danglingVR_ = vr;
+          danglingOffset_ = tagOffset;
         }
 
         uint32_t length = ReadUnsignedInteger32(block.c_str() + 4, true /* little endian */);
@@ -372,6 +378,7 @@
         {
           danglingTag_ = tag;
           danglingVR_ = vr;
+          danglingOffset_ = tagOffset;
         }
       }
     }
@@ -400,12 +407,19 @@
   }    
 
     
-  void DicomStreamReader::HandleDatasetExplicitLength(const std::string& block)
+  void DicomStreamReader::HandleDatasetExplicitLength(IVisitor& visitor,
+                                                      const std::string& block)
   {
     assert(block.size() == 4);
 
     uint32_t length = ReadUnsignedInteger32(block.c_str(), IsLittleEndian());
     HandleDatasetExplicitLength(length);
+
+    std::string empty;
+    if (!visitor.VisitDatasetTag(danglingTag_, danglingVR_, empty, IsLittleEndian(), danglingOffset_))
+    {
+      state_ = State_Done;
+    }
   }
     
 
@@ -451,13 +465,13 @@
       if (IsNormalizationNeeded(block, danglingVR_))
       {
         std::string s(block.begin(), block.end() - 1);
-        c = visitor.VisitDatasetTag(danglingTag_, danglingVR_, s, IsLittleEndian());
+        c = visitor.VisitDatasetTag(danglingTag_, danglingVR_, s, IsLittleEndian(), danglingOffset_);
       }
       else
       {
-        c = visitor.VisitDatasetTag(danglingTag_, danglingVR_, block, IsLittleEndian());
+        c = visitor.VisitDatasetTag(danglingTag_, danglingVR_, block, IsLittleEndian(), danglingOffset_);
       }
-        
+      
       if (!c)
       {
         state_ = State_Done;
@@ -476,6 +490,7 @@
     transferSyntax_(DicomTransferSyntax_LittleEndianImplicit),  // Dummy
     danglingTag_(0x0000, 0x0000),  // Dummy
     danglingVR_(ValueRepresentation_Unknown),  // Dummy
+    danglingOffset_(0),  // Dummy
     sequenceDepth_(0)
   {
     reader_.Schedule(128 /* empty header */ +
@@ -510,7 +525,7 @@
             break;
 
           case State_DatasetExplicitLength:
-            HandleDatasetExplicitLength(block);
+            HandleDatasetExplicitLength(visitor, block);
             break;
 
           case State_SequenceExplicitLength:
@@ -554,4 +569,86 @@
   {
     return reader_.GetProcessedBytes();
   }
+
+
+  class DicomStreamReader::PixelDataVisitor : public DicomStreamReader::IVisitor
+  {
+  private:
+    bool      hasPixelData_;
+    uint64_t  pixelDataOffset_;
+    
+  public:
+    PixelDataVisitor() :
+      hasPixelData_(false),
+      pixelDataOffset_(0)
+    {
+    }
+    
+    virtual void VisitMetaHeaderTag(const DicomTag& tag,
+                                    const ValueRepresentation& vr,
+                                    const std::string& value) ORTHANC_OVERRIDE
+    {
+    }
+
+    virtual void VisitTransferSyntax(DicomTransferSyntax transferSyntax) ORTHANC_OVERRIDE
+    {
+    }
+    
+    virtual bool VisitDatasetTag(const DicomTag& tag,
+                                 const ValueRepresentation& vr,
+                                 const std::string& value,
+                                 bool isLittleEndian,
+                                 uint64_t fileOffset) ORTHANC_OVERRIDE
+    {
+      if (tag == DICOM_TAG_PIXEL_DATA)
+      {
+        hasPixelData_ = true;
+        pixelDataOffset_ = fileOffset;
+      }
+
+      // Stop processing once pixel data has been passed
+      return (tag < DICOM_TAG_PIXEL_DATA);
+    }
+
+    bool HasPixelData() const
+    {
+      return hasPixelData_;
+    }
+
+    uint64_t GetPixelDataOffset() const
+    {
+      return pixelDataOffset_;
+    }
+  };
+
+  
+  bool DicomStreamReader::LookupPixelDataOffset(uint64_t& offset,
+                                                const std::string& dicom)
+  {
+    std::stringstream stream(dicom);
+    
+    DicomStreamReader reader(stream);
+
+    PixelDataVisitor visitor;
+
+    try
+    {
+      reader.Consume(visitor);
+    }
+    catch (OrthancException& e)
+    {
+      // Invalid DICOM file
+      return false;
+    }
+
+    if (visitor.HasPixelData())
+    {
+      offset = visitor.GetPixelDataOffset();
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
 }
--- a/OrthancFramework/Sources/DicomFormat/DicomStreamReader.h	Sat Jan 30 18:19:11 2021 +0100
+++ b/OrthancFramework/Sources/DicomFormat/DicomStreamReader.h	Thu Feb 04 11:42:25 2021 +0100
@@ -58,10 +58,13 @@
       virtual bool VisitDatasetTag(const DicomTag& tag,
                                    const ValueRepresentation& vr,
                                    const std::string& value,
-                                   bool isLittleEndian) = 0;
+                                   bool isLittleEndian,
+                                   uint64_t fileOffset) = 0;
     };
     
   private:
+    class PixelDataVisitor;
+    
     enum State
     {
       State_Preamble,
@@ -79,6 +82,7 @@
     DicomTransferSyntax  transferSyntax_;
     DicomTag             danglingTag_;  // Current root-level tag
     ValueRepresentation  danglingVR_;
+    uint64_t             danglingOffset_;
     unsigned int         sequenceDepth_;
     
     bool IsLittleEndian() const;
@@ -94,7 +98,8 @@
 
     void HandleDatasetExplicitLength(uint32_t length);
     
-    void HandleDatasetExplicitLength(const std::string& block);
+    void HandleDatasetExplicitLength(IVisitor& visitor,
+                                     const std::string& block);
 
     void HandleSequenceExplicitLength(const std::string& block);
 
@@ -121,5 +126,8 @@
     bool IsDone() const;
 
     uint64_t GetProcessedBytes() const;
+
+    static bool LookupPixelDataOffset(uint64_t& offset,
+                                      const std::string& dicom);
   };
 }
--- a/OrthancFramework/UnitTestsSources/DicomMapTests.cpp	Sat Jan 30 18:19:11 2021 +0100
+++ b/OrthancFramework/UnitTestsSources/DicomMapTests.cpp	Thu Feb 04 11:42:25 2021 +0100
@@ -806,8 +806,14 @@
   {
   private:
     DicomMap  map_;
+    uint64_t  pixelDataOffset_;
     
   public:
+    V() :
+      pixelDataOffset_(0)
+    {
+    }
+    
     const DicomMap& GetDicomMap() const
     {
       return map_;
@@ -828,25 +834,35 @@
     virtual bool VisitDatasetTag(const DicomTag& tag,
                                  const ValueRepresentation& vr,
                                  const std::string& value,
-                                 bool isLittleEndian) ORTHANC_OVERRIDE
+                                 bool isLittleEndian,
+                                 uint64_t fileOffset) ORTHANC_OVERRIDE
     {
       if (!isLittleEndian)
         printf("** ");
-      if (tag.GetGroup() < 0x7f00)
+
+      if (tag == DICOM_TAG_PIXEL_DATA)
       {
         std::cout << "Dataset: " << tag.Format() << " " << EnumerationToString(vr)
-                  << " [" << Toolbox::ConvertToAscii(value).c_str() << "] (" << value.size() << ")" << std::endl;
+                  << " [PIXEL] (" << value.size() << "), offset: " << std::hex << fileOffset << std::dec << std::endl;
+        pixelDataOffset_ = fileOffset;
+        return false;
       }
       else
       {
         std::cout << "Dataset: " << tag.Format() << " " << EnumerationToString(vr)
-                  << " [PIXEL] (" << value.size() << ")" << std::endl;
+                  << " [" << Toolbox::ConvertToAscii(value).c_str() << "] (" << value.size()
+                  << "), offset: " << std::hex << fileOffset << std::dec << std::endl;
       }
 
       map_.SetValue(tag, value, Toolbox::IsAsciiString(value));
                                                             
       return true;
     }
+
+    uint64_t GetPixelDataOffset() const
+    {
+      return pixelDataOffset_;
+    }
   };
 }
 
@@ -855,40 +871,81 @@
 TEST(DicomStreamReader, DISABLED_Tutu)
 {
   static const std::string PATH = "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/";
-  
-  std::string dicom;
-  SystemToolbox::ReadFile(dicom, PATH + "../ColorTestMalaterre.dcm", false);
-  //SystemToolbox::ReadFile(dicom, PATH + "1.2.840.10008.1.2.1.dcm", false);
-  //SystemToolbox::ReadFile(dicom, PATH + "1.2.840.10008.1.2.2.dcm", false);  // Big Endian
-  //SystemToolbox::ReadFile(dicom, PATH + "1.2.840.10008.1.2.4.50.dcm", false);
-  //SystemToolbox::ReadFile(dicom, PATH + "1.2.840.10008.1.2.4.51.dcm", false);
-  //SystemToolbox::ReadFile(dicom, PATH + "1.2.840.10008.1.2.4.57.dcm", false);
-  //SystemToolbox::ReadFile(dicom, PATH + "1.2.840.10008.1.2.4.70.dcm", false);
-  //SystemToolbox::ReadFile(dicom, PATH + "1.2.840.10008.1.2.4.80.dcm", false);
-  //SystemToolbox::ReadFile(dicom, PATH + "1.2.840.10008.1.2.4.81.dcm", false);
-  //SystemToolbox::ReadFile(dicom, PATH + "1.2.840.10008.1.2.4.90.dcm", false);
-  //SystemToolbox::ReadFile(dicom, PATH + "1.2.840.10008.1.2.4.91.dcm", false);
-  //SystemToolbox::ReadFile(dicom, PATH + "1.2.840.10008.1.2.5.dcm", false);
+
+  typedef std::list< std::pair<std::string, uint64_t> >  Sources;
+
+  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));
+
+  {
+    std::string dicom;
+
+    uint64_t offset;
+    // Not a DICOM image
+    SystemToolbox::ReadFile(dicom, PATH + "1.2.840.10008.1.2.4.50.png", false);
+    ASSERT_FALSE(DicomStreamReader::LookupPixelDataOffset(offset, dicom));
+
+    // Image without valid DICOM preamble
+    SystemToolbox::ReadFile(dicom, PATH + "1.2.840.10008.1.2.dcm", false);
+    ASSERT_FALSE(DicomStreamReader::LookupPixelDataOffset(offset, dicom));
+  }
   
-  std::stringstream stream;
-  size_t pos = 0;
-  
-  DicomStreamReader r(stream);
-  V visitor;
-
-  while (pos < dicom.size() &&
-         !r.IsDone())
+  for (Sources::const_iterator it = sources.begin(); it != sources.end(); ++it)
   {
-    //printf("."); 
-    //printf("%d\n", pos);
+    std::string dicom;
+    SystemToolbox::ReadFile(dicom, it->first, false);
+
+    {
+      uint64_t offset;
+      ASSERT_TRUE(DicomStreamReader::LookupPixelDataOffset(offset, dicom));
+      ASSERT_EQ(it->second, offset);
+    }
+    
+    ParsedDicomFile a(dicom);
+    Json::Value aa;
+    a.DatasetToJson(aa, DicomToJsonFormat_Short, DicomToJsonFlags_Default, 0);
+
+    std::stringstream stream;
+    size_t pos = 0;
+  
+    DicomStreamReader r(stream);
+    V visitor;
+
+    // Test reading byte per byte
+    while (pos < dicom.size() &&
+           !r.IsDone())
+    {
+      r.Consume(visitor);
+      stream.clear();
+      stream.put(dicom[pos++]);
+    }
+
     r.Consume(visitor);
-    stream.clear();
-    stream.put(dicom[pos++]);
-  }
+
+    ASSERT_EQ(it->second, visitor.GetPixelDataOffset());
+
+    // Truncate the original DICOM up to pixel data
+    dicom.resize(visitor.GetPixelDataOffset());
 
-  r.Consume(visitor);
+    ParsedDicomFile b(dicom);
+    Json::Value bb;
+    b.DatasetToJson(bb, DicomToJsonFormat_Short, DicomToJsonFlags_Default, 0);
 
-  printf(">> %d\n", static_cast<int>(r.GetProcessedBytes()));
+    aa.removeMember("7fe0,0010");
+    aa.removeMember("fffc,fffc");  // For "1.2.840.10008.1.2.5.dcm"
+    ASSERT_EQ(aa.toStyledString(), bb.toStyledString());
+  }
 }
 
 TEST(DicomStreamReader, DISABLED_Tutu2)