changeset 4678:2e850edf03d6

Full support for the anonymization of subsequences containing tags whose VR is UI
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 04 Jun 2021 17:38:43 +0200
parents 521e39b3f2c0
children 8be4721bd245
files NEWS OrthancFramework/Sources/DicomParsing/DicomModification.cpp OrthancFramework/Sources/DicomParsing/DicomModification.h OrthancFramework/Sources/DicomParsing/DicomModification_Anonymization2017c.impl.h OrthancFramework/Sources/DicomParsing/DicomModification_Anonymization2021b.impl.h OrthancServer/Resources/GenerateAnonymizationProfile.py OrthancServer/UnitTestsSources/ServerJobsTests.cpp
diffstat 7 files changed, 399 insertions(+), 173 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Thu Jun 03 21:06:34 2021 +0200
+++ b/NEWS	Fri Jun 04 17:38:43 2021 +0200
@@ -17,6 +17,7 @@
 Maintenance
 -----------
 
+* Full support for the anonymization of subsequences containing tags whose VR is UI
 * Fix issue #146 (Update Anonyization to 2019c) - was actually updated to 2021b
 
 
--- a/OrthancFramework/Sources/DicomParsing/DicomModification.cpp	Thu Jun 03 21:06:34 2021 +0200
+++ b/OrthancFramework/Sources/DicomParsing/DicomModification.cpp	Fri Jun 04 17:38:43 2021 +0200
@@ -44,6 +44,27 @@
 
 namespace Orthanc
 {
+  DicomModification::DicomTagRange::DicomTagRange(uint16_t groupFrom,
+                                                  uint16_t groupTo,
+                                                  uint16_t elementFrom,
+                                                  uint16_t elementTo) :
+    groupFrom_(groupFrom),
+    groupTo_(groupTo),
+    elementFrom_(elementFrom),
+    elementTo_(elementTo)
+  {
+  }
+
+  
+  bool DicomModification::DicomTagRange::Contains(const DicomTag& tag) const
+  {
+    return (tag.GetGroup() >= groupFrom_ &&
+            tag.GetGroup() <= groupTo_ &&
+            tag.GetElement() >= elementFrom_ &&
+            tag.GetElement() <= elementTo_);
+  }
+
+
   class DicomModification::RelationshipsVisitor : public ITagVisitor
   {
   private:
@@ -124,58 +145,51 @@
                                ValueRepresentation vr,
                                const std::string& value)
     {
+      // Note that all the tags in "uids_" have the VR UI (unique
+      // identifier), and are considered as strings
+      
       if (!IsEnabled(tag))
       {
         return Action_None;
       }
-      else if (parentTags.size() == 2 &&
-               parentTags[0] == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_SEQUENCE &&
-               parentTags[1] == DICOM_TAG_RT_REFERENCED_STUDY_SEQUENCE &&
-               tag == DICOM_TAG_REFERENCED_SOP_INSTANCE_UID)
-      {
-        // in RT-STRUCT, this ReferencedSOPInstanceUID is actually referencing a StudyInstanceUID !!
-        // (observed in many data sets including: https://wiki.cancerimagingarchive.net/display/Public/Lung+CT+Segmentation+Challenge+2017)
-        // tested in test_anonymize_relationships_5
-        newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Study);
-        return Action_Replace;
-      }
-      else if (tag == DICOM_TAG_FRAME_OF_REFERENCE_UID ||
-               tag == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_UID || 
-               tag == DICOM_TAG_REFERENCED_SOP_INSTANCE_UID ||
-               tag == DICOM_TAG_RELATED_FRAME_OF_REFERENCE_UID)
-      {
-        newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Instance);
-        return Action_Replace;
-      }
-      else if (parentTags.size() == 1 &&
-               parentTags[0] == DICOM_TAG_CURRENT_REQUESTED_PROCEDURE_EVIDENCE_SEQUENCE &&
+      else if (!parentTags.empty() &&  // Don't anonymize twice the anonymization done by "MapDicomTags()"
                tag == DICOM_TAG_STUDY_INSTANCE_UID)
       {
-        newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Study);
+        newValue = that_.MapDicomIdentifier(value, ResourceType_Study);
         return Action_Replace;
       }
-      else if (parentTags.size() == 2 &&
-               parentTags[0] == DICOM_TAG_CURRENT_REQUESTED_PROCEDURE_EVIDENCE_SEQUENCE &&
-               parentTags[1] == DICOM_TAG_REFERENCED_SERIES_SEQUENCE &&
+      else if (!parentTags.empty() &&  // Don't anonymize twice the anonymization done by "MapDicomTags()"
                tag == DICOM_TAG_SERIES_INSTANCE_UID)
       {
-        newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Series);
+        newValue = that_.MapDicomIdentifier(value, ResourceType_Series);
+        return Action_Replace;
+      }
+      else if (!parentTags.empty() &&  // Don't anonymize twice the anonymization done by "MapDicomTags()"
+               tag == DICOM_TAG_SOP_INSTANCE_UID)
+      {  
+        newValue = that_.MapDicomIdentifier(value, ResourceType_Instance);
         return Action_Replace;
       }
-      else if (parentTags.size() == 3 &&
-               parentTags[0] == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_SEQUENCE &&
-               parentTags[1] == DICOM_TAG_RT_REFERENCED_STUDY_SEQUENCE &&
-               parentTags[2] == DICOM_TAG_RT_REFERENCED_SERIES_SEQUENCE &&
-               tag == DICOM_TAG_SERIES_INSTANCE_UID)
+      else if (that_.uids_.find(tag) != that_.uids_.end())
       {
-        newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Series);
-        return Action_Replace;
-      }
-      else if (parentTags.size() == 1 &&
-               parentTags[0] == DICOM_TAG_REFERENCED_SERIES_SEQUENCE &&
-               tag == DICOM_TAG_SERIES_INSTANCE_UID)
-      {
-        newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Series);
+        assert(vr == ValueRepresentation_UniqueIdentifier ||
+               vr == ValueRepresentation_NotSupported /* for older versions of DCMTK */);
+        
+        if (parentTags.size() == 2 &&
+            parentTags[0] == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_SEQUENCE &&
+            parentTags[1] == DICOM_TAG_RT_REFERENCED_STUDY_SEQUENCE &&
+            tag == DICOM_TAG_REFERENCED_SOP_INSTANCE_UID)
+        {
+          // in RT-STRUCT, this ReferencedSOPInstanceUID is actually referencing a StudyInstanceUID !!
+          // (observed in many data sets including: https://wiki.cancerimagingarchive.net/display/Public/Lung+CT+Segmentation+Challenge+2017)
+          // tested in test_anonymize_relationships_5
+          newValue = that_.MapDicomIdentifier(value, ResourceType_Study);
+        }
+        else
+        {
+          newValue = that_.MapDicomIdentifier(value, ResourceType_Instance);
+        }
+
         return Action_Replace;
       }
       else
@@ -186,18 +200,20 @@
 
     void RemoveRelationships(ParsedDicomFile& dicom) const
     {
-      // Sequences containing the UID relationships
+      for (SetOfTags::const_iterator it = that_.uids_.begin(); it != that_.uids_.end(); ++it)
+      {
+        if (*it != DICOM_TAG_STUDY_INSTANCE_UID &&
+            *it != DICOM_TAG_SERIES_INSTANCE_UID &&
+            *it != DICOM_TAG_SOP_INSTANCE_UID)
+        {
+          RemoveIfEnabled(dicom, *it);
+        }
+      }
+
+      // The only two sequences with to the "X/Z/U*" rule in the
+      // basic profile. They were already present in Orthanc 1.9.3.
       RemoveIfEnabled(dicom, DICOM_TAG_REFERENCED_IMAGE_SEQUENCE);
       RemoveIfEnabled(dicom, DICOM_TAG_SOURCE_IMAGE_SEQUENCE);
-      
-      // Individual tags
-      RemoveIfEnabled(dicom, DICOM_TAG_FRAME_OF_REFERENCE_UID);
-
-      // The tags below should never occur at the first level of the
-      // hierarchy, but remove them anyway
-      RemoveIfEnabled(dicom, DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_UID);
-      RemoveIfEnabled(dicom, DICOM_TAG_REFERENCED_SOP_INSTANCE_UID);
-      RemoveIfEnabled(dicom, DICOM_TAG_RELATED_FRAME_OF_REFERENCE_UID);
     }
   };
 
