Mercurial > hg > orthanc
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);