changeset 4682:d38a7040474a

FromDcmtkBridge::RemovePath() and FromDcmtkBridge::ReplacePath()
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 07 Jun 2021 18:35:46 +0200
parents c5528c7847a6
children 7182f5732480
files OrthancFramework/Sources/DicomFormat/DicomPath.cpp OrthancFramework/Sources/DicomFormat/DicomPath.h OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp
diffstat 5 files changed, 300 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- 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<std::string> 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
             {
--- 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);
   };
 }
--- 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<size_t>(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<DcmElement> 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<DcmElement> element_;
+    
+    public:
+      Visitor(const DcmElement& element) :
+        element_(dynamic_cast<DcmElement*>(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<DcmElement> cloned(dynamic_cast<DcmElement*>(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);
+    }
+  }
 }
 
 
--- 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 <dcmtk/dcmdata/dcdatset.h>
 #include <dcmtk/dcmdata/dcmetinf.h>
@@ -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);
   };
 }
--- 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<DcmElement> d(FromDcmtkBridge::FromJson(DICOM_TAG_SOURCE_IMAGE_SEQUENCE,
+                                                            v, false, Encoding_Latin1, ""));
+    d->writeXML(std::cout);
+  }
+
+  {
+    Json::Value v = "Hello";
+    std::unique_ptr<DcmElement> 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<DcmDataset> 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<DcmElement> 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<DcmElement> e(FromDcmtkBridge::FromJson(DicomTag(0x0008, 0x1150), v, false, Encoding_Latin1, ""));
+      FromDcmtkBridge::ReplacePath(*d, DicomPath::Parse("ReferencedImageSequence[*].ReferencedSOPClassUID"), *e);
+    }
+
+    Json::Value vv;
+    std::set<DicomTag> ignoreTagLength;
+    FromDcmtkBridge::ExtractDicomAsJson(vv, *d, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0, ignoreTagLength);
+    std::cout << vv.toStyledString();
+  }
 }