@@ -278,9 +294,11 @@
   std::string DicomModification::MapDicomIdentifier(const std::string& original,
                                                     ResourceType level)
   {
+    const std::string stripped = Toolbox::StripSpaces(original);
+    
     std::string mapped;
 
-    UidMap::const_iterator previous = uidMap_.find(std::make_pair(level, original));
+    UidMap::const_iterator previous = uidMap_.find(std::make_pair(level, stripped));
 
     if (previous == uidMap_.end())
     {
@@ -290,14 +308,14 @@
       }
       else
       {
-        if (!identifierGenerator_->Apply(mapped, original, level, currentSource_))
+        if (!identifierGenerator_->Apply(mapped, stripped, level, currentSource_))
         {
           throw OrthancException(ErrorCode_InternalError,
                                  "Unable to generate an anonymized ID");
         }
       }
 
-      uidMap_.insert(std::make_pair(std::make_pair(level, original), mapped));
+      uidMap_.insert(std::make_pair(std::make_pair(level, stripped), mapped));
     }
     else
     {
@@ -337,7 +355,7 @@
       original = "";
     }
 
-    std::string mapped = MapDicomIdentifier(Toolbox::StripSpaces(original), level);
+    std::string mapped = MapDicomIdentifier(original, level);
 
     dicom.Replace(*tag, mapped, 
                   false /* don't try and decode data URI scheme for UIDs */, 
@@ -371,6 +389,7 @@
     
     removals_.erase(tag);
     clearings_.erase(tag);
+    uids_.erase(tag);
 
     bool wasReplaced = CancelReplacement(tag);
 
@@ -404,6 +423,7 @@
   {
     removals_.insert(tag);
     clearings_.erase(tag);
+    uids_.erase(tag);
     CancelReplacement(tag);
     privateTagsToKeep_.erase(tag);
 
@@ -414,6 +434,7 @@
   {
     removals_.erase(tag);
     clearings_.insert(tag);
+    uids_.erase(tag);
     CancelReplacement(tag);
     privateTagsToKeep_.erase(tag);
 
@@ -422,7 +443,23 @@
 
   bool DicomModification::IsRemoved(const DicomTag& tag) const
   {
-    return removals_.find(tag) != removals_.end();
+    if (removals_.find(tag) != removals_.end())
+    {
+      return true;
+    }
+    else
+    {
+      for (RemovedRanges::const_iterator it = removedRanges_.begin();
+           it != removedRanges_.end(); it++)
+      {
+        if (it->Contains(tag))
+        {
+          return true;
+        }
+      }
+
+      return false;
+    }
   }
 
   bool DicomModification::IsCleared(const DicomTag& tag) const
@@ -436,6 +473,7 @@
   {
     clearings_.erase(tag);
     removals_.erase(tag);
+    uids_.erase(tag);
     privateTagsToKeep_.erase(tag);
     ReplaceInternal(tag, value);
 
@@ -513,12 +551,44 @@
   }
 
 
+  static void SetupUidsFromOrthancInternal(std::set<DicomTag>& uids,
+                                           std::set<DicomTag>& removals,
+                                           const DicomTag& tag)
+  {
+    uids.insert(tag);
+    removals.erase(tag);  // Necessary if unserializing a job from 1.9.3
+  }
+  
+
+  void DicomModification::SetupUidsFromOrthanc_1_9_3()
+  {
+    /**
+     * Values below come from the hardcoded UID of Orthanc 1.9.3
+     * in DicomModification::RelationshipsVisitor::VisitString() and
+     * DicomModification::RelationshipsVisitor::RemoveRelationships()
+     * https://hg.orthanc-server.com/orthanc/file/Orthanc-1.9.3/OrthancFramework/Sources/DicomParsing/DicomModification.cpp#l117
+     **/
+    uids_.clear();
+
+    SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0008, 0x0014));  // Instance Creator UID                   <= from SetupAnonymization2008()
+    SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0008, 0x1155));  // Referenced SOP Instance UID            <= from VisitString() + RemoveRelationships()
+    SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0020, 0x0052));  // Frame of Reference UID                 <= from VisitString() + RemoveRelationships()
+    SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0020, 0x0200));  // Synchronization Frame of Reference UID <= from SetupAnonymization2008()
+    SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0040, 0xa124));  // UID                                    <= from SetupAnonymization2008()
+    SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0088, 0x0140));  // Storage Media File-set UID             <= from SetupAnonymization2008()
+    SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x3006, 0x0024));  // Referenced Frame of Reference UID      <= from VisitString() + RemoveRelationships()
+    SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x3006, 0x00c2));  // Related Frame of Reference UID         <= from VisitString() + RemoveRelationships()
+  }
+
+
   void DicomModification::SetupAnonymization2008()
   {
     // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles
     // https://raw.githubusercontent.com/jodogne/dicom-specification/master/2008/08_15pu.pdf
+
+    SetupUidsFromOrthanc_1_9_3();
     
-    removals_.insert(DicomTag(0x0008, 0x0014));  // Instance Creator UID
+    //uids_.insert(DicomTag(0x0008, 0x0014));  // Instance Creator UID => set in SetupUidsFromOrthanc_1_9_3()
     //removals_.insert(DicomTag(0x0008, 0x0018));  // SOP Instance UID => set in Apply()
     removals_.insert(DicomTag(0x0008, 0x0050));  // Accession Number
     removals_.insert(DicomTag(0x0008, 0x0080));  // Institution Name
@@ -535,7 +605,7 @@
     removals_.insert(DicomTag(0x0008, 0x1060));  // Name of Physician(s) Reading Study 
     removals_.insert(DicomTag(0x0008, 0x1070));  // Operators' Name 
     removals_.insert(DicomTag(0x0008, 0x1080));  // Admitting Diagnoses Description 
-    //removals_.insert(DicomTag(0x0008, 0x1155));  // Referenced SOP Instance UID => RelationshipsVisitor
+    //uids_.insert(DicomTag(0x0008, 0x1155));      // Referenced SOP Instance UID => set in SetupUidsFromOrthanc_1_9_3()
     removals_.insert(DicomTag(0x0008, 0x2111));  // Derivation Description 
     //removals_.insert(DicomTag(0x0010, 0x0010));  // Patient's Name => cf. below (*)
     //removals_.insert(DicomTag(0x0010, 0x0020));  // Patient ID => cf. below (*)
@@ -557,15 +627,15 @@
     //removals_.insert(DicomTag(0x0020, 0x000d));  // Study Instance UID => set in Apply()
     //removals_.insert(DicomTag(0x0020, 0x000e));  // Series Instance UID => set in Apply()
     removals_.insert(DicomTag(0x0020, 0x0010));  // Study ID 
-    //removals_.insert(DicomTag(0x0020, 0x0052));  // Frame of Reference UID => cf. RelationshipsVisitor
-    removals_.insert(DicomTag(0x0020, 0x0200));  // Synchronization Frame of Reference UID 
+    //uids_.insert(DicomTag(0x0020, 0x0052));      // Frame of Reference UID => set in SetupUidsFromOrthanc_1_9_3()
+    //uids_.insert(DicomTag(0x0020, 0x0200));      // Synchronization Frame of Reference UID => set in SetupUidsFromOrthanc_1_9_3()
     removals_.insert(DicomTag(0x0020, 0x4000));  // Image Comments 
     removals_.insert(DicomTag(0x0040, 0x0275));  // Request Attributes Sequence 
-    removals_.insert(DicomTag(0x0040, 0xa124));  // UID
+    //uids_.insert(DicomTag(0x0040, 0xa124));      // UID => set in SetupUidsFromOrthanc_1_9_3()
     removals_.insert(DicomTag(0x0040, 0xa730));  // Content Sequence 
-    removals_.insert(DicomTag(0x0088, 0x0140));  // Storage Media File-set UID 
-    //removals_.insert(DicomTag(0x3006, 0x0024));  // Referenced Frame of Reference UID => RelationshipsVisitor
-    //removals_.insert(DicomTag(0x3006, 0x00c2));  // Related Frame of Reference UID => RelationshipsVisitor
+    //uids_.insert(DicomTag(0x0088, 0x0140));      // Storage Media File-set UID => set in SetupUidsFromOrthanc_1_9_3()
+    //uids_.insert(DicomTag(0x3006, 0x0024));      // Referenced Frame of Reference UID => set in SetupUidsFromOrthanc_1_9_3()
+    //uids_.insert(DicomTag(0x3006, 0x00c2));      // Related Frame of Reference UID => set in SetupUidsFromOrthanc_1_9_3()
 
     // Some more removals (from the experience of DICOM files at the CHU of Liege)
     removals_.insert(DicomTag(0x0010, 0x1040));  // Patient's Address
@@ -621,6 +691,8 @@
     
     removals_.clear();
     clearings_.clear();
+    removedRanges_.clear();
+    uids_.clear();
     ClearReplacements();
     removePrivateTags_ = true;
     level_ = ResourceType_Patient;
@@ -652,6 +724,17 @@
     std::string patientId = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient);
     ReplaceInternal(DICOM_TAG_PATIENT_ID, patientId);
     ReplaceInternal(DICOM_TAG_PATIENT_NAME, patientId);
