changeset 4683:7182f5732480

use of DicomPath in ParsedDicomFile
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 08 Jun 2021 12:37:48 +0200
parents d38a7040474a
children e3810750dc9d
files OrthancFramework/Sources/DicomFormat/DicomPath.cpp OrthancFramework/Sources/DicomFormat/DicomPath.h OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp
diffstat 7 files changed, 552 insertions(+), 206 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Sources/DicomFormat/DicomPath.cpp	Mon Jun 07 18:35:46 2021 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomPath.cpp	Tue Jun 08 12:37:48 2021 +0200
@@ -141,6 +141,27 @@
   }
 
 
+  DicomPath::DicomPath(const std::vector<Orthanc::DicomTag>& parentTags,
+                       const std::vector<size_t> parentIndexes,
+                       const Orthanc::DicomTag& finalTag) :
+    finalTag_(finalTag)
+  {
+    if (parentTags.size() != parentIndexes.size())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      prefix_.reserve(parentTags.size());
+
+      for (size_t i = 0; i < prefix_.size(); i++)
+      {
+        prefix_.push_back(PrefixItem::CreateIndexed(parentTags[i], parentIndexes[i]));        
+      }
+    }
+  }
+
+
   void DicomPath::AddIndexedTagToPrefix(const Orthanc::DicomTag& tag,
                                         size_t index)
   {
@@ -259,4 +280,39 @@
 
     return path;
   }
+
+
+  bool DicomPath::IsMatch(const DicomPath& pattern,
+                          const DicomPath& path)
+  {
+    if (path.HasUniversal())
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+    else if (path.GetPrefixLength() < pattern.GetPrefixLength())
+    {
+      return false;
+    }
+    else
+    {
+      for (size_t i = 0; i < pattern.GetPrefixLength(); i++)
+      {
+        if (path.GetPrefixTag(i) != pattern.GetPrefixTag(i) ||
+            (!pattern.IsPrefixUniversal(i) &&
+             path.GetPrefixIndex(i) != pattern.GetPrefixIndex(i)))
+        {
+          return false;
+        }
+      }
+
+      if (path.GetPrefixLength() == pattern.GetPrefixLength())
+      {
+        return (path.GetFinalTag() == pattern.GetFinalTag());
+      }
+      else
+      {
+        return (path.GetPrefixTag(pattern.GetPrefixLength()) == pattern.GetFinalTag());
+      }
+    }
+  }
 }
--- a/OrthancFramework/Sources/DicomFormat/DicomPath.h	Mon Jun 07 18:35:46 2021 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomPath.h	Tue Jun 08 12:37:48 2021 +0200
@@ -99,6 +99,10 @@
               size_t index3,
               const Orthanc::DicomTag& tag);
 
+    DicomPath(const std::vector<Orthanc::DicomTag>& parentTags,
+              const std::vector<size_t> parentIndexes,
+              const Orthanc::DicomTag& finalTag);
+
     void AddIndexedTagToPrefix(const Orthanc::DicomTag& tag,
                                size_t index);
 
@@ -134,5 +138,8 @@
     std::string Format() const;
 
     static DicomPath Parse(const std::string& s);
