Mercurial > hg > orthanc
view Core/DicomParsing/DicomModification.cpp @ 3668:adb6d8b49283 storage-commitment
integration mainline->storage-commitment
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 13 Feb 2020 18:44:53 +0100 |
parents | 94f4a18a79cc |
children | 4922bdd046dd |
line wrap: on
line source
/** * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * In addition, as a special exception, the copyright holders of this * program give permission to link the code of its release with the * OpenSSL project's "OpenSSL" library (or with modified versions of it * that use the same license as the "OpenSSL" library), and distribute * the linked executables. You must obey the GNU General Public License * in all respects for all of the code used other than "OpenSSL". If you * modify file(s) with this exception, you may extend this exception to * your version of the file(s), but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source files * in the program, then also delete it here. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ #include "../PrecompiledHeaders.h" #include "DicomModification.h" #include "../Logging.h" #include "../OrthancException.h" #include "../SerializationToolbox.h" #include "FromDcmtkBridge.h" #include "ITagVisitor.h" #include <memory> // For std::auto_ptr static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2008 = "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1"; static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2017c = "Orthanc " ORTHANC_VERSION " - PS 3.15-2017c Table E.1-1 Basic Profile"; namespace Orthanc { class DicomModification::RelationshipsVisitor : public ITagVisitor { private: DicomModification& that_; bool IsEnabled(const DicomTag& tag) const { return (!that_.IsCleared(tag) && !that_.IsRemoved(tag) && !that_.IsReplaced(tag)); } void RemoveIfEnabled(ParsedDicomFile& dicom, const DicomTag& tag) const { if (IsEnabled(tag)) { dicom.Remove(tag); } } public: RelationshipsVisitor(DicomModification& that) : that_(that) { } virtual void VisitNotSupported(const std::vector<DicomTag>& parentTags, const std::vector<size_t>& parentIndexes, const DicomTag& tag, ValueRepresentation vr) { } virtual void VisitEmptySequence(const std::vector<DicomTag>& parentTags, const std::vector<size_t>& parentIndexes, const DicomTag& tag) { } virtual void VisitBinary(const std::vector<DicomTag>& parentTags, const std::vector<size_t>& parentIndexes, const DicomTag& tag, ValueRepresentation vr, const void* data, size_t size) { } virtual void VisitIntegers(const std::vector<DicomTag>& parentTags, const std::vector<size_t>& parentIndexes, const DicomTag& tag, ValueRepresentation vr, const std::vector<int64_t>& values) { } virtual void VisitDoubles(const std::vector<DicomTag>& parentTags, const std::vector<size_t>& parentIndexes, const DicomTag& tag, ValueRepresentation vr, const std::vector<double>& value) { } virtual void VisitAttributes(const std::vector<DicomTag>& parentTags, const std::vector<size_t>& parentIndexes, const DicomTag& tag, const std::vector<DicomTag>& value) { } virtual Action VisitString(std::string& newValue, const std::vector<DicomTag>& parentTags, const std::vector<size_t>& parentIndexes, const DicomTag& tag, ValueRepresentation vr, const std::string& value) { 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 && tag == DICOM_TAG_STUDY_INSTANCE_UID) { newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(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 && tag == DICOM_TAG_SERIES_INSTANCE_UID) { 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) { newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Series); return Action_Replace; } else { return Action_None; } } void RemoveRelationships(ParsedDicomFile& dicom) const { // Sequences containing the UID relationships 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); } }; bool DicomModification::CancelReplacement(const DicomTag& tag) { Replacements::iterator it = replacements_.find(tag); if (it != replacements_.end()) { delete it->second; replacements_.erase(it); return true; } else { return false; } } void DicomModification::ReplaceInternal(const DicomTag& tag, const Json::Value& value) { Replacements::iterator it = replacements_.find(tag); if (it != replacements_.end()) { delete it->second; it->second = NULL; // In the case of an exception during the clone it->second = new Json::Value(value); // Clone } else { replacements_[tag] = new Json::Value(value); // Clone } } void DicomModification::ClearReplacements() { for (Replacements::iterator it = replacements_.begin(); it != replacements_.end(); ++it) { delete it->second; } replacements_.clear(); } void DicomModification::MarkNotOrthancAnonymization() { Replacements::iterator it = replacements_.find(DICOM_TAG_DEIDENTIFICATION_METHOD); if (it != replacements_.end() && (it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2008 || it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2017c)) { delete it->second; replacements_.erase(it); } } 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) { std::string mapped; UidMap::const_iterator previous = uidMap_.find(std::make_pair(level, original)); if (previous == uidMap_.end()) { if (identifierGenerator_ == NULL) { mapped = FromDcmtkBridge::GenerateUniqueIdentifier(level); } else { if (!identifierGenerator_->Apply(mapped, original, level, currentSource_)) { throw OrthancException(ErrorCode_InternalError, "Unable to generate an anonymized ID"); } } uidMap_.insert(std::make_pair(std::make_pair(level, original), mapped)); } else { mapped = previous->second; } return mapped; } void DicomModification::MapDicomTags(ParsedDicomFile& dicom, ResourceType level) { std::auto_ptr<DicomTag> tag; switch (level) { case ResourceType_Study: tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); break; case ResourceType_Series: tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); break; case ResourceType_Instance: tag.reset(new DicomTag(DICOM_TAG_SOP_INSTANCE_UID)); break; default: throw OrthancException(ErrorCode_InternalError); } std::string original; if (!dicom.GetTagValue(original, *tag)) { original = ""; } std::string mapped = MapDicomIdentifier(Toolbox::StripSpaces(original), level); dicom.Replace(*tag, mapped, false /* don't try and decode data URI scheme for UIDs */, DicomReplaceMode_InsertIfAbsent); } DicomModification::DicomModification() : removePrivateTags_(false), level_(ResourceType_Instance), allowManualIdentifiers_(true), keepStudyInstanceUid_(false), keepSeriesInstanceUid_(false), updateReferencedRelationships_(true), isAnonymization_(false), identifierGenerator_(NULL) { } DicomModification::~DicomModification() { ClearReplacements(); } void DicomModification::Keep(const DicomTag& tag) { bool wasRemoved = IsRemoved(tag); bool wasCleared = IsCleared(tag); removals_.erase(tag); clearings_.erase(tag); bool wasReplaced = CancelReplacement(tag); if (tag == DICOM_TAG_STUDY_INSTANCE_UID) { keepStudyInstanceUid_ = true; } else if (tag == DICOM_TAG_SERIES_INSTANCE_UID) { keepSeriesInstanceUid_ = true; } else if (tag.IsPrivate()) { privateTagsToKeep_.insert(tag); } else if (!wasRemoved && !wasReplaced && !wasCleared) { LOG(WARNING) << "Marking this tag as to be kept has no effect: " << tag.Format(); } MarkNotOrthancAnonymization(); } void DicomModification::Remove(const DicomTag& tag) { removals_.insert(tag); clearings_.erase(tag); CancelReplacement(tag); privateTagsToKeep_.erase(tag); MarkNotOrthancAnonymization(); } void DicomModification::Clear(const DicomTag& tag) { removals_.erase(tag); clearings_.insert(tag); CancelReplacement(tag); privateTagsToKeep_.erase(tag); MarkNotOrthancAnonymization(); } bool DicomModification::IsRemoved(const DicomTag& tag) const { return removals_.find(tag) != removals_.end(); } bool DicomModification::IsCleared(const DicomTag& tag) const { return clearings_.find(tag) != clearings_.end(); } void DicomModification::Replace(const DicomTag& tag, const Json::Value& value, bool safeForAnonymization) { clearings_.erase(tag); removals_.erase(tag); privateTagsToKeep_.erase(tag); ReplaceInternal(tag, value); if (!safeForAnonymization) { MarkNotOrthancAnonymization(); } } bool DicomModification::IsReplaced(const DicomTag& tag) const { return replacements_.find(tag) != replacements_.end(); } const Json::Value& DicomModification::GetReplacement(const DicomTag& tag) const { Replacements::const_iterator it = replacements_.find(tag); if (it == replacements_.end()) { throw OrthancException(ErrorCode_InexistentItem); } else { return *it->second; } } std::string DicomModification::GetReplacementAsString(const DicomTag& tag) const { const Json::Value& json = GetReplacement(tag); if (json.type() != Json::stringValue) { throw OrthancException(ErrorCode_BadParameterType); } else { return json.asString(); } } void DicomModification::SetRemovePrivateTags(bool removed) { removePrivateTags_ = removed; if (!removed) { MarkNotOrthancAnonymization(); } } void DicomModification::SetLevel(ResourceType level) { uidMap_.clear(); level_ = level; if (level != ResourceType_Patient) { MarkNotOrthancAnonymization(); } } 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 removals_.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID //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 removals_.insert(DicomTag(0x0008, 0x0081)); // Institution Address removals_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name removals_.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address removals_.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers removals_.insert(DicomTag(0x0008, 0x1010)); // Station Name removals_.insert(DicomTag(0x0008, 0x1030)); // Study Description removals_.insert(DicomTag(0x0008, 0x103e)); // Series Description removals_.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name 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 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 (*) removals_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date removals_.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time removals_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex removals_.insert(DicomTag(0x0010, 0x1000)); // Other Patient Ids removals_.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names removals_.insert(DicomTag(0x0010, 0x1010)); // Patient's Age removals_.insert(DicomTag(0x0010, 0x1020)); // Patient's Size removals_.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight removals_.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator removals_.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group removals_.insert(DicomTag(0x0010, 0x2180)); // Occupation removals_.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History removals_.insert(DicomTag(0x0010, 0x4000)); // Patient Comments removals_.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number removals_.insert(DicomTag(0x0018, 0x1030)); // Protocol Name //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 removals_.insert(DicomTag(0x0020, 0x4000)); // Image Comments removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence removals_.insert(DicomTag(0x0040, 0xa124)); // UID 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 // Some more removals (from the experience of DICOM files at the CHU of Liege) removals_.insert(DicomTag(0x0010, 0x1040)); // Patient's Address removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician removals_.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers removals_.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts // Set the DeidentificationMethod tag ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2008); } void DicomModification::SetupAnonymization2017c() { /** * This is Table E.1-1 from PS 3.15-2017c (DICOM Part 15: Security * and System Management Profiles), "basic profile" column. It was * generated automatically with the * "../Resources/GenerateAnonymizationProfile.py" script. * https://raw.githubusercontent.com/jodogne/dicom-specification/master/2017c/part15.pdf **/ // TODO: (50xx,xxxx) with rule X // Curve Data // TODO: (60xx,3000) with rule X // Overlay Data // TODO: (60xx,4000) with rule X // Overlay Comments // 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 clearings_.insert(DicomTag(0x0008, 0x0033)); /* Z/D */ // Content Time clearings_.insert(DicomTag(0x0008, 0x0050)); // Accession Number clearings_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name clearings_.insert(DicomTag(0x0008, 0x009c)); // Consulting Physician's Name clearings_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date clearings_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex clearings_.insert(DicomTag(0x0018, 0x0010)); /* Z/D */ // Contrast Bolus Agent clearings_.insert(DicomTag(0x0020, 0x0010)); // Study ID clearings_.insert(DicomTag(0x0040, 0x1101)); /* D */ // Person Identification Code Sequence clearings_.insert(DicomTag(0x0040, 0x2016)); // Placer Order Number / Imaging Service Request clearings_.insert(DicomTag(0x0040, 0x2017)); // Filler Order Number / Imaging Service Request clearings_.insert(DicomTag(0x0040, 0xa073)); /* D */ // Verifying Observer Sequence clearings_.insert(DicomTag(0x0040, 0xa075)); /* D */ // Verifying Observer Name clearings_.insert(DicomTag(0x0040, 0xa088)); // Verifying Observer Identification Code Sequence clearings_.insert(DicomTag(0x0040, 0xa123)); /* D */ // Person Name 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 removals_.insert(DicomTag(0x0008, 0x0024)); // Overlay Date removals_.insert(DicomTag(0x0008, 0x0025)); // Curve Date removals_.insert(DicomTag(0x0008, 0x002a)); /* X/D */ // Acquisition DateTime removals_.insert(DicomTag(0x0008, 0x0031)); /* X/D */ // Series Time 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 removals_.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address removals_.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers removals_.insert(DicomTag(0x0008, 0x0096)); // Referring Physician Identification Sequence removals_.insert(DicomTag(0x0008, 0x009d)); // Consulting Physician Identification Sequence removals_.insert(DicomTag(0x0008, 0x0201)); // Timezone Offset From UTC removals_.insert(DicomTag(0x0008, 0x1010)); /* X/Z/D */ // Station Name removals_.insert(DicomTag(0x0008, 0x1030)); // Study Description removals_.insert(DicomTag(0x0008, 0x103e)); // Series Description removals_.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record removals_.insert(DicomTag(0x0008, 0x1049)); // Physician(s) of Record Identification Sequence removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name removals_.insert(DicomTag(0x0008, 0x1052)); // Performing Physician Identification Sequence removals_.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study removals_.insert(DicomTag(0x0008, 0x1062)); // Physician(s) Reading Study Identification Sequence removals_.insert(DicomTag(0x0008, 0x1070)); /* X/Z/D */ // Operators' Name removals_.insert(DicomTag(0x0008, 0x1072)); /* X/D */ // Operators' Identification Sequence removals_.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description removals_.insert(DicomTag(0x0008, 0x1084)); // Admitting Diagnoses Code Sequence 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 removals_.insert(DicomTag(0x0010, 0x0050)); // Patient's Insurance Plan Code Sequence removals_.insert(DicomTag(0x0010, 0x0101)); // Patient's Primary Language Code Sequence removals_.insert(DicomTag(0x0010, 0x0102)); // Patient's Primary Language Modifier Code Sequence removals_.insert(DicomTag(0x0010, 0x1000)); // Other Patient IDs removals_.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names removals_.insert(DicomTag(0x0010, 0x1002)); // Other Patient IDs Sequence removals_.insert(DicomTag(0x0010, 0x1005)); // Patient's Birth Name removals_.insert(DicomTag(0x0010, 0x1010)); // Patient's Age removals_.insert(DicomTag(0x0010, 0x1020)); // Patient's Size removals_.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight removals_.insert(DicomTag(0x0010, 0x1040)); // Patient Address removals_.insert(DicomTag(0x0010, 0x1050)); // Insurance Plan Identification removals_.insert(DicomTag(0x0010, 0x1060)); // Patient's Mother's Birth Name removals_.insert(DicomTag(0x0010, 0x1080)); // Military Rank removals_.insert(DicomTag(0x0010, 0x1081)); // Branch of Service removals_.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator removals_.insert(DicomTag(0x0010, 0x1100)); // Referenced Patient Photo Sequence removals_.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts removals_.insert(DicomTag(0x0010, 0x2110)); // Allergies removals_.insert(DicomTag(0x0010, 0x2150)); // Country of Residence removals_.insert(DicomTag(0x0010, 0x2152)); // Region of Residence removals_.insert(DicomTag(0x0010, 0x2154)); // Patient's Telephone Numbers removals_.insert(DicomTag(0x0010, 0x2155)); // Patient's Telecom Information removals_.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group removals_.insert(DicomTag(0x0010, 0x2180)); // Occupation removals_.insert(DicomTag(0x0010, 0x21a0)); // Smoking Status removals_.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History removals_.insert(DicomTag(0x0010, 0x21c0)); // Pregnancy Status removals_.insert(DicomTag(0x0010, 0x21d0)); // Last Menstrual Date removals_.insert(DicomTag(0x0010, 0x21f0)); // Patient's Religious Preference removals_.insert(DicomTag(0x0010, 0x2203)); /* X/Z */ // Patient Sex Neutered removals_.insert(DicomTag(0x0010, 0x2297)); // Responsible Person 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 removals_.insert(DicomTag(0x0032, 0x1021)); // Scheduled Study Location AE Title removals_.insert(DicomTag(0x0032, 0x1030)); // Reason for Study removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician removals_.insert(DicomTag(0x0032, 0x1033)); // Requesting Service removals_.insert(DicomTag(0x0032, 0x1060)); /* X/Z */ // Requested Procedure Description removals_.insert(DicomTag(0x0032, 0x1070)); // Requested Contrast Agent removals_.insert(DicomTag(0x0032, 0x4000)); // Study Comments removals_.insert(DicomTag(0x0038, 0x0004)); // Referenced Patient Alias Sequence removals_.insert(DicomTag(0x0038, 0x0010)); // Admission ID removals_.insert(DicomTag(0x0038, 0x0011)); // Issuer of Admission ID removals_.insert(DicomTag(0x0038, 0x001e)); // Scheduled Patient Institution Residence removals_.insert(DicomTag(0x0038, 0x0020)); // Admitting Date removals_.insert(DicomTag(0x0038, 0x0021)); // Admitting Time removals_.insert(DicomTag(0x0038, 0x0040)); // Discharge Diagnosis Description removals_.insert(DicomTag(0x0038, 0x0050)); // Special Needs removals_.insert(DicomTag(0x0038, 0x0060)); // Service Episode ID removals_.insert(DicomTag(0x0038, 0x0061)); // Issuer of Service Episode ID removals_.insert(DicomTag(0x0038, 0x0062)); // Service Episode Description removals_.insert(DicomTag(0x0038, 0x0300)); // Current Patient Location 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(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 removals_.insert(DicomTag(0x0040, 0x0004)); // Scheduled Procedure Step End Date removals_.insert(DicomTag(0x0040, 0x0005)); // Scheduled Procedure Step End Time removals_.insert(DicomTag(0x0040, 0x0006)); // Scheduled Performing Physician Name removals_.insert(DicomTag(0x0040, 0x0007)); // Scheduled Procedure Step Description removals_.insert(DicomTag(0x0040, 0x000b)); // Scheduled Performing Physician Identification Sequence removals_.insert(DicomTag(0x0040, 0x0010)); // Scheduled Station Name removals_.insert(DicomTag(0x0040, 0x0011)); // Scheduled Procedure Step Location removals_.insert(DicomTag(0x0040, 0x0012)); // Pre-Medication removals_.insert(DicomTag(0x0040, 0x0241)); // Performed Station AE Title removals_.insert(DicomTag(0x0040, 0x0242)); // Performed Station Name removals_.insert(DicomTag(0x0040, 0x0243)); // Performed Location removals_.insert(DicomTag(0x0040, 0x0244)); // Performed Procedure Step Start Date removals_.insert(DicomTag(0x0040, 0x0245)); // Performed Procedure Step Start Time removals_.insert(DicomTag(0x0040, 0x0250)); // Performed Procedure Step End Date removals_.insert(DicomTag(0x0040, 0x0251)); // Performed Procedure Step End Time removals_.insert(DicomTag(0x0040, 0x0253)); // Performed Procedure Step ID removals_.insert(DicomTag(0x0040, 0x0254)); // Performed Procedure Step Description removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence removals_.insert(DicomTag(0x0040, 0x0280)); // Comments on the Performed Procedure Step removals_.insert(DicomTag(0x0040, 0x0555)); // Acquisition Context Sequence removals_.insert(DicomTag(0x0040, 0x1001)); // Requested Procedure ID removals_.insert(DicomTag(0x0040, 0x1004)); // Patient Transport Arrangements removals_.insert(DicomTag(0x0040, 0x1005)); // Requested Procedure Location removals_.insert(DicomTag(0x0040, 0x1010)); // Names of Intended Recipient of Results removals_.insert(DicomTag(0x0040, 0x1011)); // Intended Recipients of Results Identification Sequence removals_.insert(DicomTag(0x0040, 0x1102)); // Person Address removals_.insert(DicomTag(0x0040, 0x1103)); // Person's Telephone Numbers removals_.insert(DicomTag(0x0040, 0x1104)); // Person's Telecom Information removals_.insert(DicomTag(0x0040, 0x1400)); // Requested Procedure Comments removals_.insert(DicomTag(0x0040, 0x2001)); // Reason for the Imaging Service Request removals_.insert(DicomTag(0x0040, 0x2008)); // Order Entered By removals_.insert(DicomTag(0x0040, 0x2009)); // Order Enterer Location removals_.insert(DicomTag(0x0040, 0x2010)); // Order Callback Phone Number removals_.insert(DicomTag(0x0040, 0x2011)); // Order Callback Telecom Information removals_.insert(DicomTag(0x0040, 0x2400)); // Imaging Service Request Comments removals_.insert(DicomTag(0x0040, 0x3001)); // Confidentiality Constraint on Patient Data Description 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 removals_.insert(DicomTag(0x0040, 0x4030)); // Performed Station Geographic Location Code Sequence removals_.insert(DicomTag(0x0040, 0x4034)); // Scheduled Human Performers Sequence removals_.insert(DicomTag(0x0040, 0x4035)); // Actual Human Performers Sequence removals_.insert(DicomTag(0x0040, 0x4036)); // Human Performers Organization removals_.insert(DicomTag(0x0040, 0x4037)); // Human Performers Name removals_.insert(DicomTag(0x0040, 0x4050)); // Performed Procedure Step Start DateTime removals_.insert(DicomTag(0x0040, 0x4051)); // Performed Procedure Step End DateTime removals_.insert(DicomTag(0x0040, 0x4052)); // Procedure Step Cancellation DateTime removals_.insert(DicomTag(0x0040, 0xa027)); // Verifying Organization 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) removals_.insert(DicomTag(0x0040, 0xa352)); // Verbal Source (Trial) 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 removals_.insert(DicomTag(0x0088, 0x0910)); // Topic Author removals_.insert(DicomTag(0x0088, 0x0912)); // Topic Keywords removals_.insert(DicomTag(0x0400, 0x0100)); // 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 removals_.insert(DicomTag(0x0400, 0x0550)); // Modified Attributes Sequence 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 removals_.insert(DicomTag(0x4000, 0x4000)); // Text Comments removals_.insert(DicomTag(0x4008, 0x0042)); // Results ID Issuer removals_.insert(DicomTag(0x4008, 0x0102)); // Interpretation Recorder removals_.insert(DicomTag(0x4008, 0x010a)); // Interpretation Transcriber removals_.insert(DicomTag(0x4008, 0x010b)); // Interpretation Text removals_.insert(DicomTag(0x4008, 0x010c)); // Interpretation Author removals_.insert(DicomTag(0x4008, 0x0111)); // Interpretation Approver Sequence removals_.insert(DicomTag(0x4008, 0x0114)); // Physician Approving Interpretation removals_.insert(DicomTag(0x4008, 0x0115)); // Interpretation Diagnosis Description removals_.insert(DicomTag(0x4008, 0x0118)); // Results Distribution List Sequence removals_.insert(DicomTag(0x4008, 0x0119)); // Distribution Name removals_.insert(DicomTag(0x4008, 0x011a)); // Distribution Address removals_.insert(DicomTag(0x4008, 0x0202)); // Interpretation ID Issuer removals_.insert(DicomTag(0x4008, 0x0300)); // Impressions removals_.insert(DicomTag(0x4008, 0x4000)); // Results Comments removals_.insert(DicomTag(0xfffa, 0xfffa)); // Digital Signatures Sequence removals_.insert(DicomTag(0xfffc, 0xfffc)); // Data Set Trailing Padding // Set the DeidentificationMethod tag ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2017c); } void DicomModification::SetupAnonymization(DicomVersion version) { isAnonymization_ = true; removals_.clear(); clearings_.clear(); ClearReplacements(); removePrivateTags_ = true; level_ = ResourceType_Patient; uidMap_.clear(); privateTagsToKeep_.clear(); switch (version) { case DicomVersion_2008: SetupAnonymization2008(); break; case DicomVersion_2017c: SetupAnonymization2017c(); break; default: throw OrthancException(ErrorCode_ParameterOutOfRange); } // Set the PatientIdentityRemoved tag ReplaceInternal(DicomTag(0x0012, 0x0062), "YES"); // (*) Choose a random patient name and ID std::string patientId = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient); ReplaceInternal(DICOM_TAG_PATIENT_ID, patientId); ReplaceInternal(DICOM_TAG_PATIENT_NAME, patientId); } void DicomModification::Apply(ParsedDicomFile& toModify) { // Check the request assert(ResourceType_Patient + 1 == ResourceType_Study && ResourceType_Study + 1 == ResourceType_Series && ResourceType_Series + 1 == ResourceType_Instance); if (IsRemoved(DICOM_TAG_PATIENT_ID) || IsRemoved(DICOM_TAG_STUDY_INSTANCE_UID) || IsRemoved(DICOM_TAG_SERIES_INSTANCE_UID) || IsRemoved(DICOM_TAG_SOP_INSTANCE_UID)) { throw OrthancException(ErrorCode_BadRequest); } // Sanity checks at the patient level if (level_ == ResourceType_Patient && !IsReplaced(DICOM_TAG_PATIENT_ID)) { throw OrthancException(ErrorCode_BadRequest, "When modifying a patient, her PatientID is required to be modified"); } if (!allowManualIdentifiers_) { if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) { throw OrthancException(ErrorCode_BadRequest, "When modifying a patient, the StudyInstanceUID cannot be manually modified"); } if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) { throw OrthancException(ErrorCode_BadRequest, "When modifying a patient, the SeriesInstanceUID cannot be manually modified"); } if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) { throw OrthancException(ErrorCode_BadRequest, "When modifying a patient, the SopInstanceUID cannot be manually modified"); } } // Sanity checks at the study level if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_PATIENT_ID)) { throw OrthancException(ErrorCode_BadRequest, "When modifying a study, the parent PatientID cannot be manually modified"); } if (!allowManualIdentifiers_) { if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) { throw OrthancException(ErrorCode_BadRequest, "When modifying a study, the SeriesInstanceUID cannot be manually modified"); } if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) { throw OrthancException(ErrorCode_BadRequest, "When modifying a study, the SopInstanceUID cannot be manually modified"); } } // Sanity checks at the series level if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_PATIENT_ID)) { throw OrthancException(ErrorCode_BadRequest, "When modifying a series, the parent PatientID cannot be manually modified"); } if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) { throw OrthancException(ErrorCode_BadRequest, "When modifying a series, the parent StudyInstanceUID cannot be manually modified"); } if (!allowManualIdentifiers_) { if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) { throw OrthancException(ErrorCode_BadRequest, "When modifying a series, the SopInstanceUID cannot be manually modified"); } } // Sanity checks at the instance level if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_PATIENT_ID)) { throw OrthancException(ErrorCode_BadRequest, "When modifying an instance, the parent PatientID cannot be manually modified"); } if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) { throw OrthancException(ErrorCode_BadRequest, "When modifying an instance, the parent StudyInstanceUID cannot be manually modified"); } if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) { throw OrthancException(ErrorCode_BadRequest, "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) { 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); } 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_); } // (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 */); } // (4) Remove the tags specified by the user for (SetOfTags::const_iterator it = removals_.begin(); it != removals_.end(); ++it) { toModify.Remove(*it); } // (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); } // (6) Update the DICOM identifiers if (level_ <= ResourceType_Study && !IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) { if (keepStudyInstanceUid_) { LOG(WARNING) << "Modifying a study while keeping its original StudyInstanceUID: This should be avoided!"; } else { MapDicomTags(toModify, ResourceType_Study); } } if (level_ <= ResourceType_Series && !IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) { if (keepSeriesInstanceUid_) { LOG(WARNING) << "Modifying a series while keeping its original SeriesInstanceUID: This should be avoided!"; } else { MapDicomTags(toModify, ResourceType_Series); } } if (level_ <= ResourceType_Instance && // Always true !IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) { MapDicomTags(toModify, ResourceType_Instance); } // (7) Update the "referenced" relationships in the case of an anonymization if (isAnonymization_) { RelationshipsVisitor visitor(*this); if (updateReferencedRelationships_) { toModify.Apply(visitor); } else { visitor.RemoveRelationships(toModify); } } } static bool IsDatabaseKey(const DicomTag& tag) { return (tag == DICOM_TAG_PATIENT_ID || tag == DICOM_TAG_STUDY_INSTANCE_UID || tag == DICOM_TAG_SERIES_INSTANCE_UID || tag == DICOM_TAG_SOP_INSTANCE_UID); } static void ParseListOfTags(DicomModification& target, const Json::Value& query, DicomModification::TagOperation operation, bool force) { if (!query.isArray()) { throw OrthancException(ErrorCode_BadRequest); } for (Json::Value::ArrayIndex i = 0; i < query.size(); i++) { if (query[i].type() != Json::stringValue) { throw OrthancException(ErrorCode_BadRequest); } std::string name = query[i].asString(); DicomTag tag = FromDcmtkBridge::ParseTag(name); if (!force && IsDatabaseKey(tag)) { throw OrthancException(ErrorCode_BadRequest, "Marking tag \"" + name + "\" as to be " + (operation == DicomModification::TagOperation_Keep ? "kept" : "removed") + " requires the \"Force\" option to be set to true"); } switch (operation) { case DicomModification::TagOperation_Keep: target.Keep(tag); VLOG(1) << "Keep: " << name << " " << tag; break; case DicomModification::TagOperation_Remove: target.Remove(tag); VLOG(1) << "Remove: " << name << " " << tag; break; default: throw OrthancException(ErrorCode_InternalError); } } } static void ParseReplacements(DicomModification& target, const Json::Value& replacements, bool force) { if (!replacements.isObject()) { throw OrthancException(ErrorCode_BadRequest); } Json::Value::Members members = replacements.getMemberNames(); for (size_t i = 0; i < members.size(); i++) { const std::string& name = members[i]; const Json::Value& value = replacements[name]; DicomTag tag = FromDcmtkBridge::ParseTag(name); if (!force && IsDatabaseKey(tag)) { throw OrthancException(ErrorCode_BadRequest, "Marking tag \"" + name + "\" as to be replaced " + "requires the \"Force\" option to be set to true"); } target.Replace(tag, value, false); VLOG(1) << "Replace: " << name << " " << tag << " == " << value.toStyledString(); } } static bool GetBooleanValue(const std::string& member, const Json::Value& json, bool defaultValue) { if (!json.isMember(member)) { return defaultValue; } else if (json[member].type() == Json::booleanValue) { return json[member].asBool(); } else { throw OrthancException(ErrorCode_BadFileFormat, "Member \"" + member + "\" should be a Boolean value"); } } void DicomModification::ParseModifyRequest(const Json::Value& request) { if (!request.isObject()) { throw OrthancException(ErrorCode_BadFileFormat); } bool force = GetBooleanValue("Force", request, false); if (GetBooleanValue("RemovePrivateTags", request, false)) { SetRemovePrivateTags(true); } if (request.isMember("Remove")) { ParseListOfTags(*this, request["Remove"], TagOperation_Remove, force); } if (request.isMember("Replace")) { ParseReplacements(*this, request["Replace"], force); } // The "Keep" operation only makes sense for the tags // StudyInstanceUID, SeriesInstanceUID and SOPInstanceUID. Avoid // this feature as much as possible, as this breaks the DICOM // model of the real world, except if you know exactly what // you're doing! if (request.isMember("Keep")) { ParseListOfTags(*this, request["Keep"], TagOperation_Keep, force); } } void DicomModification::ParseAnonymizationRequest(bool& patientNameReplaced, const Json::Value& request) { if (!request.isObject()) { throw OrthancException(ErrorCode_BadFileFormat); } bool force = GetBooleanValue("Force", request, false); // As of Orthanc 1.3.0, the default anonymization is done // according to PS 3.15-2017c Table E.1-1 (basic profile) DicomVersion version = DicomVersion_2017c; if (request.isMember("DicomVersion")) { if (request["DicomVersion"].type() != Json::stringValue) { throw OrthancException(ErrorCode_BadFileFormat); } else { version = StringToDicomVersion(request["DicomVersion"].asString()); } } SetupAnonymization(version); std::string patientName = GetReplacementAsString(DICOM_TAG_PATIENT_NAME); if (GetBooleanValue("KeepPrivateTags", request, false)) { SetRemovePrivateTags(false); } if (request.isMember("Remove")) { ParseListOfTags(*this, request["Remove"], TagOperation_Remove, force); } if (request.isMember("Replace")) { ParseReplacements(*this, request["Replace"], force); } if (request.isMember("Keep")) { ParseListOfTags(*this, request["Keep"], TagOperation_Keep, force); } patientNameReplaced = (IsReplaced(DICOM_TAG_PATIENT_NAME) && GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName); } static const char* REMOVE_PRIVATE_TAGS = "RemovePrivateTags"; static const char* LEVEL = "Level"; static const char* ALLOW_MANUAL_IDENTIFIERS = "AllowManualIdentifiers"; static const char* KEEP_STUDY_INSTANCE_UID = "KeepStudyInstanceUID"; static const char* KEEP_SERIES_INSTANCE_UID = "KeepSeriesInstanceUID"; static const char* UPDATE_REFERENCED_RELATIONSHIPS = "UpdateReferencedRelationships"; static const char* IS_ANONYMIZATION = "IsAnonymization"; static const char* REMOVALS = "Removals"; static const char* CLEARINGS = "Clearings"; static const char* PRIVATE_TAGS_TO_KEEP = "PrivateTagsToKeep"; static const char* REPLACEMENTS = "Replacements"; static const char* MAP_PATIENTS = "MapPatients"; static const char* MAP_STUDIES = "MapStudies"; static const char* MAP_SERIES = "MapSeries"; static const char* MAP_INSTANCES = "MapInstances"; void DicomModification::Serialize(Json::Value& value) const { if (identifierGenerator_ != NULL) { throw OrthancException(ErrorCode_InternalError, "Cannot serialize a DicomModification with a custom identifier generator"); } value = Json::objectValue; value[REMOVE_PRIVATE_TAGS] = removePrivateTags_; value[LEVEL] = EnumerationToString(level_); value[ALLOW_MANUAL_IDENTIFIERS] = allowManualIdentifiers_; value[KEEP_STUDY_INSTANCE_UID] = keepStudyInstanceUid_; value[KEEP_SERIES_INSTANCE_UID] = keepSeriesInstanceUid_; value[UPDATE_REFERENCED_RELATIONSHIPS] = updateReferencedRelationships_; value[IS_ANONYMIZATION] = isAnonymization_; SerializationToolbox::WriteSetOfTags(value, removals_, REMOVALS); SerializationToolbox::WriteSetOfTags(value, clearings_, CLEARINGS); SerializationToolbox::WriteSetOfTags(value, privateTagsToKeep_, PRIVATE_TAGS_TO_KEEP); Json::Value& tmp = value[REPLACEMENTS]; tmp = Json::objectValue; for (Replacements::const_iterator it = replacements_.begin(); it != replacements_.end(); ++it) { assert(it->second != NULL); tmp[it->first.Format()] = *it->second; } Json::Value& mapPatients = value[MAP_PATIENTS]; Json::Value& mapStudies = value[MAP_STUDIES]; Json::Value& mapSeries = value[MAP_SERIES]; Json::Value& mapInstances = value[MAP_INSTANCES]; mapPatients = Json::objectValue; mapStudies = Json::objectValue; mapSeries = Json::objectValue; mapInstances = Json::objectValue; for (UidMap::const_iterator it = uidMap_.begin(); it != uidMap_.end(); ++it) { Json::Value* tmp = NULL; switch (it->first.first) { case ResourceType_Patient: tmp = &mapPatients; break; case ResourceType_Study: tmp = &mapStudies; break; case ResourceType_Series: tmp = &mapSeries; break; case ResourceType_Instance: tmp = &mapInstances; break; default: throw OrthancException(ErrorCode_InternalError); } assert(tmp != NULL); (*tmp) [it->first.second] = it->second; } } void DicomModification::UnserializeUidMap(ResourceType level, const Json::Value& serialized, const char* field) { if (!serialized.isMember(field) || serialized[field].type() != Json::objectValue) { throw OrthancException(ErrorCode_BadFileFormat); } Json::Value::Members names = serialized[field].getMemberNames(); for (Json::Value::Members::const_iterator it = names.begin(); it != names.end(); ++it) { const Json::Value& value = serialized[field][*it]; if (value.type() != Json::stringValue) { throw OrthancException(ErrorCode_BadFileFormat); } else { uidMap_[std::make_pair(level, *it)] = value.asString(); } } } DicomModification::DicomModification(const Json::Value& serialized) : identifierGenerator_(NULL) { removePrivateTags_ = SerializationToolbox::ReadBoolean(serialized, REMOVE_PRIVATE_TAGS); level_ = StringToResourceType(SerializationToolbox::ReadString(serialized, LEVEL).c_str()); allowManualIdentifiers_ = SerializationToolbox::ReadBoolean(serialized, ALLOW_MANUAL_IDENTIFIERS); keepStudyInstanceUid_ = SerializationToolbox::ReadBoolean(serialized, KEEP_STUDY_INSTANCE_UID); keepSeriesInstanceUid_ = SerializationToolbox::ReadBoolean(serialized, KEEP_SERIES_INSTANCE_UID); updateReferencedRelationships_ = SerializationToolbox::ReadBoolean (serialized, UPDATE_REFERENCED_RELATIONSHIPS); isAnonymization_ = SerializationToolbox::ReadBoolean(serialized, IS_ANONYMIZATION); SerializationToolbox::ReadSetOfTags(removals_, serialized, REMOVALS); SerializationToolbox::ReadSetOfTags(clearings_, serialized, CLEARINGS); SerializationToolbox::ReadSetOfTags(privateTagsToKeep_, serialized, PRIVATE_TAGS_TO_KEEP); if (!serialized.isMember(REPLACEMENTS) || serialized[REPLACEMENTS].type() != Json::objectValue) { throw OrthancException(ErrorCode_BadFileFormat); } Json::Value::Members names = serialized[REPLACEMENTS].getMemberNames(); for (Json::Value::Members::const_iterator it = names.begin(); it != names.end(); ++it) { DicomTag tag(0, 0); if (!DicomTag::ParseHexadecimal(tag, it->c_str())) { throw OrthancException(ErrorCode_BadFileFormat); } else { const Json::Value& value = serialized[REPLACEMENTS][*it]; replacements_.insert(std::make_pair(tag, new Json::Value(value))); } } UnserializeUidMap(ResourceType_Patient, serialized, MAP_PATIENTS); UnserializeUidMap(ResourceType_Study, serialized, MAP_STUDIES); UnserializeUidMap(ResourceType_Series, serialized, MAP_SERIES); UnserializeUidMap(ResourceType_Instance, serialized, MAP_INSTANCES); } }