+
+    // Sanity check
+    for (SetOfTags::const_iterator it = uids_.begin(); it != uids_.end(); ++it)
+    {
+      ValueRepresentation vr = FromDcmtkBridge::LookupValueRepresentation(*it);
+      if (vr != ValueRepresentation_UniqueIdentifier &&
+          vr != ValueRepresentation_NotSupported /* for older versions of DCMTK */)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }        
   }
 
   void DicomModification::Apply(ParsedDicomFile& toModify)
@@ -1131,6 +1214,8 @@
   static const char* MAP_SERIES = "MapSeries";
   static const char* MAP_INSTANCES = "MapInstances";
   static const char* PRIVATE_CREATOR = "PrivateCreator";  // New in Orthanc 1.6.0
+  static const char* UIDS = "Uids";                       // New in Orthanc 1.9.4
+  static const char* REMOVED_RANGES = "RemovedRanges";    // New in Orthanc 1.9.4
   
   void DicomModification::Serialize(Json::Value& value) const
   {
@@ -1205,6 +1290,24 @@
       assert(tmp2 != NULL);
       (*tmp2) [it->first.second] = it->second;
     }
+
+    // New in Orthanc 1.9.4
+    SerializationToolbox::WriteSetOfTags(value, uids_, UIDS);
+
+    // New in Orthanc 1.9.4
+    Json::Value ranges = Json::arrayValue;
+      
+    for (RemovedRanges::const_iterator it = removedRanges_.begin(); it != removedRanges_.end(); ++it)
+    {
+      Json::Value item = Json::arrayValue;
+      item.append(it->GetGroupFrom());
+      item.append(it->GetGroupTo());
+      item.append(it->GetElementFrom());
+      item.append(it->GetElementTo());
+      ranges.append(item);
+    }
+
+    value[REMOVED_RANGES] = ranges;
   }
 
   void DicomModification::UnserializeUidMap(ResourceType level,
@@ -1283,6 +1386,62 @@
     UnserializeUidMap(ResourceType_Study, serialized, MAP_STUDIES);
     UnserializeUidMap(ResourceType_Series, serialized, MAP_SERIES);
     UnserializeUidMap(ResourceType_Instance, serialized, MAP_INSTANCES);
+
+    // New in Orthanc 1.9.4
+    if (serialized.isMember(UIDS))  // Backward compatibility with Orthanc <= 1.9.3
+    {
+      SerializationToolbox::ReadSetOfTags(uids_, serialized, UIDS);
+    }
+    else
+    {
+      SetupUidsFromOrthanc_1_9_3();
+    }
+
+    // New in Orthanc 1.9.4
+    removedRanges_.clear();
+    if (serialized.isMember(REMOVED_RANGES))  // Backward compatibility with Orthanc <= 1.9.3
+    {
+      const Json::Value& ranges = serialized[REMOVED_RANGES];
+      
+      if (ranges.type() != Json::arrayValue)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+      else
+      {
+        for (Json::Value::ArrayIndex i = 0; i < ranges.size(); i++)
+        {
+          if (ranges[i].type() != Json::arrayValue ||
+              ranges[i].size() != 4 ||
+              !ranges[i][0].isUInt() ||
+              !ranges[i][1].isUInt() ||
+              !ranges[i][2].isUInt() ||
+              !ranges[i][3].isUInt())
+          {
+            throw OrthancException(ErrorCode_BadFileFormat);
+          }
+          else
+          {
+            Json::LargestUInt groupFrom = ranges[i][0].asUInt();
+            Json::LargestUInt groupTo = ranges[i][1].asUInt();
+            Json::LargestUInt elementFrom = ranges[i][2].asUInt();
+            Json::LargestUInt elementTo = ranges[i][3].asUInt();
+
+            if (groupFrom > groupTo ||
+                elementFrom > elementTo ||
+                groupTo > 0xffffu ||
+                elementTo > 0xffffu)
+            {
+              throw OrthancException(ErrorCode_BadFileFormat);
+            }
+            else
+            {
+              removedRanges_.push_back(DicomTagRange(groupFrom, groupTo, elementFrom, elementTo));
+            }
+          }
+        }
+      }
+    }
   }
 
 
--- a/OrthancFramework/Sources/DicomParsing/DicomModification.h	Thu Jun 03 21:06:34 2021 +0200
+++ b/OrthancFramework/Sources/DicomParsing/DicomModification.h	Fri Jun 04 17:38:43 2021 +0200
@@ -58,13 +58,53 @@
   private:
     class RelationshipsVisitor;
 
+    class DicomTagRange
+    {
+    private:
+      uint16_t   groupFrom_;
+      uint16_t   groupTo_;
+      uint16_t   elementFrom_;
+      uint16_t   elementTo_;
+
+    public:
+      DicomTagRange(uint16_t groupFrom,
+                    uint16_t groupTo,
+                    uint16_t elementFrom,
+                    uint16_t elementTo);
+
+      uint16_t GetGroupFrom() const
+      {
+        return groupFrom_;
+      }
+
+      uint16_t GetGroupTo() const
+      {
+        return groupTo_;
+      }
+
+      uint16_t GetElementFrom() const
+      {
+        return elementFrom_;
+      }
+
+      uint16_t GetElementTo() const
+      {
+        return elementTo_;
+      }
+
+      bool Contains(const DicomTag& tag) const;
+    };
+    
     typedef std::set<DicomTag> SetOfTags;
     typedef std::map<DicomTag, Json::Value*> Replacements;
     typedef std::map< std::pair<ResourceType, std::string>, std::string>  UidMap;
+    typedef std::list<DicomTagRange>  RemovedRanges;
 
     SetOfTags removals_;
     SetOfTags clearings_;
     Replacements replacements_;
+    SetOfTags uids_;                // New in Orthanc 1.9.4
+    RemovedRanges removedRanges_;   // New in Orthanc 1.9.4
     bool removePrivateTags_;
     ResourceType level_;
     UidMap uidMap_;
@@ -99,6 +139,8 @@
     void ReplaceInternal(const DicomTag& tag,
                          const Json::Value& value);
 
+    void SetupUidsFromOrthanc_1_9_3();
+
     void SetupAnonymization2008();
 
     void SetupAnonymization2017c();
--- a/OrthancFramework/Sources/DicomParsing/DicomModification_Anonymization2017c.impl.h	Thu Jun 03 21:06:34 2021 +0200
+++ b/OrthancFramework/Sources/DicomParsing/DicomModification_Anonymization2017c.impl.h	Fri Jun 04 17:38:43 2021 +0200
@@ -1,17 +1,10 @@
-// TODO: (50xx,xxxx) with rule X                                 // Curve Data
-// TODO: (60xx,3000) with rule X                                 // Overlay Data
-// TODO: (60xx,4000) with rule X                                 // Overlay Comments
+// RelationshipsVisitor handles (0x0008, 0x1140)  /* X/Z/U* */   // Referenced Image Sequence
+// RelationshipsVisitor handles (0x0008, 0x2112)  /* X/Z/U* */   // Source Image Sequence
 // Tag (0x0008, 0x0018) is set in Apply()         /* U */        // SOP Instance UID
-// Tag (0x0008, 0x1140) => RelationshipsVisitor   /* X/Z/U* */   // Referenced Image Sequence
-// Tag (0x0008, 0x1155) => RelationshipsVisitor   /* U */        // Referenced SOP Instance UID
-// Tag (0x0008, 0x2112) => RelationshipsVisitor   /* X/Z/U* */   // Source Image Sequence
 // Tag (0x0010, 0x0010) is set below (*)          /* Z */        // Patient's Name
 // Tag (0x0010, 0x0020) is set below (*)          /* Z */        // Patient ID
 // Tag (0x0020, 0x000d) is set in Apply()         /* U */        // Study Instance UID
 // Tag (0x0020, 0x000e) is set in Apply()         /* U */        // Series Instance UID
-// Tag (0x0020, 0x0052) => RelationshipsVisitor   /* U */        // Frame of Reference UID
-// Tag (0x3006, 0x0024) => RelationshipsVisitor   /* U */        // Referenced Frame of Reference UID
-// Tag (0x3006, 0x00c2) => RelationshipsVisitor   /* U */        // Related Frame of Reference UID
 clearings_.insert(DicomTag(0x0008, 0x0020));                     // Study Date
 clearings_.insert(DicomTag(0x0008, 0x0023));  /* Z/D */          // Content Date
 clearings_.insert(DicomTag(0x0008, 0x0030));                     // Study Time