+
+    static bool IsMatch(const DicomPath& pattern,
+                        const DicomPath& path);
   };
 }
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Mon Jun 07 18:35:46 2021 +0200
+++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Tue Jun 08 12:37:48 2021 +0200
@@ -2798,16 +2798,20 @@
 
   static void ApplyInternal(FromDcmtkBridge::IDicomPathVisitor& visitor,
                             DcmItem& item,
-                            const DicomPath& path,
-                            size_t level)
+                            const DicomPath& pattern,
+                            const DicomPath& actualPath)
   {
-    if (level == path.GetPrefixLength())
+    const size_t level = actualPath.GetPrefixLength();
+      
+    if (level == pattern.GetPrefixLength())
     {
-      visitor.Visit(item, path.GetFinalTag());
+      visitor.Visit(item, actualPath);
     }
     else
     {
-      const DicomTag& tmp = path.GetPrefixTag(level);
+      assert(level < pattern.GetPrefixLength());
+
+      const DicomTag& tmp = pattern.GetPrefixTag(level);
       DcmTagKey tag(tmp.GetGroup(), tmp.GetElement());
 
       DcmSequenceOfItems *sequence = NULL;
@@ -2816,13 +2820,16 @@
       {
         for (unsigned long i = 0; i < sequence->card(); i++)
         {
-          if (path.IsPrefixUniversal(level) ||
-              path.GetPrefixIndex(level) == static_cast<size_t>(i))
+          if (pattern.IsPrefixUniversal(level) ||
+              pattern.GetPrefixIndex(level) == static_cast<size_t>(i))
           {
             DcmItem *child = sequence->getItem(i);
             if (child != NULL)
             {
-              ApplyInternal(visitor, *child, path, level + 1);
+              DicomPath childPath = actualPath;
+              childPath.AddIndexedTagToPrefix(pattern.GetPrefixTag(level), static_cast<size_t>(i));
+              
+              ApplyInternal(visitor, *child, pattern, childPath);
             }
           }
         }
@@ -2835,7 +2842,8 @@
                               DcmDataset& dataset,
                               const DicomPath& path)
   {
-    ApplyInternal(visitor, dataset, path, 0);
+    DicomPath actualPath(path.GetFinalTag());
+    ApplyInternal(visitor, dataset, path, actualPath);
   }
 
 
@@ -2846,10 +2854,10 @@
     {
     public:
       virtual void Visit(DcmItem& item,
-                         const DicomTag& tag) ORTHANC_OVERRIDE
+                         const DicomPath& path) ORTHANC_OVERRIDE
       {
-        DcmTagKey tmp(tag.GetGroup(), tag.GetElement());
-        std::unique_ptr<DcmElement> removed(item.remove(tmp));
+        DcmTagKey key(path.GetFinalTag().GetGroup(), path.GetFinalTag().GetElement());
+        std::unique_ptr<DcmElement> removed(item.remove(key));
       }
     };
     
@@ -2858,18 +2866,62 @@
   }
   
 
+  void FromDcmtkBridge::ClearPath(DcmDataset& dataset,
+                                  const DicomPath& path,
+                                  bool onlyIfExists)
+  {
+    class Visitor : public FromDcmtkBridge::IDicomPathVisitor
+    {
+    public:
+      bool  onlyIfExists_;
+      
+    public:
+      Visitor(bool onlyIfExists) :
+        onlyIfExists_(onlyIfExists)
+      {
+      }
+      
+      virtual void Visit(DcmItem& item,
+                         const DicomPath& path) ORTHANC_OVERRIDE
+      {
+        DcmTagKey key(path.GetFinalTag().GetGroup(), path.GetFinalTag().GetElement());
+
+        if (onlyIfExists_ &&
+            !item.tagExists(key))
+        {
+          // The tag is non-existing, do not clear it
+        }
+        else
+        {
+          if (!item.insertEmptyElement(key, OFTrue /* replace old value */).good())
+          {
+            throw OrthancException(ErrorCode_InternalError);
+          }
+        }
+      }
+    };
+    
+    Visitor visitor(onlyIfExists);
+    Apply(visitor, dataset, path);
+  }
+  
+
   void FromDcmtkBridge::ReplacePath(DcmDataset& dataset,
                                     const DicomPath& path,
-                                    const DcmElement& element)
+                                    const DcmElement& element,
+                                    DicomReplaceMode mode)
   {
     class Visitor : public FromDcmtkBridge::IDicomPathVisitor
     {
     private:
       std::unique_ptr<DcmElement> element_;
+      DicomReplaceMode            mode_;
     
     public:
-      Visitor(const DcmElement& element) :
-        element_(dynamic_cast<DcmElement*>(element.clone()))
+      Visitor(const DcmElement& element,
+              DicomReplaceMode mode) :
+        element_(dynamic_cast<DcmElement*>(element.clone())),
+        mode_(mode)
       {
         if (element_.get() == NULL)
         {
@@ -2878,7 +2930,7 @@
       }
     
       virtual void Visit(DcmItem& item,
-                         const DicomTag& tag) ORTHANC_OVERRIDE
+                         const DicomPath& path) ORTHANC_OVERRIDE
       {
         std::unique_ptr<DcmElement> cloned(dynamic_cast<DcmElement*>(element_->clone()));
         if (cloned.get() == NULL)
@@ -2887,25 +2939,44 @@
         }
         else
         {      
-          DcmTagKey tmp(tag.GetGroup(), tag.GetElement());
+          DcmTagKey key(path.GetFinalTag().GetGroup(), path.GetFinalTag().GetElement());
+
+          if (!item.tagExists(key))
+          {
+            switch (mode_)
+            {
+              case DicomReplaceMode_InsertIfAbsent:
+                break;  // Fine, we can proceed with insertion
+                
+              case DicomReplaceMode_ThrowIfAbsent:
+                throw OrthancException(ErrorCode_InexistentItem, "Cannot replace inexistent tag: " + GetTagName(*element_));
+                
+              case DicomReplaceMode_IgnoreIfAbsent:
+                return;  // Don't proceed with insertion
+                
+              default:
+                throw OrthancException(ErrorCode_ParameterOutOfRange);
+            }
+          }
+          
           if (!item.insert(cloned.release(), OFTrue /* replace old */).good())
           {
-            throw OrthancException(ErrorCode_InternalError, "Cannot replace an element");
+            throw OrthancException(ErrorCode_InternalError, "Cannot replace an element: " + GetTagName(*element_));
           }
         }
       }
     };
 
