changeset 3513:7db879b014ff

Fix lost relationships between CT and RT-STRUCT during anonymization
author amazy
date Thu, 05 Sep 2019 13:11:35 +0200
parents 4bced7d1ec20
children 13f9ccf05a8e fc26659493b6
files Core/DicomFormat/DicomTag.h Core/DicomParsing/DicomModification.cpp Core/DicomParsing/DicomModification.h NEWS
diffstat 4 files changed, 74 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/Core/DicomFormat/DicomTag.h	Wed Sep 04 18:23:22 2019 +0200
+++ b/Core/DicomFormat/DicomTag.h	Thu Sep 05 13:11:35 2019 +0200
@@ -220,4 +220,7 @@
   static const DicomTag DICOM_TAG_RELATED_FRAME_OF_REFERENCE_UID(0x3006, 0x00c2);
   static const DicomTag DICOM_TAG_CURRENT_REQUESTED_PROCEDURE_EVIDENCE_SEQUENCE(0x0040, 0xa375);
   static const DicomTag DICOM_TAG_REFERENCED_SERIES_SEQUENCE(0x0008, 0x1115);
+  static const DicomTag DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_SEQUENCE(0x3006, 0x0010);
+  static const DicomTag DICOM_TAG_RT_REFERENCED_STUDY_SEQUENCE(0x3006, 0x0012);
+  static const DicomTag DICOM_TAG_RT_REFERENCED_SERIES_SEQUENCE(0x3006, 0x0014);
 }
--- a/Core/DicomParsing/DicomModification.cpp	Wed Sep 04 18:23:22 2019 +0200
+++ b/Core/DicomParsing/DicomModification.cpp	Thu Sep 05 13:11:35 2019 +0200
@@ -135,7 +135,18 @@
       {
         return Action_None;
       }
-      else if (tag == DICOM_TAG_FRAME_OF_REFERENCE_UID || 
+      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)
@@ -158,6 +169,15 @@
         newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Series);
         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)
+      {
+        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)
@@ -249,7 +269,17 @@
     }
   }
 
+  void DicomModification::RegisterMappedDicomIdentifier(const std::string& original,
+                                                        const std::string& mapped,
+                                                        ResourceType level)
+  {
+    UidMap::const_iterator previous = uidMap_.find(std::make_pair(level, original));
 
+    if (previous == uidMap_.end())
+    {
+      uidMap_.insert(std::make_pair(std::make_pair(level, original), mapped));
+    }
+  }
 
   std::string DicomModification::MapDicomIdentifier(const std::string& original,
                                                     ResourceType level)
@@ -976,7 +1006,6 @@
                              "When modifying an instance, the parent SeriesInstanceUID cannot be manually modified");
     }
 
-
     // (0) Create a summary of the source file, if a custom generator
     // is provided
     if (identifierGenerator_ != NULL)
@@ -984,35 +1013,64 @@
       toModify.ExtractDicomSummary(currentSource_);
     }
 
+    // (1) Make sure the relationships are updated with the ids that we force too
+    // i.e: an RT-STRUCT is referencing its own StudyInstanceUID
+    if (isAnonymization_ && updateReferencedRelationships_)
+    {
+      if (IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
+      {
+        std::string original;
+        std::string replacement = GetReplacementAsString(DICOM_TAG_STUDY_INSTANCE_UID);
+        toModify.GetTagValue(original, DICOM_TAG_STUDY_INSTANCE_UID);
+        RegisterMappedDicomIdentifier(original, replacement, ResourceType_Study);
+      }
 
-    // (1) Remove the private tags, if need be
+      if (IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
+      {
+        std::string original;
+        std::string replacement = GetReplacementAsString(DICOM_TAG_SERIES_INSTANCE_UID);
+        toModify.GetTagValue(original, DICOM_TAG_SERIES_INSTANCE_UID);
+        RegisterMappedDicomIdentifier(original, replacement, ResourceType_Series);
+      }
+
+      if (IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
+      {
+        std::string original;
+        std::string replacement = GetReplacementAsString(DICOM_TAG_SOP_INSTANCE_UID);
+        toModify.GetTagValue(original, DICOM_TAG_SOP_INSTANCE_UID);
+        RegisterMappedDicomIdentifier(original, replacement, ResourceType_Instance);
+      }
+    }
+
+
+    // (2) Remove the private tags, if need be
     if (removePrivateTags_)
     {
       toModify.RemovePrivateTags(privateTagsToKeep_);
     }
 
-    // (2) Clear the tags specified by the user
+    // (3) Clear the tags specified by the user
     for (SetOfTags::const_iterator it = clearings_.begin(); 
          it != clearings_.end(); ++it)
     {
       toModify.Clear(*it, true /* only clear if the tag exists in the original file */);
     }
 
-    // (3) Remove the tags specified by the user
+    // (4) Remove the tags specified by the user
     for (SetOfTags::const_iterator it = removals_.begin(); 
          it != removals_.end(); ++it)
     {
       toModify.Remove(*it);
     }
 
-    // (4) Replace the tags
+    // (5) Replace the tags
     for (Replacements::const_iterator it = replacements_.begin(); 
          it != replacements_.end(); ++it)
     {
       toModify.Replace(it->first, *it->second, true /* decode data URI scheme */, DicomReplaceMode_InsertIfAbsent);
     }
 
-    // (5) Update the DICOM identifiers
+    // (6) Update the DICOM identifiers
     if (level_ <= ResourceType_Study &&
         !IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
     {
@@ -1045,7 +1103,7 @@
       MapDicomTags(toModify, ResourceType_Instance);
     }
 
-    // (6) Update the "referenced" relationships in the case of an anonymization
+    // (7) Update the "referenced" relationships in the case of an anonymization
     if (isAnonymization_)
     {
       RelationshipsVisitor visitor(*this);
--- a/Core/DicomParsing/DicomModification.h	Wed Sep 04 18:23:22 2019 +0200
+++ b/Core/DicomParsing/DicomModification.h	Thu Sep 05 13:11:35 2019 +0200
@@ -92,6 +92,10 @@
     std::string MapDicomIdentifier(const std::string& original,
                                    ResourceType level);
 
+    void RegisterMappedDicomIdentifier(const std::string& original,
+                                       const std::string& mapped,
+                                       ResourceType level);
+
     void MapDicomTags(ParsedDicomFile& dicom,
                       ResourceType level);
 
--- a/NEWS	Wed Sep 04 18:23:22 2019 +0200
+++ b/NEWS	Thu Sep 05 13:11:35 2019 +0200
@@ -12,6 +12,7 @@
 * Orthanc Explorer: include the url search params into HTTP headers to the Rest API to ease usage of the Authorization plugin
   Note that only the 'token', 'auth-token' & 'authorization' search params are transmitted into HTTP headers.
 * in /ordered-slices route, ignore instances without position/normal/seriesIndex
+* Fix lost relationships between CT and RT-STRUCT during anonymization
 
 Version 1.5.7 (2019-06-25)
 ==========================