@@ -33,10 +26,6 @@
 clearings_.insert(DicomTag(0x0070, 0x0001));  /* D */            // Graphic Annotation Sequence
 clearings_.insert(DicomTag(0x0070, 0x0084));                     // Content Creator's Name
 removals_.insert(DicomTag(0x0000, 0x1000));                      // Affected SOP Instance UID
-removals_.insert(DicomTag(0x0000, 0x1001));   /* TODO UID */     // Requested SOP Instance UID
-removals_.insert(DicomTag(0x0002, 0x0003));   /* TODO UID */     // Media Storage SOP Instance UID
-removals_.insert(DicomTag(0x0004, 0x1511));   /* TODO UID */     // Referenced SOP Instance UID in File
-removals_.insert(DicomTag(0x0008, 0x0014));   /* TODO UID */     // Instance Creator UID
 removals_.insert(DicomTag(0x0008, 0x0015));                      // Instance Coercion DateTime
 removals_.insert(DicomTag(0x0008, 0x0021));   /* X/D */          // Series Date
 removals_.insert(DicomTag(0x0008, 0x0022));   /* X/Z */          // Acquisition Date
@@ -47,7 +36,6 @@
 removals_.insert(DicomTag(0x0008, 0x0032));   /* X/Z */          // Acquisition Time
 removals_.insert(DicomTag(0x0008, 0x0034));                      // Overlay Time
 removals_.insert(DicomTag(0x0008, 0x0035));                      // Curve Time
-removals_.insert(DicomTag(0x0008, 0x0058));   /* TODO UID */     // Failed SOP Instance UID List
 removals_.insert(DicomTag(0x0008, 0x0080));   /* X/Z/D */        // Institution Name
 removals_.insert(DicomTag(0x0008, 0x0081));                      // Institution Address
 removals_.insert(DicomTag(0x0008, 0x0082));   /* X/Z/D */        // Institution Code Sequence
@@ -73,9 +61,7 @@
 removals_.insert(DicomTag(0x0008, 0x1110));   /* X/Z */          // Referenced Study Sequence
 removals_.insert(DicomTag(0x0008, 0x1111));   /* X/Z/D */        // Referenced Performed Procedure Step Sequence
 removals_.insert(DicomTag(0x0008, 0x1120));                      // Referenced Patient Sequence
-removals_.insert(DicomTag(0x0008, 0x1195));   /* TODO UID */     // Transaction UID
 removals_.insert(DicomTag(0x0008, 0x2111));                      // Derivation Description
-removals_.insert(DicomTag(0x0008, 0x3010));   /* TODO UID */     // Irradiation Event UID
 removals_.insert(DicomTag(0x0008, 0x4000));                      // Identifying Comments
 removals_.insert(DicomTag(0x0010, 0x0021));                      // Issuer of Patient ID
 removals_.insert(DicomTag(0x0010, 0x0032));                      // Patient's Birth Time
@@ -114,30 +100,23 @@
 removals_.insert(DicomTag(0x0010, 0x2299));                      // Responsible Organization
 removals_.insert(DicomTag(0x0010, 0x4000));                      // Patient Comments
 removals_.insert(DicomTag(0x0018, 0x1000));   /* X/Z/D */        // Device Serial Number
-removals_.insert(DicomTag(0x0018, 0x1002));   /* TODO UID */     // Device UID
 removals_.insert(DicomTag(0x0018, 0x1004));                      // Plate ID
 removals_.insert(DicomTag(0x0018, 0x1005));                      // Generator ID
 removals_.insert(DicomTag(0x0018, 0x1007));                      // Cassette ID
 removals_.insert(DicomTag(0x0018, 0x1008));                      // Gantry ID
 removals_.insert(DicomTag(0x0018, 0x1030));   /* X/D */          // Protocol Name
 removals_.insert(DicomTag(0x0018, 0x1400));   /* X/D */          // Acquisition Device Processing Description
-removals_.insert(DicomTag(0x0018, 0x2042));   /* TODO UID */     // Target UID
 removals_.insert(DicomTag(0x0018, 0x4000));                      // Acquisition Comments
 removals_.insert(DicomTag(0x0018, 0x700a));   /* X/D */          // Detector ID
 removals_.insert(DicomTag(0x0018, 0x9424));                      // Acquisition Protocol Description
 removals_.insert(DicomTag(0x0018, 0x9516));   /* X/D */          // Start Acquisition DateTime
 removals_.insert(DicomTag(0x0018, 0x9517));   /* X/D */          // End Acquisition DateTime
 removals_.insert(DicomTag(0x0018, 0xa003));                      // Contribution Description
-removals_.insert(DicomTag(0x0020, 0x0200));   /* TODO UID */     // Synchronization Frame of Reference UID
 removals_.insert(DicomTag(0x0020, 0x3401));                      // Modifying Device ID
 removals_.insert(DicomTag(0x0020, 0x3404));                      // Modifying Device Manufacturer
 removals_.insert(DicomTag(0x0020, 0x3406));                      // Modified Image Description
 removals_.insert(DicomTag(0x0020, 0x4000));                      // Image Comments
 removals_.insert(DicomTag(0x0020, 0x9158));                      // Frame Comments
-removals_.insert(DicomTag(0x0020, 0x9161));   /* TODO UID */     // Concatenation UID
-removals_.insert(DicomTag(0x0020, 0x9164));   /* TODO UID */     // Dimension Organization UID
-removals_.insert(DicomTag(0x0028, 0x1199));   /* TODO UID */     // Palette Color Lookup Table UID
-removals_.insert(DicomTag(0x0028, 0x1214));   /* TODO UID */     // Large Palette Color Lookup Table UID
 removals_.insert(DicomTag(0x0028, 0x4000));                      // Image Presentation Comments
 removals_.insert(DicomTag(0x0032, 0x0012));                      // Study ID Issuer
 removals_.insert(DicomTag(0x0032, 0x1020));                      // Scheduled Study Location
@@ -205,7 +184,6 @@
 removals_.insert(DicomTag(0x0040, 0x4005));                      // Scheduled Procedure Step Start DateTime
 removals_.insert(DicomTag(0x0040, 0x4010));                      // Scheduled Procedure Step Modification DateTime
 removals_.insert(DicomTag(0x0040, 0x4011));                      // Expected Completion DateTime
-removals_.insert(DicomTag(0x0040, 0x4023));   /* TODO UID */     // Referenced General Purpose Scheduled Procedure Step Transaction UID
 removals_.insert(DicomTag(0x0040, 0x4025));                      // Scheduled Station Name Code Sequence
 removals_.insert(DicomTag(0x0040, 0x4027));                      // Scheduled Station Geographic Location Code Sequence
 removals_.insert(DicomTag(0x0040, 0x4028));                      // Performed Station Name Code Sequence
@@ -221,9 +199,6 @@
 removals_.insert(DicomTag(0x0040, 0xa078));                      // Author Observer Sequence
 removals_.insert(DicomTag(0x0040, 0xa07a));                      // Participant Sequence
 removals_.insert(DicomTag(0x0040, 0xa07c));                      // Custodial Organization Sequence
-removals_.insert(DicomTag(0x0040, 0xa124));   /* TODO UID */     // UID
-removals_.insert(DicomTag(0x0040, 0xa171));   /* TODO UID */     // Observation UID
-removals_.insert(DicomTag(0x0040, 0xa172));   /* TODO UID */     // Referenced Observation UID (Trial)
 removals_.insert(DicomTag(0x0040, 0xa192));                      // Observation Date (Trial)
 removals_.insert(DicomTag(0x0040, 0xa193));                      // Observation Time (Trial)
 removals_.insert(DicomTag(0x0040, 0xa307));                      // Current Observer (Trial)
@@ -231,16 +206,8 @@
 removals_.insert(DicomTag(0x0040, 0xa353));                      // Address (Trial)
 removals_.insert(DicomTag(0x0040, 0xa354));                      // Telephone Number (Trial)
 removals_.insert(DicomTag(0x0040, 0xa358));                      // Verbal Source Identifier Code Sequence (Trial)
-removals_.insert(DicomTag(0x0040, 0xa402));   /* TODO UID */     // Observation Subject UID (Trial)
 removals_.insert(DicomTag(0x0040, 0xa730));                      // Content Sequence
