# HG changeset patch # User amazy # Date 1567681895 -7200 # Node ID 7db879b014ffbacb2080bd9a160500cfafad16f7 # Parent 4bced7d1ec2065ed7dc0f62a070591c60dc512fd Fix lost relationships between CT and RT-STRUCT during anonymization diff -r 4bced7d1ec20 -r 7db879b014ff Core/DicomFormat/DicomTag.h --- 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); } diff -r 4bced7d1ec20 -r 7db879b014ff Core/DicomParsing/DicomModification.cpp --- 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); diff -r 4bced7d1ec20 -r 7db879b014ff Core/DicomParsing/DicomModification.h --- 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); diff -r 4bced7d1ec20 -r 7db879b014ff NEWS --- 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) ==========================