# HG changeset patch # User Sebastien Jodogne # Date 1499859602 -7200 # Node ID 4dc313b9a20ad9fc77279de01ead8bb8afadaf64 # Parent 1bdc4cc68171ffa97a54cc9fb7442eaf16c9f0af Argument "DicomVersion" in URIs "/{...}/{...}/anonymization" diff -r 1bdc4cc68171 -r 4dc313b9a20a NEWS --- a/NEWS Wed Jul 12 09:12:12 2017 +0200 +++ b/NEWS Wed Jul 12 13:40:02 2017 +0200 @@ -5,6 +5,7 @@ -------- * Argument "Since" in URI "/tools/find" (related to issue 53) +* Argument "DicomVersion" in URIs "/{...}/{...}/anonymization" Plugins ------- diff -r 1bdc4cc68171 -r 4dc313b9a20a OrthancServer/DicomModification.cpp --- a/OrthancServer/DicomModification.cpp Wed Jul 12 09:12:12 2017 +0200 +++ b/OrthancServer/DicomModification.cpp Wed Jul 12 13:40:02 2017 +0200 @@ -41,7 +41,14 @@ #include // For std::auto_ptr -static const std::string ORTHANC_DEIDENTIFICATION_METHOD = "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1"; +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_2011 = + "Orthanc " ORTHANC_VERSION " - PS 3.15-2011 Table E.1-1"; + +static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2017c = + "Orthanc " ORTHANC_VERSION " - PS 3.15-2017c Table E.1-1"; namespace Orthanc { @@ -92,7 +99,9 @@ Replacements::iterator it = replacements_.find(DICOM_TAG_DEIDENTIFICATION_METHOD); if (it != replacements_.end() && - it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD) + (it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2008 || + it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2011 || + it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2017c)) { delete it->second; replacements_.erase(it); @@ -267,16 +276,77 @@ } } - void DicomModification::SetupAnonymization() + + void DicomModification::SetupAnonymization2008() { - removals_.clear(); - ClearReplacements(); - removePrivateTags_ = true; - level_ = ResourceType_Patient; - uidMap_.clear(); - privateTagsToKeep_.clear(); + // 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 + 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 + 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 + removals_.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID + // 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::SetupAnonymization2011() + { // This is Table E.1-1 from PS 3.15-2011 - DICOM Part 15: Security and System Management Profiles + // https://raw.githubusercontent.com/jodogne/dicom-specification/master/2011/11_15pu.pdf + removals_.insert(DicomTag(0x0000, 0x1000)); // Affected SOP Instance UID removals_.insert(DicomTag(0x0000, 0x1001)); // Requested SOP Instance UID removals_.insert(DicomTag(0x0002, 0x0003)); // Media Storage SOP Instance UID => TODO: replace with a non-zero length UID that is internally consistent within a set of Instances @@ -523,7 +593,42 @@ //removals_.insert(DicomTag(0x60xx, 0x3000)); // Overlay Data => TODO // Set the DeidentificationMethod tag - ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD); + ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2011); + } + + + void DicomModification::SetupAnonymization2017c() + { + throw OrthancException(ErrorCode_NotImplemented); + } + + + void DicomModification::SetupAnonymization(DicomVersion version) + { + removals_.clear(); + ClearReplacements(); + removePrivateTags_ = true; + level_ = ResourceType_Patient; + uidMap_.clear(); + privateTagsToKeep_.clear(); + + switch (version) + { + case DicomVersion_2008: + SetupAnonymization2008(); + break; + + case DicomVersion_2011: + SetupAnonymization2011(); + break; + + case DicomVersion_2017c: + SetupAnonymization2017c(); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } // Set the PatientIdentityRemoved tag ReplaceInternal(DicomTag(0x0012, 0x0062), "YES"); diff -r 1bdc4cc68171 -r 4dc313b9a20a OrthancServer/DicomModification.h --- a/OrthancServer/DicomModification.h Wed Jul 12 09:12:12 2017 +0200 +++ b/OrthancServer/DicomModification.h Wed Jul 12 13:40:02 2017 +0200 @@ -73,6 +73,12 @@ void ReplaceInternal(const DicomTag& tag, const Json::Value& value); + void SetupAnonymization2008(); + + void SetupAnonymization2011(); + + void SetupAnonymization2017c(); + public: DicomModification(); @@ -108,7 +114,7 @@ return level_; } - void SetupAnonymization(); + void SetupAnonymization(DicomVersion version); void Apply(ParsedDicomFile& toModify); diff -r 1bdc4cc68171 -r 4dc313b9a20a OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Wed Jul 12 09:12:12 2017 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Wed Jul 12 13:40:02 2017 +0200 @@ -178,46 +178,58 @@ { // curl http://localhost:8042/instances/6e67da51-d119d6ae-c5667437-87b9a8a5-0f07c49f/anonymize -X POST -d '{"Replace":{"PatientName":"hello","0010-0020":"world"},"Keep":["StudyDescription", "SeriesDescription"],"KeepPrivateTags": null,"Remove":["Modality"]}' > Anonymized.dcm - target.SetupAnonymization(); - std::string patientName = target.GetReplacementAsString(DICOM_TAG_PATIENT_NAME); - Json::Value request; - if (call.ParseJsonRequest(request) && request.isObject()) - { - if (request.isMember("KeepPrivateTags")) - { - target.SetRemovePrivateTags(false); - } - - if (request.isMember("Remove")) - { - ParseListOfTags(target, request["Remove"], TagOperation_Remove); - } - - if (request.isMember("Replace")) - { - ParseReplacements(target, request["Replace"]); - } - - if (request.isMember("Keep")) - { - ParseListOfTags(target, request["Keep"], TagOperation_Keep); - } - - if (target.IsReplaced(DICOM_TAG_PATIENT_NAME) && - target.GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName) - { - // Overwrite the random Patient's Name by one that is more - // user-friendly (provided none was specified by the user) - target.Replace(DICOM_TAG_PATIENT_NAME, GeneratePatientName(OrthancRestApi::GetContext(call)), true); - } - - return true; - } - else + if (!call.ParseJsonRequest(request) || + !request.isObject()) { return false; } + + DicomVersion version = DicomVersion_2008; // TODO Switch to 2017c + if (request.isMember("DicomVersion")) + { + if (request["DicomVersion"].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + version = StringToDicomVersion(request["DicomVersion"].asString()); + } + } + + target.SetupAnonymization(version); + std::string patientName = target.GetReplacementAsString(DICOM_TAG_PATIENT_NAME); + + if (request.isMember("KeepPrivateTags")) + { + target.SetRemovePrivateTags(false); + } + + if (request.isMember("Remove")) + { + ParseListOfTags(target, request["Remove"], TagOperation_Remove); + } + + if (request.isMember("Replace")) + { + ParseReplacements(target, request["Replace"]); + } + + if (request.isMember("Keep")) + { + ParseListOfTags(target, request["Keep"], TagOperation_Keep); + } + + if (target.IsReplaced(DICOM_TAG_PATIENT_NAME) && + target.GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName) + { + // Overwrite the random Patient's Name by one that is more + // user-friendly (provided none was specified by the user) + target.Replace(DICOM_TAG_PATIENT_NAME, GeneratePatientName(OrthancRestApi::GetContext(call)), true); + } + + return true; } diff -r 1bdc4cc68171 -r 4dc313b9a20a OrthancServer/ServerEnumerations.cpp --- a/OrthancServer/ServerEnumerations.cpp Wed Jul 12 09:12:12 2017 +0200 +++ b/OrthancServer/ServerEnumerations.cpp Wed Jul 12 13:40:02 2017 +0200 @@ -462,6 +462,49 @@ } + const char* EnumerationToString(DicomVersion version) + { + switch (version) + { + case DicomVersion_2008: + return "2008"; + break; + + case DicomVersion_2011: + return "2011"; + break; + + case DicomVersion_2017c: + return "2017c"; + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + DicomVersion StringToDicomVersion(const std::string& version) + { + if (version == "2008") + { + return DicomVersion_2008; + } + else if (version == "2011") + { + return DicomVersion_2011; + } + else if (version == "2017c") + { + return DicomVersion_2017c; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + bool IsUserMetadata(MetadataType metadata) { return (metadata >= MetadataType_StartUser && diff -r 1bdc4cc68171 -r 4dc313b9a20a OrthancServer/ServerEnumerations.h --- a/OrthancServer/ServerEnumerations.h Wed Jul 12 09:12:12 2017 +0200 +++ b/OrthancServer/ServerEnumerations.h Wed Jul 12 13:40:02 2017 +0200 @@ -135,6 +135,13 @@ IdentifierConstraintType_Wildcard /* Case sensitive, "*" or "?" are the only allowed wildcards */ }; + enum DicomVersion + { + DicomVersion_2008, + DicomVersion_2011, + DicomVersion_2017c + }; + /** * WARNING: Do not change the explicit values in the enumerations @@ -230,7 +237,11 @@ const char* EnumerationToString(TransferSyntax syntax); + const char* EnumerationToString(DicomVersion version); + ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer); + DicomVersion StringToDicomVersion(const std::string& version); + bool IsUserMetadata(MetadataType type); } diff -r 1bdc4cc68171 -r 4dc313b9a20a UnitTestsSources/FromDcmtkTests.cpp --- a/UnitTestsSources/FromDcmtkTests.cpp Wed Jul 12 09:12:12 2017 +0200 +++ b/UnitTestsSources/FromDcmtkTests.cpp Wed Jul 12 13:40:02 2017 +0200 @@ -77,7 +77,7 @@ TEST(DicomModification, Basic) { DicomModification m; - m.SetupAnonymization(); + m.SetupAnonymization(DicomVersion_2008); //m.SetLevel(DicomRootLevel_Study); //m.ReplacePlainString(DICOM_TAG_PATIENT_ID, "coucou"); //m.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "coucou"); @@ -133,7 +133,7 @@ ASSERT_FALSE(Toolbox::IsUuid(s)); DicomModification m; - m.SetupAnonymization(); + m.SetupAnonymization(DicomVersion_2008); m.Keep(privateTag); m.Apply(o); @@ -143,7 +143,7 @@ ASSERT_TRUE(o.GetTagValue(s, privateTag)); ASSERT_STREQ("private tag", s.c_str()); - m.SetupAnonymization(); + m.SetupAnonymization(DicomVersion_2008); m.Apply(o); ASSERT_FALSE(o.GetTagValue(s, privateTag)); } diff -r 1bdc4cc68171 -r 4dc313b9a20a UnitTestsSources/UnitTestsMain.cpp --- a/UnitTestsSources/UnitTestsMain.cpp Wed Jul 12 09:12:12 2017 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Wed Jul 12 13:40:02 2017 +0200 @@ -648,6 +648,10 @@ ASSERT_STREQ("Unknown", EnumerationToString(PhotometricInterpretation_Unknown)); ASSERT_THROW(StringToPhotometricInterpretation("Unknown"), OrthancException); + + ASSERT_EQ(DicomVersion_2008, StringToDicomVersion(EnumerationToString(DicomVersion_2008))); + ASSERT_EQ(DicomVersion_2011, StringToDicomVersion(EnumerationToString(DicomVersion_2011))); + ASSERT_EQ(DicomVersion_2017c, StringToDicomVersion(EnumerationToString(DicomVersion_2017c))); }