-removals_.insert(DicomTag(0x0040, 0xdb0c));   /* TODO UID */     // Template Extension Organization UID
-removals_.insert(DicomTag(0x0040, 0xdb0d));   /* TODO UID */     // Template Extension Creator UID
-removals_.insert(DicomTag(0x0062, 0x0021));   /* TODO UID */     // Tracking UID
 removals_.insert(DicomTag(0x0070, 0x0086));                      // Content Creator's Identification Code Sequence
-removals_.insert(DicomTag(0x0070, 0x031a));   /* TODO UID */     // Fiducial UID
-removals_.insert(DicomTag(0x0070, 0x1101));   /* TODO UID */     // Presentation Display Collection UID
-removals_.insert(DicomTag(0x0070, 0x1102));   /* TODO UID */     // Presentation Sequence Collection UID
-removals_.insert(DicomTag(0x0088, 0x0140));   /* TODO UID */     // Storage Media File-set UID
 removals_.insert(DicomTag(0x0088, 0x0200));                      // Icon Image Sequence(see Note 12)
 removals_.insert(DicomTag(0x0088, 0x0904));                      // Topic Title
 removals_.insert(DicomTag(0x0088, 0x0906));                      // Topic Subject
@@ -254,7 +221,6 @@
 removals_.insert(DicomTag(0x0400, 0x0561));                      // Original Attributes Sequence
 removals_.insert(DicomTag(0x2030, 0x0020));                      // Text String
 removals_.insert(DicomTag(0x3008, 0x0105));                      // Source Serial Number
-removals_.insert(DicomTag(0x300a, 0x0013));   /* TODO UID */     // Dose Reference UID
 removals_.insert(DicomTag(0x300c, 0x0113));                      // Reason for Omission Description
 removals_.insert(DicomTag(0x300e, 0x0008));   /* X/Z */          // Reviewer Name
 removals_.insert(DicomTag(0x4000, 0x0010));                      // Arbitrary
@@ -275,3 +241,37 @@
 removals_.insert(DicomTag(0x4008, 0x4000));                      // Results Comments
 removals_.insert(DicomTag(0xfffa, 0xfffa));                      // Digital Signatures Sequence
 removals_.insert(DicomTag(0xfffc, 0xfffc));                      // Data Set Trailing Padding
+removedRanges_.push_back(DicomTagRange(0x5000, 0x50ff, 0x0000, 0xffff));  // Curve Data
+removedRanges_.push_back(DicomTagRange(0x6000, 0x60ff, 0x3000, 0x3000));  // Overlay Data
+removedRanges_.push_back(DicomTagRange(0x6000, 0x60ff, 0x4000, 0x4000));  // Overlay Comments
+uids_.insert(DicomTag(0x0000, 0x1001));                          // Requested SOP Instance UID
+uids_.insert(DicomTag(0x0002, 0x0003));                          // Media Storage SOP Instance UID
+uids_.insert(DicomTag(0x0004, 0x1511));                          // Referenced SOP Instance UID in File
+uids_.insert(DicomTag(0x0008, 0x0014));                          // Instance Creator UID
+uids_.insert(DicomTag(0x0008, 0x0058));                          // Failed SOP Instance UID List
+uids_.insert(DicomTag(0x0008, 0x1155));                          // Referenced SOP Instance UID
+uids_.insert(DicomTag(0x0008, 0x1195));                          // Transaction UID
+uids_.insert(DicomTag(0x0008, 0x3010));                          // Irradiation Event UID
+uids_.insert(DicomTag(0x0018, 0x1002));                          // Device UID
+uids_.insert(DicomTag(0x0018, 0x2042));                          // Target UID
+uids_.insert(DicomTag(0x0020, 0x0052));                          // Frame of Reference UID
+uids_.insert(DicomTag(0x0020, 0x0200));                          // Synchronization Frame of Reference UID
+uids_.insert(DicomTag(0x0020, 0x9161));                          // Concatenation UID
+uids_.insert(DicomTag(0x0020, 0x9164));                          // Dimension Organization UID
+uids_.insert(DicomTag(0x0028, 0x1199));                          // Palette Color Lookup Table UID
+uids_.insert(DicomTag(0x0028, 0x1214));                          // Large Palette Color Lookup Table UID
+uids_.insert(DicomTag(0x0040, 0x4023));                          // Referenced General Purpose Scheduled Procedure Step Transaction UID
+uids_.insert(DicomTag(0x0040, 0xa124));                          // UID
+uids_.insert(DicomTag(0x0040, 0xa171));                          // Observation UID
+uids_.insert(DicomTag(0x0040, 0xa172));                          // Referenced Observation UID (Trial)
+uids_.insert(DicomTag(0x0040, 0xa402));                          // Observation Subject UID (Trial)
+uids_.insert(DicomTag(0x0040, 0xdb0c));                          // Template Extension Organization UID
+uids_.insert(DicomTag(0x0040, 0xdb0d));                          // Template Extension Creator UID
+uids_.insert(DicomTag(0x0062, 0x0021));                          // Tracking UID
+uids_.insert(DicomTag(0x0070, 0x031a));                          // Fiducial UID
+uids_.insert(DicomTag(0x0070, 0x1101));                          // Presentation Display Collection UID
+uids_.insert(DicomTag(0x0070, 0x1102));                          // Presentation Sequence Collection UID
+uids_.insert(DicomTag(0x0088, 0x0140));                          // Storage Media File-set UID
+uids_.insert(DicomTag(0x3006, 0x0024));                          // Referenced Frame of Reference UID
+uids_.insert(DicomTag(0x3006, 0x00c2));                          // Related Frame of Reference UID
+uids_.insert(DicomTag(0x300a, 0x0013));                          // Dose Reference UID
--- a/OrthancFramework/Sources/DicomParsing/DicomModification_Anonymization2021b.impl.h	Thu Jun 03 21:06:34 2021 +0200
+++ b/OrthancFramework/Sources/DicomParsing/DicomModification_Anonymization2021b.impl.h	Fri Jun 04 17:38:43 2021 +0200
@@ -1,17 +1,10 @@
-// TODO: (50xx,xxxx) with rule X                                 // Curve Data
-// TODO: (60xx,3000) with rule X                                 // Overlay Data
-// TODO: (60xx,4000) with rule X                                 // Overlay Comments
+// RelationshipsVisitor handles (0x0008, 0x1140)  /* X/Z/U* */   // Referenced Image Sequence
+// RelationshipsVisitor handles (0x0008, 0x2112)  /* X/Z/U* */   // Source Image Sequence
 // Tag (0x0008, 0x0018) is set in Apply()         /* U */        // SOP Instance UID
-// Tag (0x0008, 0x1140) => RelationshipsVisitor   /* X/Z/U* */   // Referenced Image Sequence
-// Tag (0x0008, 0x1155) => RelationshipsVisitor   /* U */        // Referenced SOP Instance UID
-// Tag (0x0008, 0x2112) => RelationshipsVisitor   /* X/Z/U* */   // Source Image Sequence
 // Tag (0x0010, 0x0010) is set below (*)          /* Z */        // Patient's Name
 // Tag (0x0010, 0x0020) is set below (*)          /* Z */        // Patient ID
 // Tag (0x0020, 0x000d) is set in Apply()         /* U */        // Study Instance UID
 // Tag (0x0020, 0x000e) is set in Apply()         /* U */        // Series Instance UID
-// Tag (0x0020, 0x0052) => RelationshipsVisitor   /* U */        // Frame of Reference UID
-// Tag (0x3006, 0x0024) => RelationshipsVisitor   /* U */        // Referenced Frame of Reference UID
-// Tag (0x3006, 0x00c2) => RelationshipsVisitor   /* U */        // Related Frame of Reference UID
 clearings_.insert(DicomTag(0x0008, 0x0020));                     // Study Date
 clearings_.insert(DicomTag(0x0008, 0x0023));  /* Z/D */          // Content Date
 clearings_.insert(DicomTag(0x0008, 0x0030));                     // Study Time
@@ -98,10 +91,6 @@
 clearings_.insert(DicomTag(0x3010, 0x007f));                     // Fractionation Notes
 clearings_.insert(DicomTag(0x3010, 0x0081));                     // Prescription Notes Sequence
 removals_.insert(DicomTag(0x0000, 0x1000));                      // Affected SOP Instance UID
-removals_.insert(DicomTag(0x0000, 0x1001));   /* TODO UID */     // Requested SOP Instance UID
-removals_.insert(DicomTag(0x0002, 0x0003));   /* TODO UID */     // Media Storage SOP Instance UID
-removals_.insert(DicomTag(0x0004, 0x1511));   /* TODO UID */     // Referenced SOP Instance UID in File
-removals_.insert(DicomTag(0x0008, 0x0014));   /* TODO UID */     // Instance Creator UID
 removals_.insert(DicomTag(0x0008, 0x0015));                      // Instance Coercion DateTime
 removals_.insert(DicomTag(0x0008, 0x0021));   /* X/D */          // Series Date
 removals_.insert(DicomTag(0x0008, 0x0022));   /* X/Z */          // Acquisition Date
