changeset 2309:4dc313b9a20a issue-46-anonymization

Argument "DicomVersion" in URIs "/{...}/{...}/anonymization"
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 12 Jul 2017 13:40:02 +0200
parents 1bdc4cc68171
children b7fba68747f6
files NEWS OrthancServer/DicomModification.cpp OrthancServer/DicomModification.h OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp OrthancServer/ServerEnumerations.cpp OrthancServer/ServerEnumerations.h UnitTestsSources/FromDcmtkTests.cpp UnitTestsSources/UnitTestsMain.cpp
diffstat 8 files changed, 232 insertions(+), 50 deletions(-) [+]
line wrap: on
line diff
--- 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
 -------
--- 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 <memory>   // 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");
--- 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);
 
--- 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;
   }
 
 
--- 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 &&
--- 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);
 }
--- 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));
 }
--- 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)));
 }