changeset 5322:a904a4caf5b7

unit testing ParsedDicomFile::GuessPixelDataValueRepresentation()
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sun, 25 Jun 2023 11:48:47 +0200
parents 5fae323b11ed
children 138e9d0c08c1
files OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp
diffstat 2 files changed, 123 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Sat Jun 24 12:43:10 2023 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Sun Jun 25 11:48:47 2023 +0200
@@ -2180,16 +2180,8 @@
   ValueRepresentation ParsedDicomFile::GuessPixelDataValueRepresentation() const
   {
     /**
-     * DICOM specification is at:
-     * https://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_d.html
-     *
-     * Our algorithm for guessing the pixel data VR is imperfect, and
-     * inspired from: https://forum.dcmtk.org/viewtopic.php?t=4961
-     *
-     * "The baseline for Little Endian Implicit/Explicit is: (a) if
-     * the TS is Explicit Little Endian and the pixeldata is <= 8bpp,
-     * VR of pixel data shall be VR_OB, and (b) in all other cases, VR
-     * of pixel data shall be VR_OW."
+     * This approach is validated in "Tests/GuessPixelDataVR.py":
+     * https://hg.orthanc-server.com/orthanc-tests/file/tip/Tests/GuessPixelDataVR.py
      **/
     
     DicomTransferSyntax ts;
@@ -2198,6 +2190,13 @@
       if (ts == DicomTransferSyntax_LittleEndianExplicit ||
           ts == 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
+         **/
         DcmItem& dataset = *GetDcmtkObjectConst().getDataset();
         
         uint16_t bitsAllocated;
@@ -2213,17 +2212,20 @@
       }
       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;
       }
     }
     else
     {
-      // Assume "OB" if transfer syntax is not available
+      // Assume "OB" if the transfer syntax is unknown
       return ValueRepresentation_OtherByte;
     }
   }
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Sat Jun 24 12:43:10 2023 +0200
+++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Sun Jun 25 11:48:47 2023 +0200
@@ -3264,7 +3264,6 @@
 }
 
 
-#include "../Sources/DicomFormat/DicomArray.h"
 TEST(ParsedDicomFile, RemoveFromPixelData)
 {
   ParsedDicomFile dicom(true);
@@ -3312,6 +3311,116 @@
 }
 
 
+TEST(ParsedDicomFile, GuessPixelDataValueRepresentation)
+{
+  typedef std::list< std::pair<E_TransferSyntax, DicomTransferSyntax> > Syntaxes;
+
+  // Create a list of the main non-retired transfer syntaxes, from:
+  // https://www.dicomlibrary.com/dicom/transfer-syntax/
+  Syntaxes compressedSyntaxes;
+  compressedSyntaxes.push_back(std::make_pair(EXS_DeflatedLittleEndianExplicit, DicomTransferSyntax_DeflatedLittleEndianExplicit));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEGProcess1, DicomTransferSyntax_JPEGProcess1));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEGProcess2_4, DicomTransferSyntax_JPEGProcess2_4));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEGProcess14, DicomTransferSyntax_JPEGProcess14));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEGProcess14SV1, DicomTransferSyntax_JPEGProcess14SV1));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEGLSLossless, DicomTransferSyntax_JPEGLSLossless));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEGLSLossy, DicomTransferSyntax_JPEGLSLossy));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEG2000LosslessOnly, DicomTransferSyntax_JPEG2000LosslessOnly));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEG2000, DicomTransferSyntax_JPEG2000));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEG2000MulticomponentLosslessOnly, DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPEG2000Multicomponent, DicomTransferSyntax_JPEG2000Multicomponent));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPIPReferenced, DicomTransferSyntax_JPIPReferenced));
+  compressedSyntaxes.push_back(std::make_pair(EXS_JPIPReferencedDeflate, DicomTransferSyntax_JPIPReferencedDeflate));
+  compressedSyntaxes.push_back(std::make_pair(EXS_RLELossless, DicomTransferSyntax_RLELossless));
+  compressedSyntaxes.push_back(std::make_pair(EXS_MPEG2MainProfileAtMainLevel, DicomTransferSyntax_MPEG2MainProfileAtMainLevel));
+  compressedSyntaxes.push_back(std::make_pair(EXS_MPEG4HighProfileLevel4_1, DicomTransferSyntax_MPEG4HighProfileLevel4_1));
+  compressedSyntaxes.push_back(std::make_pair(EXS_MPEG4BDcompatibleHighProfileLevel4_1, DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1));
+
+  for (unsigned int i = 0; i < 3; i++)
+  {
+    unsigned int bitsAllocated;
+    switch (i)
+    {
+      case 0: bitsAllocated = 1;   break;
+      case 1: bitsAllocated = 8;   break;
+      case 2: bitsAllocated = 16;  break;
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+      
+    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());
+    }
+
+    {
+      // 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());
+    }
+  }
+
+  // Explicit little and big endian with <= 8 bpp is OB
+
+  for (unsigned int i = 0; i < 2; i++)
+  {
+    unsigned int bitsAllocated;
+    switch (i)
+    {
+      case 0: bitsAllocated = 1;   break;
+      case 1: bitsAllocated = 8;   break;
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+    
+    {
+      ParsedDicomFile dicom(true);
+      ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, bitsAllocated).good());
+      ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(EXS_LittleEndianExplicit, NULL).good());
+      dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
+      ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation());
+    }
+
+    {
+      ParsedDicomFile dicom(true);
+      ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, bitsAllocated).good());
+      ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(EXS_BigEndianExplicit, NULL).good());
+      dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
+      ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation());
+    }
+  }
+
+  // Explicit little and big endian with > 8 bpp is OW
+
+  {
+    ParsedDicomFile dicom(true);
+    ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, 16).good());
+    ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(EXS_LittleEndianExplicit, NULL).good());
+    dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
+    ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation());
+  }
+
+  {
+    ParsedDicomFile dicom(true);
+    ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, 16).good());
+    ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(EXS_BigEndianExplicit, NULL).good());
+    dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
+    ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation());
+  }
+}
+
+
 TEST(ParsedDicomFile, DISABLED_InjectEmptyPixelData2)
 {
   static const char* PIXEL_DATA = "7FE00010";