changeset 787:ac18946afa74

refactoring of anonymization/modification
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 05 May 2014 15:52:14 +0200
parents b6d6b65142e8
children 7ebe4bf87196
files OrthancServer/DicomModification.cpp OrthancServer/DicomModification.h OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp
diffstat 3 files changed, 142 insertions(+), 405 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/DicomModification.cpp	Mon May 05 13:07:10 2014 +0200
+++ b/OrthancServer/DicomModification.cpp	Mon May 05 15:52:14 2014 +0200
@@ -87,7 +87,7 @@
     level_ = DicomRootLevel_Instance;
   }
 
-  void DicomModification::Reset(const DicomTag& tag)
+  void DicomModification::Keep(const DicomTag& tag)
   {
     removals_.erase(tag);
     replacements_.erase(tag);
--- a/OrthancServer/DicomModification.h	Mon May 05 13:07:10 2014 +0200
+++ b/OrthancServer/DicomModification.h	Mon May 05 15:52:14 2014 +0200
@@ -62,7 +62,7 @@
   public:
     DicomModification();
 
-    void Reset(const DicomTag& tag);
+    void Keep(const DicomTag& tag);
 
     void Remove(const DicomTag& tag);
 
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Mon May 05 13:07:10 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Mon May 05 15:52:14 2014 +0200
@@ -32,67 +32,55 @@
 
 #include "OrthancRestApi.h"
 
+#include "../DicomModification.h"
+
 #include <glog/logging.h>
 
 namespace Orthanc
 {
   // Modification of DICOM instances ------------------------------------------
 
-  namespace
+  enum TagOperation
   {
-    typedef std::set<DicomTag> Removals;
-    typedef std::map<DicomTag, std::string> Replacements;
-    typedef std::map< std::pair<DicomRootLevel, std::string>, std::string>  UidMap;
-  }
-
-  static void ReplaceInstanceInternal(ParsedDicomFile& toModify,
-                                      const Removals& removals,
-                                      const Replacements& replacements,
-                                      bool removePrivateTags)
-  {
-    if (removePrivateTags)
-    {
-      toModify.RemovePrivateTags();
-    }
+    TagOperation_Keep,
+    TagOperation_Remove
+  };
 
-    for (Removals::const_iterator it = removals.begin(); 
-         it != removals.end(); ++it)
-    {
-      toModify.Remove(*it);
-    }
-
-    for (Replacements::const_iterator it = replacements.begin(); 
-         it != replacements.end(); ++it)
-    {
-      toModify.Replace(it->first, it->second, DicomReplaceMode_InsertIfAbsent);
-    }
-
-    // A new SOP instance UID is automatically generated
-    std::string instanceUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance);
-    toModify.Replace(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, DicomReplaceMode_InsertIfAbsent);
-  }
-
-
-  static void ParseRemovals(Removals& target,
-                            const Json::Value& removals)
+  static void ParseListOfTags(DicomModification& target,
+                              const Json::Value& query,
+                              TagOperation operation)
   {
-    if (!removals.isArray())
+    if (!query.isArray())
     {
       throw OrthancException(ErrorCode_BadRequest);
     }
 
-    for (Json::Value::ArrayIndex i = 0; i < removals.size(); i++)
+    for (Json::Value::ArrayIndex i = 0; i < query.size(); i++)
     {
-      std::string name = removals[i].asString();
+      std::string name = query[i].asString();
+
       DicomTag tag = FromDcmtkBridge::ParseTag(name);
-      target.insert(tag);
+
+      switch (operation)
+      {
+        case TagOperation_Keep:
+          target.Keep(tag);
+          VLOG(1) << "Keep: " << name << " " << tag << std::endl;
+          break;
 
-      VLOG(1) << "Removal: " << name << " " << tag << std::endl;
+        case TagOperation_Remove:
+          target.Remove(tag);
+          VLOG(1) << "Remove: " << name << " " << tag << std::endl;
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
     }
   }
 
 
-  static void ParseReplacements(Replacements& target,
+  static void ParseReplacements(DicomModification& target,
                                 const Json::Value& replacements)
   {
     if (!replacements.isObject())
@@ -107,9 +95,9 @@
       std::string value = replacements[name].asString();
 
       DicomTag tag = FromDcmtkBridge::ParseTag(name);      
-      target[tag] = value;
+      target.Replace(tag, value);
 
-      VLOG(1) << "Replacement: " << name << " " << tag << " == " << value << std::endl;
+      VLOG(1) << "Replace: " << name << " " << tag << " == " << value << std::endl;
     }
   }
 
@@ -121,186 +109,27 @@
   }
 
 