@@ -112,7 +101,6 @@
 removals_.insert(DicomTag(0x0008, 0x0032));   /* X/Z */          // Acquisition Time
 removals_.insert(DicomTag(0x0008, 0x0034));                      // Overlay Time
 removals_.insert(DicomTag(0x0008, 0x0035));                      // Curve Time
-removals_.insert(DicomTag(0x0008, 0x0058));   /* TODO UID */     // Failed SOP Instance UID List
 removals_.insert(DicomTag(0x0008, 0x0080));   /* X/Z/D */        // Institution Name
 removals_.insert(DicomTag(0x0008, 0x0081));                      // Institution Address
 removals_.insert(DicomTag(0x0008, 0x0082));   /* X/Z/D */        // Institution Code Sequence
@@ -139,9 +127,7 @@
 removals_.insert(DicomTag(0x0008, 0x1110));   /* X/Z */          // Referenced Study Sequence
 removals_.insert(DicomTag(0x0008, 0x1111));   /* X/Z/D */        // Referenced Performed Procedure Step Sequence
 removals_.insert(DicomTag(0x0008, 0x1120));                      // Referenced Patient Sequence
-removals_.insert(DicomTag(0x0008, 0x1195));   /* TODO UID */     // Transaction UID
 removals_.insert(DicomTag(0x0008, 0x2111));                      // Derivation Description
-removals_.insert(DicomTag(0x0008, 0x3010));   /* TODO UID */     // Irradiation Event UID
 removals_.insert(DicomTag(0x0008, 0x4000));                      // Identifying Comments
 removals_.insert(DicomTag(0x0010, 0x0021));                      // Issuer of Patient ID
 removals_.insert(DicomTag(0x0010, 0x0032));                      // Patient's Birth Time
@@ -222,17 +208,14 @@
 removals_.insert(DicomTag(0x0016, 0x008d));                      // GPS Date Stamp
 removals_.insert(DicomTag(0x0016, 0x008e));                      // GPS Differential
 removals_.insert(DicomTag(0x0018, 0x1000));   /* X/Z/D */        // Device Serial Number
-removals_.insert(DicomTag(0x0018, 0x1002));   /* TODO UID */     // Device UID
 removals_.insert(DicomTag(0x0018, 0x1004));                      // Plate ID
 removals_.insert(DicomTag(0x0018, 0x1005));                      // Generator ID
 removals_.insert(DicomTag(0x0018, 0x1007));                      // Cassette ID
 removals_.insert(DicomTag(0x0018, 0x1008));                      // Gantry ID
 removals_.insert(DicomTag(0x0018, 0x1009));                      // Unique Device Identifier
 removals_.insert(DicomTag(0x0018, 0x100a));                      // UDI Sequence
-removals_.insert(DicomTag(0x0018, 0x100b));   /* TODO UID */     // Manufacturer's Device Class UID
 removals_.insert(DicomTag(0x0018, 0x1030));   /* X/D */          // Protocol Name
 removals_.insert(DicomTag(0x0018, 0x1400));   /* X/D */          // Acquisition Device Processing Description
-removals_.insert(DicomTag(0x0018, 0x2042));   /* TODO UID */     // Target UID
 removals_.insert(DicomTag(0x0018, 0x4000));                      // Acquisition Comments
 removals_.insert(DicomTag(0x0018, 0x5011));                      // Transducer Identification Sequence
 removals_.insert(DicomTag(0x0018, 0x700a));   /* X/D */          // Detector ID
@@ -245,15 +228,10 @@
 removals_.insert(DicomTag(0x0018, 0x9517));   /* X/D */          // End Acquisition DateTime
 removals_.insert(DicomTag(0x0018, 0x9937));                      // Requested Series Description
 removals_.insert(DicomTag(0x0018, 0xa003));                      // Contribution Description
-removals_.insert(DicomTag(0x0020, 0x0200));   /* TODO UID */     // Synchronization Frame of Reference UID
 removals_.insert(DicomTag(0x0020, 0x3401));                      // Modifying Device ID
 removals_.insert(DicomTag(0x0020, 0x3406));                      // Modified Image Description
 removals_.insert(DicomTag(0x0020, 0x4000));                      // Image Comments
 removals_.insert(DicomTag(0x0020, 0x9158));                      // Frame Comments
-removals_.insert(DicomTag(0x0020, 0x9161));   /* TODO UID */     // Concatenation UID
-removals_.insert(DicomTag(0x0020, 0x9164));   /* TODO UID */     // Dimension Organization UID
-removals_.insert(DicomTag(0x0028, 0x1199));   /* TODO UID */     // Palette Color Lookup Table UID
-removals_.insert(DicomTag(0x0028, 0x1214));   /* TODO UID */     // Large Palette Color Lookup Table UID
 removals_.insert(DicomTag(0x0028, 0x4000));                      // Image Presentation Comments
 removals_.insert(DicomTag(0x0032, 0x0012));                      // Study ID Issuer
 removals_.insert(DicomTag(0x0032, 0x1020));                      // Scheduled Study Location
@@ -283,7 +261,6 @@
 removals_.insert(DicomTag(0x0038, 0x0400));                      // Patient's Institution Residence
 removals_.insert(DicomTag(0x0038, 0x0500));                      // Patient State
 removals_.insert(DicomTag(0x0038, 0x4000));                      // Visit Comments
-removals_.insert(DicomTag(0x003a, 0x0310));   /* TODO UID */     // Multiplex Group UID
 removals_.insert(DicomTag(0x0040, 0x0001));                      // Scheduled Station AE Title
 removals_.insert(DicomTag(0x0040, 0x0002));                      // Scheduled Procedure Step Start Date
 removals_.insert(DicomTag(0x0040, 0x0003));                      // Scheduled Procedure Step Start Time
@@ -310,7 +287,6 @@
 removals_.insert(DicomTag(0x0040, 0x0310));                      // Comments on Radiation Dose
 removals_.insert(DicomTag(0x0040, 0x050a));                      // Specimen Accession Number
 removals_.insert(DicomTag(0x0040, 0x051a));                      // Container Description
-removals_.insert(DicomTag(0x0040, 0x0554));   /* TODO UID */     // Specimen UID
 removals_.insert(DicomTag(0x0040, 0x0555));   /* X/Z */          // Acquisition Context Sequence
 removals_.insert(DicomTag(0x0040, 0x0600));                      // Specimen Short Description
 removals_.insert(DicomTag(0x0040, 0x0602));                      // Specimen Detailed Description
@@ -337,7 +313,6 @@
 removals_.insert(DicomTag(0x0040, 0x4008));                      // Scheduled Procedure Step Expiration DateTime
 removals_.insert(DicomTag(0x0040, 0x4010));                      // Scheduled Procedure Step Modification DateTime
 removals_.insert(DicomTag(0x0040, 0x4011));                      // Expected Completion DateTime
-removals_.insert(DicomTag(0x0040, 0x4023));   /* TODO UID */     // Referenced General Purpose Scheduled Procedure Step Transaction UID
 removals_.insert(DicomTag(0x0040, 0x4025));                      // Scheduled Station Name Code Sequence
 removals_.insert(DicomTag(0x0040, 0x4027));                      // Scheduled Station Geographic Location Code Sequence
 removals_.insert(DicomTag(0x0040, 0x4028));                      // Performed Station Name Code Sequence
@@ -352,9 +327,6 @@
 removals_.insert(DicomTag(0x0040, 0xa078));                      // Author Observer Sequence
 removals_.insert(DicomTag(0x0040, 0xa07a));                      // Participant Sequence
 removals_.insert(DicomTag(0x0040, 0xa07c));                      // Custodial Organization Sequence
-removals_.insert(DicomTag(0x0040, 0xa124));   /* TODO UID */     // UID
-removals_.insert(DicomTag(0x0040, 0xa171));   /* TODO UID */     // Observation UID
-removals_.insert(DicomTag(0x0040, 0xa172));   /* TODO UID */     // Referenced Observation UID (Trial)
 removals_.insert(DicomTag(0x0040, 0xa192));                      // Observation Date (Trial)
 removals_.insert(DicomTag(0x0040, 0xa193));                      // Observation Time (Trial)
 removals_.insert(DicomTag(0x0040, 0xa307));                      // Current Observer (Trial)
