changeset 5323:138e9d0c08c1

added DicomMap::GuessPixelDataValueRepresentation()
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sun, 25 Jun 2023 12:29:39 +0200
parents a904a4caf5b7
children e95caa87fed8
files OrthancFramework/Sources/DicomFormat/DicomArray.cpp OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp OrthancFramework/Sources/DicomFormat/DicomImageInformation.h OrthancFramework/Sources/DicomFormat/DicomMap.cpp OrthancFramework/Sources/DicomFormat/DicomMap.h OrthancFramework/Sources/DicomFormat/DicomValue.cpp OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp
diffstat 9 files changed, 166 insertions(+), 59 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Sources/DicomFormat/DicomArray.cpp	Sun Jun 25 11:48:47 2023 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomArray.cpp	Sun Jun 25 12:29:39 2023 +0200
@@ -86,7 +86,21 @@
     {
       DicomTag t = elements_[i]->GetTag();
       const DicomValue& v = elements_[i]->GetValue();
-      std::string s = v.IsNull() ? "(null)" : v.GetContent();
+
+      std::string s;
+      if (v.IsNull())
+      {
+        s = "(null)";
+      }
+      else if (v.IsSequence())
+      {
+        s = "(sequence)";
+      }
+      else
+      {
+        s = v.GetContent();
+      }
+
       printf("0x%04x 0x%04x [%s]\n", t.GetGroup(), t.GetElement(), s.c_str());
     }
   }
--- a/OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp	Sun Jun 25 11:48:47 2023 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp	Sun Jun 25 12:29:39 2023 +0200
@@ -423,4 +423,46 @@
   {
     return 256;
   }
+
+
+  ValueRepresentation DicomImageInformation::GuessPixelDataValueRepresentation(const DicomTransferSyntax& transferSyntax,
+                                                                               unsigned int bitsAllocated)
+  {
+    /**
+     * This approach is validated in "Tests/GuessPixelDataVR.py":
+     * https://hg.orthanc-server.com/orthanc-tests/file/tip/Tests/GuessPixelDataVR.py
+     **/
+
+    if (transferSyntax == DicomTransferSyntax_LittleEndianExplicit ||
+        transferSyntax == DicomTransferSyntax_BigEndianExplicit)
+    {
+      /**
+       * Same rules apply to Little Endian Explicit and Big Endian
+       * Explicit (now retired). The VR of the pixel data directly
+       * depends upon the "Bits Allocated (0028,0100)" tag:
+       * https://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_A.2.html
+       * https://dicom.nema.org/medical/dicom/2016b/output/chtml/part05/sect_A.3.html
+       **/
+      if (bitsAllocated > 8)
+      {
+        return ValueRepresentation_OtherWord;
+      }
+      else
+      {
+        return ValueRepresentation_OtherByte;
+      }
+    }
+    else if (transferSyntax == DicomTransferSyntax_LittleEndianImplicit)
+    {
+      // Assume "OW" for DICOM Implicit VR Little Endian Transfer Syntax
+      // https://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_A.html#sect_A.1
+      return ValueRepresentation_OtherWord;
+    }
+    else
+    {
+      // Assume "OB" for all the compressed transfer syntaxes
+      // https://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_A.4.html
+      return ValueRepresentation_OtherByte;
+    }
+  }
 }
--- a/OrthancFramework/Sources/DicomFormat/DicomImageInformation.h	Sun Jun 25 11:48:47 2023 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomImageInformation.h	Sun Jun 25 12:29:39 2023 +0200
@@ -95,5 +95,8 @@
      * was implicitly used in Orthanc <= 1.7.2.
      **/
     static unsigned int GetUsefulTagLength();
+
+    static ValueRepresentation GuessPixelDataValueRepresentation(const DicomTransferSyntax& transferSyntax,
+                                                                 unsigned int bitsAllocated);
   };
 }
--- a/OrthancFramework/Sources/DicomFormat/DicomMap.cpp	Sun Jun 25 11:48:47 2023 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomMap.cpp	Sun Jun 25 12:29:39 2023 +0200
@@ -34,6 +34,7 @@
 #include "../OrthancException.h"
 #include "../Toolbox.h"
 #include "DicomArray.h"
+#include "DicomImageInformation.h"
 
 #if ORTHANC_ENABLE_DCMTK == 1
 #include "../DicomParsing/FromDcmtkBridge.h"
@@ -418,7 +419,7 @@
     SetValueInternal(group, element, new DicomValue(str, isBinary));
   }
 
-  void DicomMap::SetValue(const DicomTag& tag, const Json::Value& value)
+  void DicomMap::SetSequenceValue(const DicomTag& tag, const Json::Value& value)
   {
     SetValueInternal(tag.GetGroup(), tag.GetElement(), new DicomValue(value));
   }
@@ -1456,7 +1457,7 @@
         }
         else
         {
-          SetValue(tag, value["Value"]);
+          SetSequenceValue(tag, value["Value"]);
         }
       }
     }