-  static void SetupAnonymization(Removals& removals,
-                                 Replacements& replacements)
-  {
-    // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles
-    removals.insert(DicomTag(0x0008, 0x0014));  // Instance Creator UID
-    //removals.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set by ReplaceInstanceInternal()
-    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 
-    //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 => cf. below (*)
-    //removals.insert(DicomTag(0x0020, 0x000e));  // Series Instance UID => cf. below (*)
-    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 
-
-    /**
-     *   (*) Patient ID, Study Instance UID and Series Instance UID
-     * are modified by "AnonymizeInstance()" if anonymizing a single
-     * instance, or by "RetrieveMappedUid()" if anonymizing a
-     * patient/study/series.
-     **/
-
-
-    // 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
-    replacements.insert(std::make_pair(DicomTag(0x0012, 0x0063), "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1"));
-
-    // Set the PatientIdentityRemoved tag
-    replacements.insert(std::make_pair(DicomTag(0x0012, 0x0062), "YES"));
-  }
-
-
-  static bool ParseModifyRequest(Removals& removals,
-                                 Replacements& replacements,
-                                 bool& removePrivateTags,
+  static bool ParseModifyRequest(DicomModification& target,
                                  const RestApi::PostCall& call)
   {
-    removePrivateTags = false;
+    // curl http://localhost:8042/series/95a6e2bf-9296e2cc-bf614e2f-22b391ee-16e010e0/modify -X POST -d '{"Replace":{"InstitutionName":"My own clinic"}}'
+
     Json::Value request;
-    if (call.ParseJsonRequest(request) &&
-        request.isObject())
+    if (call.ParseJsonRequest(request) && request.isObject())
     {
-      Json::Value removalsPart = Json::arrayValue;
-      Json::Value replacementsPart = Json::objectValue;
+      if (request.isMember("RemovePrivateTags"))
+      {
+        target.SetRemovePrivateTags(true);
+      }
 
       if (request.isMember("Remove"))
       {
-        removalsPart = request["Remove"];
+        ParseListOfTags(target, request["Remove"], TagOperation_Remove);
       }
 
       if (request.isMember("Replace"))
       {
-        replacementsPart = request["Replace"];
-      }
-
-      if (request.isMember("RemovePrivateTags"))
-      {
-        removePrivateTags = true;
-      }
-      
-      ParseRemovals(removals, removalsPart);
-      ParseReplacements(replacements, replacementsPart);
-
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  static bool ParseAnonymizationRequest(Removals& removals,
-                                        Replacements& replacements,
-                                        bool& removePrivateTags,
-                                        bool& keepPatientId,
-                                        RestApi::PostCall& call)
-  {
-    ServerContext& context = OrthancRestApi::GetContext(call);
-
-    removePrivateTags = true;
-    keepPatientId = false;
-
-    Json::Value request;
-    if (call.ParseJsonRequest(request) &&
-        request.isObject())
-    {
-      Json::Value keepPart = Json::arrayValue;
-      Json::Value removalsPart = Json::arrayValue;
-      Json::Value replacementsPart = Json::objectValue;
-
-      if (request.isMember("Keep"))
-      {
-        keepPart = request["Keep"];
-      }
-
-      if (request.isMember("KeepPrivateTags"))
-      {
-        removePrivateTags = false;
-      }
-
-      if (request.isMember("Replace"))
-      {
-        replacementsPart = request["Replace"];
-      }
-
-      Removals toKeep;
-      ParseRemovals(toKeep, keepPart);
-
-      SetupAnonymization(removals, replacements);
-
-      for (Removals::iterator it = toKeep.begin(); it != toKeep.end(); ++it)
-      {
-        if (*it == DICOM_TAG_PATIENT_ID)
-        {
-          keepPatientId = true;
-        }
-
-        removals.erase(*it);
-      }
-
-      Removals additionalRemovals;
-      ParseRemovals(additionalRemovals, removalsPart);
-
-      for (Removals::iterator it = additionalRemovals.begin(); 
-           it != additionalRemovals.end(); ++it)
-      {
-        removals.insert(*it);
-      }     
-
-      ParseReplacements(replacements, replacementsPart);
-
-      // Generate random Patient's Name if none is specified
-      if (toKeep.find(DICOM_TAG_PATIENT_NAME) == toKeep.end() &&
-          replacements.find(DICOM_TAG_PATIENT_NAME) == replacements.end())
-      {
-        replacements.insert(std::make_pair(DICOM_TAG_PATIENT_NAME, GeneratePatientName(context)));
+        ParseReplacements(target, request["Replace"]);
       }
 
       return true;
@@ -312,9 +141,54 @@
   }
 
 
-  static void AnonymizeOrModifyInstance(Removals& removals,
-                                        Replacements& replacements,
-                                        bool removePrivateTags,
+  static bool ParseAnonymizationRequest(DicomModification& target,
+                                        RestApi::PostCall& call)
+  {
+    // 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.GetReplacement(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.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)));
+      }
+
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  static void AnonymizeOrModifyInstance(DicomModification& modification,
                                         RestApi::PostCall& call)
   {
     std::string id = call.GetUriComponent("id", "");
@@ -322,79 +196,23 @@
     ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), id);
 
     std::auto_ptr<ParsedDicomFile> modified(locker.GetDicom().Clone());
-    ReplaceInstanceInternal(*modified, removals, replacements, removePrivateTags);
+    modification.Apply(*modified);
     modified->Answer(call.GetOutput());
   }
 
 
-  static bool RetrieveMappedUid(ParsedDicomFile& dicom,
-                                DicomRootLevel level,
-                                Replacements& replacements,
-                                UidMap& uidMap)
-  {
-    std::auto_ptr<DicomTag> tag;
-
-    switch (level)
-    {
-      case DicomRootLevel_Series:
-        tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID));
-        break;
-
-      case DicomRootLevel_Study:
-        tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID));
-        break;
-
-      case DicomRootLevel_Patient:
-        tag.reset(new DicomTag(DICOM_TAG_PATIENT_ID));
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-
-    std::string original;
-    if (!dicom.GetTagValue(original, *tag))
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-    std::string mapped;
-    bool isNew;
-
-    UidMap::const_iterator previous = uidMap.find(std::make_pair(level, original));
-    if (previous == uidMap.end())
-    {
-      mapped = FromDcmtkBridge::GenerateUniqueIdentifier(level);
-      uidMap.insert(std::make_pair(std::make_pair(level, original), mapped));
-      isNew = true;
-    }
-    else
-    {
-      mapped = previous->second;
-      isNew = false;
-    }    
-
-    replacements[*tag] = mapped;
-    return isNew;
-  }
-
-
-  static void AnonymizeOrModifyResource(Removals& removals,
-                                        Replacements& replacements,
-                                        bool removePrivateTags,
-                                        bool keepPatientId,
+  static void AnonymizeOrModifyResource(DicomModification& modification,
                                         MetadataType metadataType,
                                         ChangeType changeType,
                                         ResourceType resourceType,
                                         RestApi::PostCall& call)
   {
-    typedef std::list<std::string> Instances;
-
     bool isFirst = true;
     Json::Value result(Json::objectValue);
 
     ServerContext& context = OrthancRestApi::GetContext(call);
 
+    typedef std::list<std::string> Instances;
     Instances instances;
     std::string id = call.GetUriComponent("id", "");
     context.GetIndex().GetChildInstances(instances, id);
@@ -404,11 +222,11 @@
       return;
     }
 
+
     /**
      * Loop over all the instances of the resource.
      **/
 
-    UidMap uidMap;
     for (Instances::const_iterator it = instances.begin(); 
          it != instances.end(); ++it)
     {
@@ -427,26 +245,15 @@
       }
 
       ParsedDicomFile& original = locker->GetDicom();
-
       DicomInstanceHasher originalHasher = original.GetHasher();
 
-      if (isFirst && keepPatientId)
-      {
-        std::string patientId = originalHasher.GetPatientId();
-        uidMap[std::make_pair(DicomRootLevel_Patient, patientId)] = patientId;
-      }
-
-      bool isNewSeries = RetrieveMappedUid(original, DicomRootLevel_Series, replacements, uidMap);
-      bool isNewStudy = RetrieveMappedUid(original, DicomRootLevel_Study, replacements, uidMap);
-      bool isNewPatient = RetrieveMappedUid(original, DicomRootLevel_Patient, replacements, uidMap);
-
 
       /**
        * Compute the resulting DICOM instance and store it into the Orthanc store.
        **/
 
       std::auto_ptr<ParsedDicomFile> modified(original.Clone());
-      ReplaceInstanceInternal(*modified, removals, replacements, removePrivateTags);
+      modification.Apply(*modified);
 
       std::string modifiedInstance;
       if (context.Store(modifiedInstance, modified->GetDicom()) != StoreStatus_Success)
@@ -462,19 +269,19 @@
 
       DicomInstanceHasher modifiedHasher = modified->GetHasher();
 
-      if (isNewSeries)
+      if (originalHasher.HashSeries() != modifiedHasher.HashSeries())
       {
         context.GetIndex().SetMetadata(modifiedHasher.HashSeries(), 
                                        metadataType, originalHasher.HashSeries());
       }
 
-      if (isNewStudy)
+      if (originalHasher.HashStudy() != modifiedHasher.HashStudy())
       {
         context.GetIndex().SetMetadata(modifiedHasher.HashStudy(), 
                                        metadataType, originalHasher.HashStudy());
       }
 
-      if (isNewPatient)
+      if (originalHasher.HashPatient() != modifiedHasher.HashPatient())
       {
         context.GetIndex().SetMetadata(modifiedHasher.HashPatient(), 
                                        metadataType, originalHasher.HashPatient());
@@ -526,154 +333,84 @@
 
   static void ModifyInstance(RestApi::PostCall& call)
   {
-    Removals removals;
-    Replacements replacements;
-    bool removePrivateTags;
+    DicomModification modification;
+
+    // TODO : modification.SetLevel(DicomRootLevel_Series); ?????
 
-    if (ParseModifyRequest(removals, replacements, removePrivateTags, call))
+    if (ParseModifyRequest(modification, call))
     {
-      AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call);
+      AnonymizeOrModifyInstance(modification, call);
     }
   }
 
 
   static void AnonymizeInstance(RestApi::PostCall& call)
   {
-    Removals removals;
-    Replacements replacements;
-    bool removePrivateTags, keepPatientId;
-
-    if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call))
-    {
-      // TODO Handle "keepPatientId"
-
-      // Generate random patient ID if not specified
-      if (replacements.find(DICOM_TAG_PATIENT_ID) == replacements.end())
-      {
-        replacements.insert(std::make_pair(DICOM_TAG_PATIENT_ID, 
-                                           FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient)));
-      }
-
-      // Generate random study UID if not specified
-      if (replacements.find(DICOM_TAG_STUDY_INSTANCE_UID) == replacements.end())
-      {
-        replacements.insert(std::make_pair(DICOM_TAG_STUDY_INSTANCE_UID, 
-                                           FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study)));
-      }
+    DicomModification modification;
 