@@ -362,24 +334,15 @@
 removals_.insert(DicomTag(0x0040, 0xa353));                      // Address (Trial)
 removals_.insert(DicomTag(0x0040, 0xa354));                      // Telephone Number (Trial)
 removals_.insert(DicomTag(0x0040, 0xa358));                      // Verbal Source Identifier Code Sequence (Trial)
-removals_.insert(DicomTag(0x0040, 0xa402));   /* TODO UID */     // Observation Subject UID (Trial)
-removals_.insert(DicomTag(0x0040, 0xdb0c));   /* TODO UID */     // Template Extension Organization UID
-removals_.insert(DicomTag(0x0040, 0xdb0d));   /* TODO UID */     // Template Extension Creator UID
 removals_.insert(DicomTag(0x0050, 0x001b));                      // Container Component ID
 removals_.insert(DicomTag(0x0050, 0x0020));                      // Device Description
 removals_.insert(DicomTag(0x0050, 0x0021));                      // Long Device Description
-removals_.insert(DicomTag(0x0062, 0x0021));   /* TODO UID */     // Tracking UID
 removals_.insert(DicomTag(0x0070, 0x0086));                      // Content Creator's Identification Code Sequence
-removals_.insert(DicomTag(0x0070, 0x031a));   /* TODO UID */     // Fiducial UID
-removals_.insert(DicomTag(0x0070, 0x1101));   /* TODO UID */     // Presentation Display Collection UID
-removals_.insert(DicomTag(0x0070, 0x1102));   /* TODO UID */     // Presentation Sequence Collection UID
-removals_.insert(DicomTag(0x0088, 0x0140));   /* TODO UID */     // Storage Media File-set UID
 removals_.insert(DicomTag(0x0088, 0x0200));                      // Icon Image Sequence
 removals_.insert(DicomTag(0x0088, 0x0904));                      // Topic Title
 removals_.insert(DicomTag(0x0088, 0x0906));                      // Topic Subject
 removals_.insert(DicomTag(0x0088, 0x0910));                      // Topic Author
 removals_.insert(DicomTag(0x0088, 0x0912));                      // Topic Keywords
-removals_.insert(DicomTag(0x0400, 0x0100));   /* TODO UID */     // Digital Signature UID
 removals_.insert(DicomTag(0x0400, 0x0402));                      // Referenced Digital Signature Sequence
 removals_.insert(DicomTag(0x0400, 0x0403));                      // Referenced SOP Instance MAC Sequence
 removals_.insert(DicomTag(0x0400, 0x0404));                      // MAC
@@ -407,10 +370,8 @@
 removals_.insert(DicomTag(0x300a, 0x0006));   /* X/D */          // RT Plan Date
 removals_.insert(DicomTag(0x300a, 0x0007));   /* X/D */          // RT Plan Time
 removals_.insert(DicomTag(0x300a, 0x000e));                      // Prescription Description
-removals_.insert(DicomTag(0x300a, 0x0013));   /* TODO UID */     // Dose Reference UID
 removals_.insert(DicomTag(0x300a, 0x0016));                      // Dose Reference Description
 removals_.insert(DicomTag(0x300a, 0x0072));                      // Fraction Group Description
-removals_.insert(DicomTag(0x300a, 0x0083));   /* TODO UID */     // Referenced Dose Reference UID
 removals_.insert(DicomTag(0x300a, 0x00b2));   /* X/Z */          // Treatment Machine Name
 removals_.insert(DicomTag(0x300a, 0x00c3));                      // Beam Description
 removals_.insert(DicomTag(0x300a, 0x00dd));                      // Bolus Description
@@ -419,26 +380,15 @@
 removals_.insert(DicomTag(0x300a, 0x01b2));                      // Setup Technique Description
 removals_.insert(DicomTag(0x300a, 0x0216));                      // Source Manufacturer
 removals_.insert(DicomTag(0x300a, 0x02eb));                      // Compensator Description
-removals_.insert(DicomTag(0x300a, 0x0609));   /* TODO UID */     // Treatment Position Group UID
-removals_.insert(DicomTag(0x300a, 0x0650));   /* TODO UID */     // Patient Setup UID
 removals_.insert(DicomTag(0x300a, 0x0676));                      // Equipment Frame of Reference Description
-removals_.insert(DicomTag(0x300a, 0x0700));   /* TODO UID */     // Treatment Session UID
 removals_.insert(DicomTag(0x300c, 0x0113));                      // Reason for Omission Description
 removals_.insert(DicomTag(0x300e, 0x0008));   /* X/Z */          // Reviewer Name
-removals_.insert(DicomTag(0x3010, 0x0006));   /* TODO UID */     // Conceptual Volume UID
-removals_.insert(DicomTag(0x3010, 0x000b));   /* TODO UID */     // Referenced Conceptual Volume UID
-removals_.insert(DicomTag(0x3010, 0x0013));   /* TODO UID */     // Constituent Conceptual Volume UID
-removals_.insert(DicomTag(0x3010, 0x0015));   /* TODO UID */     // Source Conceptual Volume UID
-removals_.insert(DicomTag(0x3010, 0x0031));   /* TODO UID */     // Referenced Fiducials UID
 removals_.insert(DicomTag(0x3010, 0x0036));                      // Entity Name
 removals_.insert(DicomTag(0x3010, 0x0037));                      // Entity Description
-removals_.insert(DicomTag(0x3010, 0x003b));   /* TODO UID */     // RT Treatment Phase UID
 removals_.insert(DicomTag(0x3010, 0x004c));   /* X/D */          // Intended Phase Start Date
 removals_.insert(DicomTag(0x3010, 0x004d));   /* X/D */          // Intended Phase End Date
 removals_.insert(DicomTag(0x3010, 0x0056));   /* X/D */          // RT Treatment Approach Label
 removals_.insert(DicomTag(0x3010, 0x0061));                      // Prior Treatment Dose Description
-removals_.insert(DicomTag(0x3010, 0x006e));   /* TODO UID */     // Dosimetric Objective UID
-removals_.insert(DicomTag(0x3010, 0x006f));   /* TODO UID */     // Referenced Dosimetric Objective UID
 removals_.insert(DicomTag(0x4000, 0x0010));                      // Arbitrary
 removals_.insert(DicomTag(0x4000, 0x4000));                      // Text Comments
 removals_.insert(DicomTag(0x4008, 0x0042));                      // Results ID Issuer
@@ -457,3 +407,53 @@
 removals_.insert(DicomTag(0x4008, 0x4000));                      // Results Comments
 removals_.insert(DicomTag(0xfffa, 0xfffa));                      // Digital Signatures Sequence
 removals_.insert(DicomTag(0xfffc, 0xfffc));                      // Data Set Trailing Padding