-    DcmTagKey tmp(path.GetFinalTag().GetGroup(), path.GetFinalTag().GetElement());
+    DcmTagKey key(path.GetFinalTag().GetGroup(), path.GetFinalTag().GetElement());
   
-    if (element.getTag() != tmp)
+    if (element.getTag() != key)
     {
       throw OrthancException(ErrorCode_ParameterOutOfRange,
                              "The final tag must be the same as the tag of the element during a replacement");
     }
     else
     {
-      Visitor visitor(element);
+      Visitor visitor(element, mode);
       Apply(visitor, dataset, path);
     }
   }
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h	Mon Jun 07 18:35:46 2021 +0200
+++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h	Tue Jun 08 12:37:48 2021 +0200
@@ -70,7 +70,7 @@
       }
 
       virtual void Visit(DcmItem& item,
-                         const DicomTag& tag) = 0;
+                         const DicomPath& path) = 0;
     };
     
 
@@ -249,8 +249,13 @@
     static void RemovePath(DcmDataset& dataset,
                            const DicomPath& path);
 
+    static void ClearPath(DcmDataset& dataset,
+                          const DicomPath& path,
+                          bool onlyIfExists);
+
     static void ReplacePath(DcmDataset& dataset,
                             const DicomPath& path,
-                            const DcmElement& element);
+                            const DcmElement& element,
+                            DicomReplaceMode mode);
   };
 }
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Mon Jun 07 18:35:46 2021 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Tue Jun 08 12:37:48 2021 +0200
@@ -482,44 +482,14 @@
 
   void ParsedDicomFile::Remove(const DicomTag& tag)
   {
-    InvalidateCache();
-
-    DcmTagKey key(tag.GetGroup(), tag.GetElement());
-    DcmElement* element = GetDcmtkObject().getDataset()->remove(key);
-    if (element != NULL)
-    {
-      delete element;
-    }
+    RemovePath(DicomPath(tag));
   }
 
 
   void ParsedDicomFile::Clear(const DicomTag& tag,
                               bool onlyIfExists)
   {
-    if (tag.GetElement() == 0x0000)
-    {
-      // Prevent manually modifying generic group length tags: This is
-      // handled by DCMTK serialization
-      return;
-    }
-
-    InvalidateCache();
-
-    DcmItem* dicom = GetDcmtkObject().getDataset();
-    DcmTagKey key(tag.GetGroup(), tag.GetElement());
-
-    if (onlyIfExists &&
-        !dicom->tagExists(key))
-    {
-      // The tag is non-existing, do not clear it
-    }
-    else
-    {
-      if (!dicom->insertEmptyElement(key, OFTrue /* replace old value */).good())
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-    }
+    ClearPath(DicomPath(tag), onlyIfExists);
   }
 
 
@@ -678,7 +648,8 @@
           return true;
 
         case DicomReplaceMode_ThrowIfAbsent:
-          throw OrthancException(ErrorCode_InexistentItem);
+          throw OrthancException(ErrorCode_InexistentItem, "Cannot replace inexistent tag: " +
+                                 FromDcmtkBridge::GetTagName(DicomTag(tag.getGroup(), tag.getElement()), ""));
 
         case DicomReplaceMode_IgnoreIfAbsent:
           return false;
@@ -758,36 +729,59 @@
       // handled by DCMTK serialization
       return;
     }