-      // Generate random series UID if not specified
-      if (replacements.find(DICOM_TAG_SERIES_INSTANCE_UID) == replacements.end())
-      {
-        replacements.insert(std::make_pair(DICOM_TAG_SERIES_INSTANCE_UID, 
-                                           FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series)));
-      }
-
-      AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call);
-    }
-  }
-
-
-  static void ModifySeriesInplace(RestApi::PostCall& call)
-  {
-    Removals removals;
-    Replacements replacements;
-    bool removePrivateTags;
-
-    if (ParseModifyRequest(removals, replacements, removePrivateTags, call))
+    if (ParseAnonymizationRequest(modification, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, true /*keepPatientId*/,
-                                MetadataType_ModifiedFrom, ChangeType_ModifiedSeries, 
-                                ResourceType_Series, call);
+      AnonymizeOrModifyInstance(modification, call);
     }
   }
 
 
-  static void AnonymizeSeriesInplace(RestApi::PostCall& call)
+  template <enum ChangeType changeType,
+            enum ResourceType resourceType>
+  static void ModifyResource(RestApi::PostCall& call)
   {
-    Removals removals;
-    Replacements replacements;
-    bool removePrivateTags, keepPatientId;
+    DicomModification modification;
 
-    if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call))
+    switch (resourceType)
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId,
-                                MetadataType_AnonymizedFrom, ChangeType_AnonymizedSeries, 
-                                ResourceType_Series, call);
+      case ResourceType_Series:
+        modification.SetLevel(DicomRootLevel_Series);
+        break;
+
+      case ResourceType_Study:
+        modification.SetLevel(DicomRootLevel_Study);
+        break;
+
+      case ResourceType_Patient:
+        modification.SetLevel(DicomRootLevel_Patient);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
     }
