# HG changeset patch # User Sebastien Jodogne # Date 1687686527 -7200 # Node ID a904a4caf5b721ad70dd1fda6ddebf314d49e3b6 # Parent 5fae323b11edc76c7cce7ce50298c8dc1bd2ef1c unit testing ParsedDicomFile::GuessPixelDataValueRepresentation() diff -r 5fae323b11ed -r a904a4caf5b7 OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp --- 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; } } diff -r 5fae323b11ed -r a904a4caf5b7 OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp --- 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 > 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";