-
-    InvalidateCache();
-
-    DcmDataset& dicom = *GetDcmtkObject().getDataset();
-    if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode))
+    else
     {
-      // Either the tag was previously existing (and now removed), or
-      // the replace mode was set to "InsertIfAbsent"
+      InvalidateCache();
+
+      DcmDataset& dicom = *GetDcmtkObject().getDataset();
+      if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode))
+      {
+        // Either the tag was previously existing (and now removed), or
+        // the replace mode was set to "InsertIfAbsent"
+
+        if (decodeDataUriScheme &&
+            (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT ||
+             tag == DICOM_TAG_PIXEL_DATA))
+        {
+          if (EmbedContentInternal(utf8Value))
+          {
+            return;
+          }
+        }
+
+        std::unique_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag, privateCreator));
 
-      if (decodeDataUriScheme &&
-          (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT ||
-           tag == DICOM_TAG_PIXEL_DATA))
-      {
-        if (EmbedContentInternal(utf8Value))
+        if (!utf8Value.empty())
+        {
+          bool hasCodeExtensions;
+          Encoding encoding = DetectEncoding(hasCodeExtensions);
+          FromDcmtkBridge::FillElementWithString(*element, utf8Value, decodeDataUriScheme, encoding);
+        }
+
+        InsertInternal(dicom, element.release());
+
+        if (tag == DICOM_TAG_SOP_CLASS_UID ||
+            tag == DICOM_TAG_SOP_INSTANCE_UID)
         {
-          return;
+          if (decodeDataUriScheme &&
+              boost::starts_with(utf8Value, URI_SCHEME_PREFIX_BINARY))
+          {
+            std::string mime, decoded;
+            if (!Toolbox::DecodeDataUriScheme(mime, decoded, utf8Value))
+            {
+              throw OrthancException(ErrorCode_BadFileFormat);
+            }
+            else
+            {
+              UpdateStorageUid(tag, decoded, false);
+            }
+          }
+          else
+          {
+            UpdateStorageUid(tag, utf8Value, false);
+          }
         }
       }
-
-      std::unique_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag, privateCreator));
-
-      if (!utf8Value.empty())
-      {
-        bool hasCodeExtensions;
-        Encoding encoding = DetectEncoding(hasCodeExtensions);
-        FromDcmtkBridge::FillElementWithString(*element, utf8Value, decodeDataUriScheme, encoding);
-      }
-
-      InsertInternal(dicom, element.release());
-      UpdateStorageUid(tag, utf8Value, false);
     }
   }
 
@@ -804,39 +798,30 @@
       // handled by DCMTK serialization
       return;
     }
-
-    InvalidateCache();
-
-    DcmDataset& dicom = *GetDcmtkObject().getDataset();
-    if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode))
+    else if (value.type() == Json::stringValue)
     {
-      // Either the tag was previously existing (and now removed), or
-      // the replace mode was set to "InsertIfAbsent"
-
-      if (decodeDataUriScheme &&
-          value.type() == Json::stringValue &&
-          (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT ||
-           tag == DICOM_TAG_PIXEL_DATA))
-      {
-        if (EmbedContentInternal(value.asString()))
-        {
-          return;
-        }
-      }
-
-      bool hasCodeExtensions;
-      Encoding encoding = DetectEncoding(hasCodeExtensions);
-      InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator));
-
+      Replace(tag, value.asString(), decodeDataUriScheme, mode, privateCreator);
+    }
+    else
+    {
       if (tag == DICOM_TAG_SOP_CLASS_UID ||
           tag == DICOM_TAG_SOP_INSTANCE_UID)
       {
-        if (value.type() != Json::stringValue)
-        {
-          throw OrthancException(ErrorCode_BadParameterType);
-        }
+        // Must be a string
+        throw OrthancException(ErrorCode_BadParameterType);
+      }
+
+      InvalidateCache();
 
-        UpdateStorageUid(tag, value.asString(), decodeDataUriScheme);
+      DcmDataset& dicom = *GetDcmtkObject().getDataset();
+      if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode))
+      {
+        // Either the tag was previously existing (and now removed), or
+        // the replace mode was set to "InsertIfAbsent"
+
+        bool hasCodeExtensions;
+        Encoding encoding = DetectEncoding(hasCodeExtensions);
+        InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator));
       }
     }
   }