+removedRanges_.push_back(DicomTagRange(0x5000, 0x50ff, 0x0000, 0xffff));  // Curve Data
+removedRanges_.push_back(DicomTagRange(0x6000, 0x60ff, 0x3000, 0x3000));  // Overlay Data
+removedRanges_.push_back(DicomTagRange(0x6000, 0x60ff, 0x4000, 0x4000));  // Overlay Comments
+uids_.insert(DicomTag(0x0000, 0x1001));                          // Requested SOP Instance UID
+uids_.insert(DicomTag(0x0002, 0x0003));                          // Media Storage SOP Instance UID
+uids_.insert(DicomTag(0x0004, 0x1511));                          // Referenced SOP Instance UID in File
+uids_.insert(DicomTag(0x0008, 0x0014));                          // Instance Creator UID
+uids_.insert(DicomTag(0x0008, 0x0058));                          // Failed SOP Instance UID List
+uids_.insert(DicomTag(0x0008, 0x1155));                          // Referenced SOP Instance UID
+uids_.insert(DicomTag(0x0008, 0x1195));                          // Transaction UID
+uids_.insert(DicomTag(0x0008, 0x3010));                          // Irradiation Event UID
+uids_.insert(DicomTag(0x0018, 0x1002));                          // Device UID
+uids_.insert(DicomTag(0x0018, 0x100b));                          // Manufacturer's Device Class UID
+uids_.insert(DicomTag(0x0018, 0x2042));                          // Target UID
+uids_.insert(DicomTag(0x0020, 0x0052));                          // Frame of Reference UID
+uids_.insert(DicomTag(0x0020, 0x0200));                          // Synchronization Frame of Reference UID
+uids_.insert(DicomTag(0x0020, 0x9161));                          // Concatenation UID
+uids_.insert(DicomTag(0x0020, 0x9164));                          // Dimension Organization UID
+uids_.insert(DicomTag(0x0028, 0x1199));                          // Palette Color Lookup Table UID
+uids_.insert(DicomTag(0x0028, 0x1214));                          // Large Palette Color Lookup Table UID
+uids_.insert(DicomTag(0x003a, 0x0310));                          // Multiplex Group UID
+uids_.insert(DicomTag(0x0040, 0x0554));                          // Specimen UID
+uids_.insert(DicomTag(0x0040, 0x4023));                          // Referenced General Purpose Scheduled Procedure Step Transaction UID
+uids_.insert(DicomTag(0x0040, 0xa124));                          // UID
+uids_.insert(DicomTag(0x0040, 0xa171));                          // Observation UID
+uids_.insert(DicomTag(0x0040, 0xa172));                          // Referenced Observation UID (Trial)
+uids_.insert(DicomTag(0x0040, 0xa402));                          // Observation Subject UID (Trial)
+uids_.insert(DicomTag(0x0040, 0xdb0c));                          // Template Extension Organization UID
+uids_.insert(DicomTag(0x0040, 0xdb0d));                          // Template Extension Creator UID
+uids_.insert(DicomTag(0x0062, 0x0021));                          // Tracking UID
+uids_.insert(DicomTag(0x0070, 0x031a));                          // Fiducial UID
+uids_.insert(DicomTag(0x0070, 0x1101));                          // Presentation Display Collection UID
+uids_.insert(DicomTag(0x0070, 0x1102));                          // Presentation Sequence Collection UID
+uids_.insert(DicomTag(0x0088, 0x0140));                          // Storage Media File-set UID
+uids_.insert(DicomTag(0x0400, 0x0100));                          // Digital Signature UID
+uids_.insert(DicomTag(0x3006, 0x0024));                          // Referenced Frame of Reference UID
+uids_.insert(DicomTag(0x3006, 0x00c2));                          // Related Frame of Reference UID
+uids_.insert(DicomTag(0x300a, 0x0013));                          // Dose Reference UID
+uids_.insert(DicomTag(0x300a, 0x0083));                          // Referenced Dose Reference UID
+uids_.insert(DicomTag(0x300a, 0x0609));                          // Treatment Position Group UID
+uids_.insert(DicomTag(0x300a, 0x0650));                          // Patient Setup UID
+uids_.insert(DicomTag(0x300a, 0x0700));                          // Treatment Session UID
+uids_.insert(DicomTag(0x3010, 0x0006));                          // Conceptual Volume UID
+uids_.insert(DicomTag(0x3010, 0x000b));                          // Referenced Conceptual Volume UID
+uids_.insert(DicomTag(0x3010, 0x0013));                          // Constituent Conceptual Volume UID
+uids_.insert(DicomTag(0x3010, 0x0015));                          // Source Conceptual Volume UID
+uids_.insert(DicomTag(0x3010, 0x0031));                          // Referenced Fiducials UID
+uids_.insert(DicomTag(0x3010, 0x003b));                          // RT Treatment Phase UID
+uids_.insert(DicomTag(0x3010, 0x006e));                          // Dosimetric Objective UID
+uids_.insert(DicomTag(0x3010, 0x006f));                          // Referenced Dosimetric Objective UID
--- a/OrthancServer/Resources/GenerateAnonymizationProfile.py	Thu Jun 03 21:06:34 2021 +0200
+++ b/OrthancServer/Resources/GenerateAnonymizationProfile.py	Fri Jun 04 17:38:43 2021 +0200
@@ -62,7 +62,7 @@
     indentation = 65
     
     if len(command) > indentation:
-        raise Exception('Too long command')
+        indentation = len(command) + 2
         
     line = command + (' ' * (indentation - len(command))) + '// ' + name
     LINES.append(line)
@@ -71,7 +71,7 @@
     FormatLine('// TODO: %s with rule %s' % (rawTag, profile), name)
 
     
-RAW_TAG_RE = re.compile(r'^\(\s*([0-9A-F]{4})\s*,\s*([0-9A-F]{4})\s*\)$')
+RAW_TAG_RE = re.compile(r'^\(\s*([0-9A-Fx]{4})\s*,\s*([0-9A-Fx]{4})\s*\)$')
 
 
 for table in root.iter('%stable' % br):
@@ -86,30 +86,37 @@
 
             match = RAW_TAG_RE.match(rawTag)
             if match == None:
-                FormatUnknown(rawTag, name, profile)
+                raise Exception('Unsupported rule: %s, %s, %s' % (rawTag, profile, name))
             else:
-                tag = '0x%s, 0x%s' % (match.group(1).lower(), match.group(2).lower())
+                group = match.group(1).lower()
+                element = match.group(2).lower()
+                tag = '0x%s, 0x%s' % (group, element)
 
-                if name in [
-                        'SOP Instance UID',
-                        'Series Instance UID',
-                        'Study Instance UID',
+                if 'x' in group or 'x' in element:
+                    if profile == 'X':
+                        groupFrom = group.replace('x', '0')
+                        groupTo = group.replace('x', 'f')
+                        elementFrom = element.replace('x', '0')
+                        elementTo = element.replace('x', 'f')
+                        FormatLine('removedRanges_.push_back(DicomTagRange(0x%s, 0x%s, 0x%s, 0x%s));' % (
+                            groupFrom, groupTo, elementFrom, elementTo), name)
+                    else:
+                        raise Exception('Unsupported rule: %s, %s, %s' % (rawTag, profile, name))
+                elif tag in [
+                        '0x0008, 0x0018',   # SOP Instance UID
+                        '0x0020, 0x000e',   # Series Instance UID
+                        '0x0020, 0x000d',   # Study Instance UID
                 ]:
                     FormatLine('// Tag (%s) is set in Apply()         /* %s */' % (tag, profile), name)
-                elif name in [
-                        'Referenced Image Sequence',
-                        'Source Image Sequence',
-                        'Referenced SOP Instance UID',
-                        'Frame of Reference UID',
-                        'Referenced Frame of Reference UID',
-                        'Related Frame of Reference UID',
-                ]:
-                    FormatLine('// Tag (%s) => RelationshipsVisitor   /* %s */' % (tag, profile), name)
-                elif name in [
-                        'Patient\'s Name',
-                        'Patient ID',
+                elif tag in [
+                        '0x0010, 0x0010',   # Patient's Name
+                        '0x0010, 0x0020',   # Patient ID
                 ]:
                     FormatLine('// Tag (%s) is set below (*)          /* %s */' % (tag, profile), name)
+                elif profile == 'U':
+                    FormatLine('uids_.insert(DicomTag(%s));' % (tag), name)
+                elif profile == 'X/Z/U*':
+                    FormatLine('// RelationshipsVisitor handles (%s)  /* %s */' % (tag, profile), name)
                 elif profile == 'X':
                     FormatLine('removals_.insert(DicomTag(%s));' % tag, name)
                 elif profile.startswith('X/'):
@@ -118,10 +125,9 @@
                     FormatLine('clearings_.insert(DicomTag(%s));' % tag, name)
                 elif profile == 'D' or profile.startswith('Z/'):
                     FormatLine('clearings_.insert(DicomTag(%s));  /* %s */' % (tag, profile), name)
-                elif profile == 'U':
-                    FormatLine('removals_.insert(DicomTag(%s));   /* TODO UID */' % (tag), name)
                 else:
-                    FormatUnknown(rawTag, name, profile)
+                    # FormatUnknown(rawTag, name, profile)
+                    raise Exception('Unsupported rule: %s, %s, %s' % (rawTag, profile, name))
 
 for line in sorted(LINES):
     print(line.encode('ascii', 'ignore').decode('ascii'))
--- a/OrthancServer/UnitTestsSources/ServerJobsTests.cpp	Thu Jun 03 21:06:34 2021 +0200
+++ b/OrthancServer/UnitTestsSources/ServerJobsTests.cpp	Fri Jun 04 17:38:43 2021 +0200
@@ -698,6 +698,24 @@
     ASSERT_EQ(RequestOrigin_Lua, tmp.GetRequestOrigin());
     ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION));
   }
+
+  {
+    std::unique_ptr<DicomModification> modification(new DicomModification);
+    modification->SetupAnonymization(DicomVersion_2021b);
+    
+    ModifyInstanceOperation operation(GetContext(), RequestOrigin_Lua, modification.release());
+
+    ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation));
+    operation.Serialize(s);
+  }
+
+  {
+    operation.reset(unserializer.UnserializeOperation(s));
+
+    const ModifyInstanceOperation& tmp = dynamic_cast<ModifyInstanceOperation&>(*operation);
+    ASSERT_EQ(RequestOrigin_Lua, tmp.GetRequestOrigin());
+    ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION));
+  }
 }