# HG changeset patch # User Sebastien Jodogne # Date 1623083746 -7200 # Node ID d38a7040474aa9f090d6cd217f53e2c340c00c70 # Parent c5528c7847a688bbaff0e4a8f8c5c101042b5a78 FromDcmtkBridge::RemovePath() and FromDcmtkBridge::ReplacePath() diff -r c5528c7847a6 -r d38a7040474a OrthancFramework/Sources/DicomFormat/DicomPath.cpp --- a/OrthancFramework/Sources/DicomFormat/DicomPath.cpp Mon Jun 07 17:05:48 2021 +0200 +++ b/OrthancFramework/Sources/DicomFormat/DicomPath.cpp Mon Jun 07 18:35:46 2021 +0200 @@ -154,6 +154,20 @@ } + bool DicomPath::HasUniversal() const + { + for (size_t i = 0; i < prefix_.size(); i++) + { + if (prefix_[i].IsUniversal()) + { + return true; + } + } + + return false; + } + + std::string DicomPath::Format() const { std::string s; @@ -176,8 +190,7 @@ } - DicomPath DicomPath::Parse(const std::string& s, - bool allowUniversal) + DicomPath DicomPath::Parse(const std::string& s) { std::vector tokens; Toolbox::TokenizeString(tokens, s, '.'); @@ -221,14 +234,7 @@ std::string s = Toolbox::StripSpaces(right.substr(0, right.size() - 1)); if (s == "*") { - if (allowUniversal) - { - path.AddUniversalTagToPrefix(tag); - } - else - { - throw OrthancException(ErrorCode_ParameterOutOfRange, "Cannot create an universal parent path"); - } + path.AddUniversalTagToPrefix(tag); } else { diff -r c5528c7847a6 -r d38a7040474a OrthancFramework/Sources/DicomFormat/DicomPath.h --- a/OrthancFramework/Sources/DicomFormat/DicomPath.h Mon Jun 07 17:05:48 2021 +0200 +++ b/OrthancFramework/Sources/DicomFormat/DicomPath.h Mon Jun 07 18:35:46 2021 +0200 @@ -29,7 +29,7 @@ namespace Orthanc { - class DicomPath + class ORTHANC_PUBLIC DicomPath { private: class PrefixItem @@ -129,9 +129,10 @@ return GetLevel(level).GetIndex(); } + bool HasUniversal() const; + std::string Format() const; - static DicomPath Parse(const std::string& s, - bool allowUniversal); + static DicomPath Parse(const std::string& s); }; } diff -r c5528c7847a6 -r d38a7040474a OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp --- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Mon Jun 07 17:05:48 2021 +0200 +++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Mon Jun 07 18:35:46 2021 +0200 @@ -2794,6 +2794,121 @@ DicomMap::LogMissingTagsForStore(patientId, studyInstanceUid, seriesInstanceUid, sopInstanceUid); } + + + static void ApplyInternal(FromDcmtkBridge::IDicomPathVisitor& visitor, + DcmItem& item, + const DicomPath& path, + size_t level) + { + if (level == path.GetPrefixLength()) + { + visitor.Visit(item, path.GetFinalTag()); + } + else + { + const DicomTag& tmp = path.GetPrefixTag(level); + DcmTagKey tag(tmp.GetGroup(), tmp.GetElement()); + + DcmSequenceOfItems *sequence = NULL; + if (item.findAndGetSequence(tag, sequence).good() && + sequence != NULL) + { + for (unsigned long i = 0; i < sequence->card(); i++) + { + if (path.IsPrefixUniversal(level) || + path.GetPrefixIndex(level) == static_cast(i)) + { + DcmItem *child = sequence->getItem(i); + if (child != NULL) + { + ApplyInternal(visitor, *child, path, level + 1); + } + } + } + } + } + } + + + void FromDcmtkBridge::Apply(IDicomPathVisitor& visitor, + DcmDataset& dataset, + const DicomPath& path) + { + ApplyInternal(visitor, dataset, path, 0); + } + + + void FromDcmtkBridge::RemovePath(DcmDataset& dataset, + const DicomPath& path) + { + class Visitor : public FromDcmtkBridge::IDicomPathVisitor + { + public: + virtual void Visit(DcmItem& item, + const DicomTag& tag) ORTHANC_OVERRIDE + { + DcmTagKey tmp(tag.GetGroup(), tag.GetElement()); + std::unique_ptr removed(item.remove(tmp)); + } + }; + + Visitor visitor; + Apply(visitor, dataset, path); + } + + + void FromDcmtkBridge::ReplacePath(DcmDataset& dataset, + const DicomPath& path, + const DcmElement& element) + { + class Visitor : public FromDcmtkBridge::IDicomPathVisitor + { + private: + std::unique_ptr element_; + + public: + Visitor(const DcmElement& element) : + element_(dynamic_cast(element.clone())) + { + if (element_.get() == NULL) + { + throw OrthancException(ErrorCode_InternalError, "Cannot clone DcmElement"); + } + } + + virtual void Visit(DcmItem& item, + const DicomTag& tag) ORTHANC_OVERRIDE + { + std::unique_ptr cloned(dynamic_cast(element_->clone())); + if (cloned.get() == NULL) + { + throw OrthancException(ErrorCode_InternalError, "Cannot clone DcmElement"); + } + else + { + DcmTagKey tmp(tag.GetGroup(), tag.GetElement()); + if (!item.insert(cloned.release(), OFTrue /* replace old */).good()) + { + throw OrthancException(ErrorCode_InternalError, "Cannot replace an element"); + } + } + } + }; + + DcmTagKey tmp(path.GetFinalTag().GetGroup(), path.GetFinalTag().GetElement()); + + if (element.getTag() != tmp) + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "The final tag must be the same as the tag of the element during a replacement"); + } + else + { + Visitor visitor(element); + Apply(visitor, dataset, path); + } + } } diff -r c5528c7847a6 -r d38a7040474a OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h --- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h Mon Jun 07 17:05:48 2021 +0200 +++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h Mon Jun 07 18:35:46 2021 +0200 @@ -25,6 +25,7 @@ #include "ITagVisitor.h" #include "../DicomFormat/DicomElement.h" #include "../DicomFormat/DicomMap.h" +#include "../DicomFormat/DicomPath.h" #include #include @@ -59,6 +60,20 @@ friend class ParsedDicomFile; + public: + // New in Orthanc 1.9.4 + class ORTHANC_PUBLIC IDicomPathVisitor : public boost::noncopyable + { + public: + virtual ~IDicomPathVisitor() + { + } + + virtual void Visit(DcmItem& item, + const DicomTag& tag) = 0; + }; + + private: FromDcmtkBridge(); // Pure static class @@ -226,5 +241,16 @@ DcmDataset& dicom); static void LogMissingTagsForStore(DcmDataset& dicom); + + static void Apply(IDicomPathVisitor& visitor, + DcmDataset& dataset, + const DicomPath& path); + + static void RemovePath(DcmDataset& dataset, + const DicomPath& path); + + static void ReplacePath(DcmDataset& dataset, + const DicomPath& path, + const DcmElement& element); }; } diff -r c5528c7847a6 -r d38a7040474a OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp --- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp Mon Jun 07 17:05:48 2021 +0200 +++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp Mon Jun 07 18:35:46 2021 +0200 @@ -2265,28 +2265,33 @@ static const DicomTag DICOM_TAG_ACQUISITION_MATRIX(0x0018, 0x1310); static const DicomTag DICOM_TAG_REFERENCED_PERFORMED_PROCEDURE_STEP_SEQUENCE(0x0008, 0x1111); - DicomPath path = DicomPath::Parse("(0010,0010)", true); + DicomPath path = DicomPath::Parse("(0010,0010)"); + ASSERT_FALSE(path.HasUniversal()); ASSERT_EQ(0u, path.GetPrefixLength()); ASSERT_EQ(DICOM_TAG_PATIENT_NAME, path.GetFinalTag()); ASSERT_THROW(path.GetPrefixTag(0), OrthancException); - path = DicomPath::Parse("0018,1310", true); + path = DicomPath::Parse("0018,1310"); + ASSERT_FALSE(path.HasUniversal()); ASSERT_EQ(0u, path.GetPrefixLength()); ASSERT_EQ(DICOM_TAG_ACQUISITION_MATRIX, path.GetFinalTag()); ASSERT_EQ("(0018,1310)", path.Format()); // The following sample won't work without DCMTK - path = DicomPath::Parse("PatientID", true); + path = DicomPath::Parse("PatientID"); + ASSERT_FALSE(path.HasUniversal()); ASSERT_EQ(0u, path.GetPrefixLength()); ASSERT_EQ(DICOM_TAG_PATIENT_ID, path.GetFinalTag()); ASSERT_EQ("(0010,0020)", path.Format()); - path = DicomPath::Parse("(0018,1310)", true); + path = DicomPath::Parse("(0018,1310)"); + ASSERT_FALSE(path.HasUniversal()); ASSERT_EQ(0u, path.GetPrefixLength()); ASSERT_EQ(DICOM_TAG_ACQUISITION_MATRIX, path.GetFinalTag()); ASSERT_EQ("(0018,1310)", path.Format()); - path = DicomPath::Parse("(0008,1111)[0].PatientName", true); + path = DicomPath::Parse("(0008,1111)[0].PatientName"); + ASSERT_FALSE(path.HasUniversal()); ASSERT_EQ(1u, path.GetPrefixLength()); ASSERT_EQ(DICOM_TAG_REFERENCED_PERFORMED_PROCEDURE_STEP_SEQUENCE, path.GetPrefixTag(0)); ASSERT_FALSE(path.IsPrefixUniversal(0)); @@ -2294,7 +2299,8 @@ ASSERT_THROW(path.GetPrefixTag(1), OrthancException); ASSERT_EQ(DICOM_TAG_PATIENT_NAME, path.GetFinalTag()); - path = DicomPath::Parse("(0008,1111)[1].(0008,1111)[2].(0010,0010)", true); + path = DicomPath::Parse("(0008,1111)[1].(0008,1111)[2].(0010,0010)"); + ASSERT_FALSE(path.HasUniversal()); ASSERT_EQ(2u, path.GetPrefixLength()); ASSERT_EQ(DICOM_TAG_REFERENCED_PERFORMED_PROCEDURE_STEP_SEQUENCE, path.GetPrefixTag(0)); ASSERT_FALSE(path.IsPrefixUniversal(0)); @@ -2305,7 +2311,8 @@ ASSERT_THROW(path.GetPrefixTag(2), OrthancException); ASSERT_EQ(DICOM_TAG_PATIENT_NAME, path.GetFinalTag()); - path = DicomPath::Parse("(0008,1111)[*].PatientName", true); + path = DicomPath::Parse("(0008,1111)[*].PatientName"); + ASSERT_TRUE(path.HasUniversal()); ASSERT_EQ(1u, path.GetPrefixLength()); ASSERT_EQ(DICOM_TAG_REFERENCED_PERFORMED_PROCEDURE_STEP_SEQUENCE, path.GetPrefixTag(0)); ASSERT_TRUE(path.IsPrefixUniversal(0)); @@ -2314,9 +2321,8 @@ ASSERT_EQ(DICOM_TAG_PATIENT_NAME, path.GetFinalTag()); ASSERT_EQ("(0008,1111)[*].(0010,0010)", path.Format()); - ASSERT_THROW(DicomPath::Parse("(0008,1111)[*].PatientName", false), OrthancException); - - path = DicomPath::Parse("(0008,1111)[1].(0008,1111)[*].(0010,0010)", true); + path = DicomPath::Parse("(0008,1111)[1].(0008,1111)[*].(0010,0010)"); + ASSERT_TRUE(path.HasUniversal()); ASSERT_EQ(2u, path.GetPrefixLength()); ASSERT_EQ(DICOM_TAG_REFERENCED_PERFORMED_PROCEDURE_STEP_SEQUENCE, path.GetPrefixTag(0)); ASSERT_FALSE(path.IsPrefixUniversal(0)); @@ -2327,7 +2333,8 @@ ASSERT_THROW(path.GetPrefixTag(2), OrthancException); ASSERT_EQ(DICOM_TAG_PATIENT_NAME, path.GetFinalTag()); - path = DicomPath::Parse("PatientID[1].PatientName", true); + path = DicomPath::Parse("PatientID[1].PatientName"); + ASSERT_FALSE(path.HasUniversal()); ASSERT_EQ(1u, path.GetPrefixLength()); ASSERT_EQ(DICOM_TAG_PATIENT_ID, path.GetPrefixTag(0)); ASSERT_FALSE(path.IsPrefixUniversal(0)); @@ -2335,7 +2342,8 @@ ASSERT_THROW(path.GetPrefixTag(1), OrthancException); ASSERT_EQ(DICOM_TAG_PATIENT_NAME, path.GetFinalTag()); - path = DicomPath::Parse(" PatientID [ 42 ] . PatientName ", true); + path = DicomPath::Parse(" PatientID [ 42 ] . PatientName "); + ASSERT_FALSE(path.HasUniversal()); ASSERT_EQ(1u, path.GetPrefixLength()); ASSERT_EQ(DICOM_TAG_PATIENT_ID, path.GetPrefixTag(0)); ASSERT_FALSE(path.IsPrefixUniversal(0)); @@ -2344,12 +2352,125 @@ ASSERT_EQ(DICOM_TAG_PATIENT_NAME, path.GetFinalTag()); ASSERT_EQ("(0010,0020)[42].(0010,0010)", path.Format()); - ASSERT_THROW(DicomPath::Parse("nope", true), OrthancException); - ASSERT_THROW(DicomPath::Parse("(0010,0010)[.PatientID", true), OrthancException); - ASSERT_THROW(DicomPath::Parse("(0010,0010)[].PatientID", true), OrthancException); - ASSERT_THROW(DicomPath::Parse("(0010,0010[].PatientID", true), OrthancException); - ASSERT_THROW(DicomPath::Parse("(0010,0010)0].PatientID", true), OrthancException); - ASSERT_THROW(DicomPath::Parse("(0010,0010)[-1].PatientID", true), OrthancException); + ASSERT_THROW(DicomPath::Parse("nope"), OrthancException); + ASSERT_THROW(DicomPath::Parse("(0010,0010)[.PatientID"), OrthancException); + ASSERT_THROW(DicomPath::Parse("(0010,0010)[].PatientID"), OrthancException); + ASSERT_THROW(DicomPath::Parse("(0010,0010[].PatientID"), OrthancException); + ASSERT_THROW(DicomPath::Parse("(0010,0010)0].PatientID"), OrthancException); + ASSERT_THROW(DicomPath::Parse("(0010,0010)[-1].PatientID"), OrthancException); +} + + + +TEST(ParsedDicomFile, RemovePath) +{ + { + Json::Value v = Json::arrayValue; + + Json::Value item = Json::objectValue; + item["PatientID"] = "HELLO"; + v.append(item); + + std::unique_ptr d(FromDcmtkBridge::FromJson(DICOM_TAG_SOURCE_IMAGE_SEQUENCE, + v, false, Encoding_Latin1, "")); + d->writeXML(std::cout); + } + + { + Json::Value v = "Hello"; + std::unique_ptr d(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_ID, + v, false, Encoding_Latin1, "")); + d->writeXML(std::cout); + } + + printf("\n"); + + { + Json::Value v = Json::objectValue; + v["PatientID"] = "Hello"; + + { + Json::Value a = Json::arrayValue; + + { + Json::Value item = Json::objectValue; + item["ReferencedSOPClassUID"] = "1.2.840.10008.5.1.4.1.1.4"; + item["ReferencedSOPInstanceUID"] = "1.2.840.113619.2.176.2025.1499492.7040.1171286241.719"; + a.append(item); + } + + { + Json::Value item = Json::objectValue; + item["ReferencedSOPClassUID"] = "1.2.840.10008.5.1.4.1.1.4"; + item["ReferencedSOPInstanceUID"] = "1.2.840.113619.2.176.2025.1499492.7040.1171286241.726"; + a.append(item); + } + + v["ReferencedImageSequence"] = a; + } + + { + Json::Value a = Json::arrayValue; + + { + Json::Value item = Json::objectValue; + item["StudyInstanceUID"] = "1.2.840.113704.1.111.7016.1342451220.40"; + + { + Json::Value b = Json::arrayValue; + + { + Json::Value c = Json::objectValue; + c["CodeValue"] = "122403"; + b.append(c); + } + + item["PurposeOfReferenceCodeSequence"] = b; + } + + a.append(item); + } + + v["RelatedSeriesSequence"] = a; + } + + std::unique_ptr d(FromDcmtkBridge::FromJson(v, false /* generate UID */, false, Encoding_Latin1, "")); + + static const DicomTag DICOM_TAG_REFERENCED_SOP_CLASS_UID(0x0008, 0x1150); + static const DicomTag DICOM_TAG_REFERENCED_IMAGE_SEQUENCE(0x0008, 0x1140); + + DicomPath path(DICOM_TAG_REFERENCED_SOP_CLASS_UID); + path.AddIndexedTagToPrefix(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE, 2); + //path.AddUniversalTagToPrefix(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE); + + //DicomPath path(DicomTag(0x0008, 0x0100)); + //path.AddIndexedTagToPrefix(DicomTag(0x0008, 0x1250), 0); + //path.AddIndexedTagToPrefix(DicomTag(0x0040, 0xa170), 1); + + //FromDcmtkBridge::RemovePath(*d, DicomPath::Parse("ReferencedImageSequence[*].ReferencedSOPClassUID")); + //FromDcmtkBridge::RemovePath(*d, DicomPath::Parse("ReferencedImageSequence[0].ReferencedSOPClassUID")); + //FromDcmtkBridge::RemovePath(*d, DicomPath::Parse("ReferencedImageSequence[1].ReferencedSOPClassUID")); + FromDcmtkBridge::RemovePath(*d, DicomPath::Parse("RelatedSeriesSequence[0].PurposeOfReferenceCodeSequence[0].CodeValue")); + //FromDcmtkBridge::RemovePath(*d, DicomPath::Parse("RelatedSeriesSequence[0].PurposeOfReferenceCodeSequence")); + //FromDcmtkBridge::RemovePath(*d, DicomPath::Parse("RelatedSeriesSequence")); + + { + Json::Value v = "Hello"; + std::unique_ptr e(FromDcmtkBridge::FromJson(DicomTag(0x0008, 0x0100), v, false, Encoding_Latin1, "")); + FromDcmtkBridge::ReplacePath(*d, DicomPath::Parse("RelatedSeriesSequence[0].PurposeOfReferenceCodeSequence[0].CodeValue"), *e); + } + + { + Json::Value v = "Hello"; + std::unique_ptr e(FromDcmtkBridge::FromJson(DicomTag(0x0008, 0x1150), v, false, Encoding_Latin1, "")); + FromDcmtkBridge::ReplacePath(*d, DicomPath::Parse("ReferencedImageSequence[*].ReferencedSOPClassUID"), *e); + } + + Json::Value vv; + std::set ignoreTagLength; + FromDcmtkBridge::ExtractDicomAsJson(vv, *d, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0, ignoreTagLength); + std::cout << vv.toStyledString(); + } }