@@ -1724,6 +1709,74 @@
   }
 
 
+  static bool HasGenericGroupLength(const DicomPath& path)
+  {
+    for (size_t i = 0; i < path.GetPrefixLength(); i++)
+    {
+      if (path.GetPrefixTag(i).GetElement() == 0x0000)
+      {
+        return true;
+      }
+    }
+    
+    return (path.GetFinalTag().GetElement() == 0x0000);
+  }
+  
+
+  void ParsedDicomFile::ReplacePath(const DicomPath& path,
+                                    const Json::Value& value,
+                                    bool decodeDataUriScheme,
+                                    DicomReplaceMode mode,
+                                    const std::string& privateCreator)
+  {
+    if (HasGenericGroupLength(path))
+    {
+      // Prevent manually modifying generic group length tags: This is
+      // handled by DCMTK serialization
+      return;
+    }
+    else if (path.GetPrefixLength() == 0)
+    {
+      Replace(path.GetFinalTag(), value, decodeDataUriScheme, mode, privateCreator);
+    }
+    else
+    {
+      InvalidateCache();
+
+      bool hasCodeExtensions;
+      Encoding encoding = DetectEncoding(hasCodeExtensions);
+      std::unique_ptr<DcmElement> element(
+        FromDcmtkBridge::FromJson(path.GetFinalTag(), value, decodeDataUriScheme, encoding, privateCreator));
+
+      FromDcmtkBridge::ReplacePath(*GetDcmtkObject().getDataset(), path, *element, mode);
+    }
+  }
+  
+
+  void ParsedDicomFile::RemovePath(const DicomPath& path)
+  {
+    InvalidateCache();
+    FromDcmtkBridge::RemovePath(*GetDcmtkObject().getDataset(), path);
+  }
+
+
+  void ParsedDicomFile::ClearPath(const DicomPath& path,
+                                  bool onlyIfExists)
+  {
+    if (HasGenericGroupLength(path))
+    {
+      // Prevent manually modifying generic group length tags: This is
+      // handled by DCMTK serialization
+      return;
+    }
+    else
+    {
+      InvalidateCache();
+      FromDcmtkBridge::ClearPath(*GetDcmtkObject().getDataset(), path, onlyIfExists);
+    }
+  }
+
+
 #if ORTHANC_BUILDING_FRAMEWORK_LIBRARY == 1
   // Alias for binary compatibility with Orthanc Framework 1.7.2 => don't use it anymore
   void ParsedDicomFile::DatasetToJson(Json::Value& target,
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h	Mon Jun 07 18:35:46 2021 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h	Tue Jun 08 12:37:48 2021 +0200
@@ -54,6 +54,7 @@
 
 #include "ITagVisitor.h"
 #include "../DicomFormat/DicomInstanceHasher.h"
+#include "../DicomFormat/DicomPath.h"
 #include "../Images/ImageAccessor.h"
 #include "../IDynamicObject.h"
 #include "../Toolbox.h"
@@ -273,5 +274,16 @@
 
     // Decode the given frame, using the built-in DICOM decoder of Orthanc
     ImageAccessor* DecodeFrame(unsigned int frame) const;
+
+    void ReplacePath(const DicomPath& path,
+                     const Json::Value& value,  // Assumed to be encoded with UTF-8
+                     bool decodeDataUriScheme,
+                     DicomReplaceMode mode,
+                     const std::string& privateCreator /* used only for private tags */);
+
+    void RemovePath(const DicomPath& path);
+
+    void ClearPath(const DicomPath& path,
+                   bool onlyIfExists);
   };
 }
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Mon Jun 07 18:35:46 2021 +0200
+++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Tue Jun 08 12:37:48 2021 +0200
@@ -2358,118 +2358,260 @@
   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);