-  }
 
-
-  static void ModifyStudyInplace(RestApi::PostCall& call)
-  {
-    Removals removals;
-    Replacements replacements;
-    bool removePrivateTags;
-
-    if (ParseModifyRequest(removals, replacements, removePrivateTags, call))
+    if (ParseModifyRequest(modification, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, true /*keepPatientId*/,
-                                MetadataType_ModifiedFrom, ChangeType_ModifiedStudy, 
-                                ResourceType_Study, call);
+      AnonymizeOrModifyResource(modification, MetadataType_ModifiedFrom, 
+                                changeType, resourceType, call);
     }
   }
 
 
-  static void AnonymizeStudyInplace(RestApi::PostCall& call)
+  template <enum ChangeType changeType,
+            enum ResourceType resourceType>
+  static void AnonymizeResource(RestApi::PostCall& call)
   {
-    Removals removals;
-    Replacements replacements;
-    bool removePrivateTags, keepPatientId;
+    DicomModification modification;
 
-    if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call))
+    if (ParseAnonymizationRequest(modification, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId,
-                                MetadataType_AnonymizedFrom, ChangeType_AnonymizedStudy, 
-                                ResourceType_Study, call);
+      AnonymizeOrModifyResource(modification, MetadataType_AnonymizedFrom, 
+                                changeType, resourceType, call);
     }
   }
 
 