@@ -1530,7 +1531,7 @@
     {
       if (it->second->IsSequence())
       {
-        result.SetValue(it->first, it->second->GetSequenceContent());
+        result.SetSequenceValue(it->first, it->second->GetSequenceContent());
       }
     }
   }
@@ -1808,6 +1809,19 @@
   }
   
 
+  ValueRepresentation DicomMap::GuessPixelDataValueRepresentation(DicomTransferSyntax transferSyntax) const
+  {
+    const DicomValue* value = TestAndGetValue(DICOM_TAG_BITS_ALLOCATED);
+
+    uint32_t bitsAllocated;
+    if (value == NULL ||
+        !value->ParseUnsignedInteger32(bitsAllocated))
+    {
+      bitsAllocated = 8;
+    }
+
+    return DicomImageInformation::GuessPixelDataValueRepresentation(transferSyntax, bitsAllocated);
+  }
   
 
   void DicomMap::Print(FILE* fp) const
--- a/OrthancFramework/Sources/DicomFormat/DicomMap.h	Sun Jun 25 11:48:47 2023 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomMap.h	Sun Jun 25 12:29:39 2023 +0200
@@ -85,8 +85,8 @@
                   const std::string& str,
                   bool isBinary);
 
-    void SetValue(const DicomTag& tag,
-                  const Json::Value& value);
+    void SetSequenceValue(const DicomTag& tag,
+                          const Json::Value& value);
 
     bool HasTag(uint16_t group, uint16_t element) const;
 
@@ -230,6 +230,8 @@
     void DumpMainDicomTags(Json::Value& target,
                            ResourceType level) const;
 
+    ValueRepresentation GuessPixelDataValueRepresentation(DicomTransferSyntax transferSyntax) const;
+
     void Print(FILE* fp) const;  // For debugging only
   };
 }
--- a/OrthancFramework/Sources/DicomFormat/DicomValue.cpp	Sun Jun 25 11:48:47 2023 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomValue.cpp	Sun Jun 25 12:29:39 2023 +0200
@@ -66,6 +66,10 @@
     type_(Type_SequenceAsJson),
     sequenceJson_(value)
   {
+    if (value.type() != Json::arrayValue)
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
   }
   
   const std::string& DicomValue::GetContent() const
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Sun Jun 25 11:48:47 2023 +0200
+++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Sun Jun 25 12:29:39 2023 +0200
@@ -582,8 +582,8 @@
                           ignoreTagLength, 1);
           }
 
-          target.SetValue(DicomTag(element->getTag().getGTag(), element->getTag().getETag()),
-                          jsonSequence);
+          target.SetSequenceValue(DicomTag(element->getTag().getGTag(), element->getTag().getETag()),
+                                  jsonSequence);
         }
       }
     }
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Sun Jun 25 11:48:47 2023 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Sun Jun 25 12:29:39 2023 +0200
@@ -76,6 +76,7 @@
 #include "Internals/DicomImageDecoder.h"
 #include "ToDcmtkBridge.h"
 
+#include "../DicomFormat/DicomImageInformation.h"
 #include "../Images/Image.h"
 #include "../Images/ImageProcessing.h"
 #include "../Images/PamReader.h"