+
+  ASSERT_TRUE(DicomPath::IsMatch(DicomPath::Parse("(0010,0010)"),
+                                 DicomPath::Parse("(0010,0010)")));
+  ASSERT_FALSE(DicomPath::IsMatch(DicomPath::Parse("(0010,0010)"),
+                                  DicomPath::Parse("(0010,0020)")));
+  ASSERT_TRUE(DicomPath::IsMatch(DicomPath::Parse("(0010,0010)"),
+                                 DicomPath::Parse("(0010,0010)[1].(0010,0020)")));
+  ASSERT_FALSE(DicomPath::IsMatch(DicomPath::Parse("(0010,0010)[1].(0010,0020)"),
+                                  DicomPath::Parse("(0010,0010)")));
+  ASSERT_TRUE(DicomPath::IsMatch(DicomPath::Parse("(0010,0010)[1].(0010,0020)"),
+                                 DicomPath::Parse("(0010,0010)[1].(0010,0020)")));
+  ASSERT_TRUE(DicomPath::IsMatch(DicomPath::Parse("(0010,0010)[*].(0010,0020)"),
+                                 DicomPath::Parse("(0010,0010)[1].(0010,0020)")));
+  ASSERT_FALSE(DicomPath::IsMatch(DicomPath::Parse("(0010,0010)[2].(0010,0020)"),
+                                  DicomPath::Parse("(0010,0010)[1].(0010,0020)")));
+  ASSERT_THROW(DicomPath::IsMatch(DicomPath::Parse("(0010,0010)[1].(0010,0020)"),
+                                  DicomPath::Parse("(0010,0010)[*].(0010,0020)")), OrthancException);
+  ASSERT_TRUE(DicomPath::IsMatch(DicomPath::Parse("(0010,0010)[*].(0010,0020)[*].(0010,0030)"),
+                                 DicomPath::Parse("(0010,0010)[1].(0010,0020)[2].(0010,0030)[3].(0010,0040)")));
+  ASSERT_TRUE(DicomPath::IsMatch(DicomPath::Parse("(0010,0010)[1].(0010,0020)[2].(0010,0030)"),
+                                 DicomPath::Parse("(0010,0010)[1].(0010,0020)[2].(0010,0030)[3].(0010,0040)")));
+  ASSERT_FALSE(DicomPath::IsMatch(DicomPath::Parse("(0010,0010)[1].(0010,0020)[3].(0010,0030)"),
+                                  DicomPath::Parse("(0010,0010)[1].(0010,0020)[2].(0010,0030)[3].(0010,0040)")));
+  ASSERT_FALSE(DicomPath::IsMatch(DicomPath::Parse("(0010,0010)[2].(0010,0020)[2].(0010,0030)"),
+                                  DicomPath::Parse("(0010,0010)[1].(0010,0020)[2].(0010,0030)[3].(0010,0040)")));
 }
 
 
 
