# HG changeset patch # User Sebastien Jodogne # Date 1612435345 -3600 # Node ID b57ca702a4304cd555e3e27c6248d20234dc3469 # Parent 0b2484663233d8f6b856563410f34fd22224f1f5 DicomStreamReader::LookupPixelDataOffset() diff -r 0b2484663233 -r b57ca702a430 OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp --- 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 +#include + 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; + } + } } diff -r 0b2484663233 -r b57ca702a430 OrthancFramework/Sources/DicomFormat/DicomStreamReader.h --- 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); }; } diff -r 0b2484663233 -r b57ca702a430 OrthancFramework/UnitTestsSources/DicomMapTests.cpp --- 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 > 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(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)