-  /*static void ModifyPatientInplace(RestApi::PostCall& call)
-  {
-    Removals removals;
-    Replacements replacements;
-    bool removePrivateTags;
-
-    if (ParseModifyRequest(removals, replacements, removePrivateTags, call))
-    {
-      AnonymizeOrModifyResource(false, removals, replacements, removePrivateTags, 
-                                MetadataType_ModifiedFrom, ChangeType_ModifiedPatient, 
-                                ResourceType_Patient, call);
-    }
-    }*/
-
-
-  static void AnonymizePatientInplace(RestApi::PostCall& call)
-  {
-    Removals removals;
-    Replacements replacements;
-    bool removePrivateTags, keepPatientId;
-
-    if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call))
-    {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId,
-                                MetadataType_AnonymizedFrom, ChangeType_AnonymizedPatient, 
-                                ResourceType_Patient, call);
-    }
-  }
-
-
-
   void OrthancRestApi::RegisterAnonymizeModify()
   {
     Register("/instances/{id}/modify", ModifyInstance);
-    Register("/series/{id}/modify", ModifySeriesInplace);
-    Register("/studies/{id}/modify", ModifyStudyInplace);
-    //Register("/patients/{id}/modify", ModifyPatientInplace);
+    Register("/series/{id}/modify", ModifyResource<ChangeType_ModifiedSeries, ResourceType_Series>);
+    Register("/studies/{id}/modify", ModifyResource<ChangeType_ModifiedStudy, ResourceType_Study>);
+    //Register("/patients/{id}/modify", ModifyResource<ChangeType_ModifiedPatient, ResourceType_Patient>);
 
     Register("/instances/{id}/anonymize", AnonymizeInstance);
-    Register("/series/{id}/anonymize", AnonymizeSeriesInplace);
-    Register("/studies/{id}/anonymize", AnonymizeStudyInplace);
-    Register("/patients/{id}/anonymize", AnonymizePatientInplace);
+    Register("/series/{id}/anonymize", AnonymizeResource<ChangeType_ModifiedSeries, ResourceType_Series>);
+    Register("/studies/{id}/anonymize", AnonymizeResource<ChangeType_ModifiedStudy, ResourceType_Study>);
+    Register("/patients/{id}/anonymize", AnonymizeResource<ChangeType_ModifiedPatient, ResourceType_Patient>);
   }
 }