-TEST(ParsedDicomFile, RemovePath)
+TEST(ParsedDicomFile, DicomPath)
 {
+  Json::Value v = Json::objectValue;
+  v["PatientName"] = "Hello";
+  v["ReferencedSOPClassUID"] = "1.2.840.10008.5.1.4.1.1.4";
+
+  {
+    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";  // ReferencedSOPClassUID
+      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";
+          c["0010,0010"] = "WORLD";  // Patient name
+          b.append(c);
+        }
+
+        item["PurposeOfReferenceCodeSequence"] = b;
+      }
+        
+      a.append(item);
+    }
+      
+    v["RelatedSeriesSequence"] = a;
+  }
+
+  static const char* CODE_VALUE = "0008,0100";
+  static const char* PATIENT_ID = "0010,0020";
+  static const char* PATIENT_NAME = "0010,0010";
+  static const char* PURPOSE_CODE_SEQ = "0040,a170";
+  static const char* REF_IM_SEQ = "0008,1140";
+  static const char* REF_SOP_CLASS = "0008,1150";
+  static const char* REF_SOP_INSTANCE = "0008,1155";
+  static const char* REL_SERIES_SEQ = "0008,1250";
+
   {
-    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);
+    std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
+
+    Json::Value vv;
+    dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
+
+    ASSERT_EQ(5u, vv.size());
+    ASSERT_TRUE(vv.isMember(PATIENT_NAME));
+    ASSERT_EQ(2u, vv[REF_IM_SEQ].size());
+    ASSERT_EQ(1u, vv[REL_SERIES_SEQ].size());
+    ASSERT_EQ(2u, vv[REF_IM_SEQ][0].size());
+    ASSERT_EQ(2u, vv[REL_SERIES_SEQ][0].size());
+    ASSERT_EQ(1u, vv[REL_SERIES_SEQ][0][PURPOSE_CODE_SEQ].size());
+
+    ASSERT_TRUE(vv[REF_IM_SEQ][0].isMember(REF_SOP_CLASS));
+    ASSERT_TRUE(vv[REF_IM_SEQ][1].isMember(REF_SOP_CLASS));
+    ASSERT_TRUE(vv[REL_SERIES_SEQ][0][PURPOSE_CODE_SEQ][0].isMember(CODE_VALUE));
+  }
+    
+  {
+    std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
+
+    dicom->RemovePath(DicomPath::Parse("ReferencedImageSequence[*].ReferencedSOPClassUID"));
+
+    Json::Value vv;
+    dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
+
+    ASSERT_EQ(2u, vv[REF_IM_SEQ].size());
+    ASSERT_EQ(1u, vv[REF_IM_SEQ][0].size());
+    ASSERT_EQ(1u, vv[REF_IM_SEQ][1].size());
+    ASSERT_FALSE(vv[REF_IM_SEQ][0].isMember(REF_SOP_CLASS));
+    ASSERT_FALSE(vv[REF_IM_SEQ][1].isMember(REF_SOP_CLASS));
+  }
+    
+  {
+    std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
+
+    dicom->RemovePath(DicomPath::Parse("ReferencedImageSequence[0].ReferencedSOPClassUID"));
+
+    Json::Value vv;
+    dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
+
+    ASSERT_EQ(2u, vv[REF_IM_SEQ].size());
+    ASSERT_EQ(1u, vv[REF_IM_SEQ][0].size());
+    ASSERT_EQ(2u, vv[REF_IM_SEQ][1].size());
+    ASSERT_FALSE(vv[REF_IM_SEQ][0].isMember(REF_SOP_CLASS));
+    ASSERT_TRUE(vv[REF_IM_SEQ][1].isMember(REF_SOP_CLASS));
+  }
+    
+  {
+    std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
+
+    dicom->RemovePath(DicomPath::Parse("ReferencedImageSequence[1].ReferencedSOPClassUID"));
+
+    Json::Value vv;
+    dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
+
+    ASSERT_EQ(2u, vv[REF_IM_SEQ].size());
+    ASSERT_EQ(2u, vv[REF_IM_SEQ][0].size());
+    ASSERT_EQ(1u, vv[REF_IM_SEQ][1].size());
+    ASSERT_TRUE(vv[REF_IM_SEQ][0].isMember(REF_SOP_CLASS));
+    ASSERT_FALSE(vv[REF_IM_SEQ][1].isMember(REF_SOP_CLASS));
+  }
+    
+  {
+    std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
+
+    dicom->RemovePath(DicomPath::Parse("RelatedSeriesSequence[0].PurposeOfReferenceCodeSequence[0].CodeValue"));
+
+    Json::Value vv;
+    dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
+
+    ASSERT_EQ("WORLD", vv[REL_SERIES_SEQ][0][PURPOSE_CODE_SEQ][0][PATIENT_NAME].asString());
+    ASSERT_FALSE(vv[REL_SERIES_SEQ][0][PURPOSE_CODE_SEQ][0].isMember(CODE_VALUE));
+  }
+    
+  {
+    std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
+
+    dicom->RemovePath(DicomPath::Parse("RelatedSeriesSequence[0].PurposeOfReferenceCodeSequence"));
+
+    Json::Value vv;
+    dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
+      
+    ASSERT_EQ(1u, vv[REL_SERIES_SEQ][0].size());
+    ASSERT_FALSE(vv[REL_SERIES_SEQ][0].isMember(PURPOSE_CODE_SEQ));
+  }
+    
+  {
+    std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
+
+    dicom->RemovePath(DicomPath::Parse("RelatedSeriesSequence"));
+
+    Json::Value vv;
+    dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
+      
+    ASSERT_FALSE(vv.isMember(REL_SERIES_SEQ));
   }
 
   {
-    Json::Value v = "Hello";
-    std::unique_ptr<DcmElement> d(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_ID,
-                                                            v, false, Encoding_Latin1, ""));
-    d->writeXML(std::cout);
+    std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
+
+    dicom->RemovePath(DicomPath(DICOM_TAG_PATIENT_NAME));
+    dicom->ReplacePath(DicomPath::Parse("ReferencedImageSequence[*].ReferencedSOPClassUID"),
+                       "Hello1", false, DicomReplaceMode_ThrowIfAbsent, "");
+    ASSERT_THROW(dicom->ReplacePath(DicomPath::Parse("ReferencedImageSequence[*].PatientID"),
+                                    "Hello2", false, DicomReplaceMode_ThrowIfAbsent, ""), OrthancException);
+    dicom->ReplacePath(DicomPath::Parse("ReferencedImageSequence[*].PatientID"),
+                       "Hello3", false, DicomReplaceMode_InsertIfAbsent, "");
+    dicom->ReplacePath(DicomPath::Parse("ReferencedImageSequence[*].PatientName"),
+                       "Hello4", false, DicomReplaceMode_IgnoreIfAbsent, "");
+    dicom->ReplacePath(DicomPath::Parse("RelatedSeriesSequence[*].PurposeOfReferenceCodeSequence[*].CodeValue"),
+                       "Hello5", false, DicomReplaceMode_ThrowIfAbsent, "");
+      
+    Json::Value vv;
+    dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
+
+    ASSERT_EQ(4u, vv.size());
+    ASSERT_FALSE(vv.isMember(PATIENT_NAME));
+    ASSERT_EQ("Hello1", vv[REF_IM_SEQ][0][REF_SOP_CLASS].asString());
+    ASSERT_EQ("Hello3", vv[REF_IM_SEQ][0][PATIENT_ID].asString());
+    ASSERT_EQ("Hello1", vv[REF_IM_SEQ][1][REF_SOP_CLASS].asString());
+    ASSERT_EQ("Hello3", vv[REF_IM_SEQ][1][PATIENT_ID].asString());
+    ASSERT_EQ("Hello5", vv[REL_SERIES_SEQ][0][PURPOSE_CODE_SEQ][0][CODE_VALUE].asString());
   }
 