@@ -2179,49 +2180,18 @@
 
   ValueRepresentation ParsedDicomFile::GuessPixelDataValueRepresentation() const
   {
-    /**
-     * This approach is validated in "Tests/GuessPixelDataVR.py":
-     * https://hg.orthanc-server.com/orthanc-tests/file/tip/Tests/GuessPixelDataVR.py
-     **/
-    
     DicomTransferSyntax ts;
     if (LookupTransferSyntax(ts))
     {
-      if (ts == DicomTransferSyntax_LittleEndianExplicit ||
-          ts == DicomTransferSyntax_BigEndianExplicit)
+      DcmItem& dataset = *GetDcmtkObjectConst().getDataset();
+
+      uint16_t bitsAllocated;
+      if (!dataset.findAndGetUint16(DCM_BitsAllocated, bitsAllocated).good())
       {
-        /**
-         * Same rules apply to Little Endian Explicit and Big Endian
-         * Explicit (now retired). The VR of the pixel data directly
-         * depends upon the "Bits Allocated (0028,0100)" tag:
-         * https://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_A.2.html
-         * https://dicom.nema.org/medical/dicom/2016b/output/chtml/part05/sect_A.3.html
-         **/
-        DcmItem& dataset = *GetDcmtkObjectConst().getDataset();
-        
-        uint16_t bitsAllocated;
-        if (dataset.findAndGetUint16(DCM_BitsAllocated, bitsAllocated).good() &&
-            bitsAllocated > 8)
-        {
-          return ValueRepresentation_OtherWord;
-        }
-        else
-        {
-          return ValueRepresentation_OtherByte;
-        }
+        bitsAllocated = 8;
       }
-      else if (ts == DicomTransferSyntax_LittleEndianImplicit)
-      {
-        // Assume "OW" for DICOM Implicit VR Little Endian Transfer Syntax
-        // https://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_A.html#sect_A.1
-        return ValueRepresentation_OtherWord;
-      }
-      else
-      {
-        // Assume "OB" for all the compressed transfer syntaxes
-        // https://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_A.4.html
-        return ValueRepresentation_OtherByte;
-      }
+
+      return DicomImageInformation::GuessPixelDataValueRepresentation(ts, bitsAllocated);
     }
     else
     {
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Sun Jun 25 11:48:47 2023 +0200
+++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Sun Jun 25 12:29:39 2023 +0200
@@ -37,6 +37,7 @@
 #include <gtest/gtest.h>
 
 #include "../Sources/Compatibility.h"
+#include "../Sources/DicomFormat/DicomImageInformation.h"
 #include "../Sources/DicomFormat/DicomPath.h"
 #include "../Sources/DicomNetworking/DicomFindAnswers.h"
 #include "../Sources/DicomParsing/DicomModification.h"
@@ -3351,24 +3352,55 @@
     for (Syntaxes::const_iterator it = compressedSyntaxes.begin(); it != compressedSyntaxes.end(); ++it)
     {
       // All the compressed transfer syntaxes must have "OB" pixel data
-      ParsedDicomFile dicom(true);
-      ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, bitsAllocated).good());
-      ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(it->first, NULL).good());
-      dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
-      DicomTransferSyntax ts;
-      ASSERT_TRUE(dicom.LookupTransferSyntax(ts));
-      ASSERT_EQ(ts, it->second);
-      ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation());
+      ASSERT_EQ(ValueRepresentation_OtherByte, DicomImageInformation::GuessPixelDataValueRepresentation(it->second, bitsAllocated));
+
+      {
+        DicomMap dicom;
+        dicom.SetValue(DICOM_TAG_BITS_ALLOCATED, boost::lexical_cast<std::string>(bitsAllocated), false);
+        ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation(it->second));
+      }
+
+      {
+        DicomMap dicom;
+        ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation(it->second));
+      }
+
+      {
+        ParsedDicomFile dicom(true);
+        ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, bitsAllocated).good());
+        ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(it->first, NULL).good());
+        dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
+        DicomTransferSyntax ts;
+        ASSERT_TRUE(dicom.LookupTransferSyntax(ts));
+        ASSERT_EQ(ts, it->second);
+        ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation());
+      }
     }
 
     {
       // Little endian implicit is always OW
-      ParsedDicomFile dicom(true);
-      ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, bitsAllocated).good());
-      ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(EXS_LittleEndianImplicit, NULL).good());
-      dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
-      ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation());
+      ASSERT_EQ(ValueRepresentation_OtherWord, DicomImageInformation::GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianImplicit, bitsAllocated));
+
+      {
+        DicomMap dicom;
+        dicom.SetValue(DICOM_TAG_BITS_ALLOCATED, boost::lexical_cast<std::string>(bitsAllocated), false);
+        ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianImplicit));
+      }
+
+      {
+        DicomMap dicom;
+        ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianImplicit));
+      }
+
+      {
+        ParsedDicomFile dicom(true);
+        ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, bitsAllocated).good());
+        ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(EXS_LittleEndianImplicit, NULL).good());
+        dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
+        ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation());
+      }
     }
+
   }
 
   // Explicit little and big endian with <= 8 bpp is OB
@@ -3383,6 +3415,22 @@
       default:
         throw OrthancException(ErrorCode_InternalError);
     }
+
+    ASSERT_EQ(ValueRepresentation_OtherByte, DicomImageInformation::GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianExplicit, bitsAllocated));
+    ASSERT_EQ(ValueRepresentation_OtherByte, DicomImageInformation::GuessPixelDataValueRepresentation(DicomTransferSyntax_BigEndianExplicit, bitsAllocated));
+
+    {
+      DicomMap dicom;
+      dicom.SetValue(DICOM_TAG_BITS_ALLOCATED, boost::lexical_cast<std::string>(bitsAllocated), false);
+      ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianExplicit));
+      ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_BigEndianExplicit));
+    }
+
+    {
+      DicomMap dicom;
+      ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianExplicit));
+      ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_BigEndianExplicit));
+    }
     
     {
       ParsedDicomFile dicom(true);
@@ -3402,6 +3450,16 @@
   }
 
   // Explicit little and big endian with > 8 bpp is OW
+  
+  ASSERT_EQ(ValueRepresentation_OtherWord, DicomImageInformation::GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianExplicit, 16));
+  ASSERT_EQ(ValueRepresentation_OtherWord, DicomImageInformation::GuessPixelDataValueRepresentation(DicomTransferSyntax_BigEndianExplicit, 16));
+
+  {
+    DicomMap dicom;
+    dicom.SetValue(DICOM_TAG_BITS_ALLOCATED, "16", false);
+    ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianExplicit));
+    ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_BigEndianExplicit));
+  }
 
   {
     ParsedDicomFile dicom(true);