-  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);
-      }
+    std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
+
+    dicom->ReplacePath(DicomPath::Parse("ReferencedImageSequence[1].ReferencedSOPClassUID"),
+                       "Hello1", false, DicomReplaceMode_ThrowIfAbsent, "");
+    dicom->ReplacePath(DicomPath::Parse("RelatedSeriesSequence[0].PurposeOfReferenceCodeSequence[0].CodeValue"),
+                       "Hello2", false, DicomReplaceMode_ThrowIfAbsent, "");
       
-      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);
-      }
+    Json::Value vv;
+    dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
+
+    ASSERT_EQ("1.2.840.10008.5.1.4.1.1.4", vv[REF_IM_SEQ][0][REF_SOP_CLASS].asString());
+    ASSERT_EQ("Hello1", vv[REF_IM_SEQ][1][REF_SOP_CLASS].asString());
+    ASSERT_EQ("Hello2", vv[REL_SERIES_SEQ][0][PURPOSE_CODE_SEQ][0][CODE_VALUE].asString());
+  }
+
+  {
+    std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
+
+    dicom->ClearPath(DicomPath::Parse("ReferencedImageSequence[1].ReferencedSOPClassUID"), true);
+    dicom->ClearPath(DicomPath::Parse("RelatedSeriesSequence[0].PurposeOfReferenceCodeSequence[0].CodeValue"), true);
+    dicom->ClearPath(DicomPath::Parse("ReferencedImageSequence[0].PatientID"), false);
+    dicom->ClearPath(DicomPath::Parse("ReferencedImageSequence[0].PatientName"), true);
       
-      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();
+    dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
+
+    ASSERT_EQ(3u, vv[REF_IM_SEQ][0].size());
+    ASSERT_EQ(2u, vv[REF_IM_SEQ][1].size());
+      
+    ASSERT_EQ("1.2.840.10008.5.1.4.1.1.4", vv[REF_IM_SEQ][0][REF_SOP_CLASS].asString());
+    ASSERT_EQ("1.2.840.113619.2.176.2025.1499492.7040.1171286241.719", vv[REF_IM_SEQ][0][REF_SOP_INSTANCE].asString());
+    ASSERT_EQ("", vv[REF_IM_SEQ][0][PATIENT_ID].asString());
+      
+    ASSERT_EQ("", vv[REF_IM_SEQ][1][REF_SOP_CLASS].asString());
+    ASSERT_EQ("1.2.840.113619.2.176.2025.1499492.7040.1171286241.726", vv[REF_IM_SEQ][1][REF_SOP_INSTANCE].asString());
+      
+    ASSERT_EQ("", vv[REL_SERIES_SEQ][0][PURPOSE_CODE_SEQ][0][CODE_VALUE].asString());
   }
 }