changeset 515:a8be42bcf2bb

Link from modified to original resource in Orthanc Explorer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 19 Aug 2013 13:40:36 +0200
parents 935e8c7e0b18
children b2b56b4e33b0
files NEWS OrthancExplorer/explorer.html OrthancExplorer/explorer.js OrthancServer/OrthancRestApi.cpp
diffstat 4 files changed, 105 insertions(+), 62 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Fri Aug 16 17:11:45 2013 +0200
+++ b/NEWS	Mon Aug 19 13:40:36 2013 +0200
@@ -3,6 +3,7 @@
 
 * Detection of stable patients/studies/series
 * C-Find SCU at the instance level
+* Link from modified to original resource in Orthanc Explorer
 
 
 Version 0.6.0 (2013/07/16)
--- a/OrthancExplorer/explorer.html	Fri Aug 16 17:11:45 2013 +0200
+++ b/OrthancExplorer/explorer.html	Mon Aug 19 13:40:36 2013 +0200
@@ -101,6 +101,9 @@
                 <li data-icon="info" data-theme="e" style="display:none">
                   <a href="#" id="patient-anonymized-from">Before anonymization</a>
                 </li>
+                <li data-icon="info" data-theme="e" style="display:none">
+                  <a href="#" id="patient-modified-from">Before modification</a>
+                </li>
                 <li data-icon="gear"><a href="#" id="patient-archive">Download ZIP</a></li>
               </ul>
             </div>
@@ -144,6 +147,9 @@
                 <li data-icon="info" data-theme="e" style="display:none">
                   <a href="#" id="study-anonymized-from">Before anonymization</a>
                 </li>
+                <li data-icon="info" data-theme="e" style="display:none">
+                  <a href="#" id="study-modified-from">Before modification</a>
+                </li>
                 <li data-icon="gear"><a href="#" id="study-archive">Download ZIP</a></li>
               </ul>
             </div>
@@ -189,6 +195,9 @@
                 <li data-icon="info" data-theme="e" style="display:none">
                   <a href="#" id="series-anonymized-from">Before anonymization</a>
                 </li>
+                <li data-icon="info" data-theme="e" style="display:none">
+                  <a href="#" id="series-modified-from">Before modification</a>
+                </li>
                 <li data-icon="search"><a href="#" id="series-preview">Preview this series</a></li>
                 <li data-icon="gear"><a href="#" id="series-archive">Download ZIP</a></li>
               </ul>
@@ -231,6 +240,12 @@
 
               <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c">
                 <li data-role="list-divider">Access</li>
+                <li data-icon="info" data-theme="e" style="display:none">
+                  <a href="#" id="instance-anonymized-from">Before anonymization</a>
+                </li>
+                <li data-icon="info" data-theme="e" style="display:none">
+                  <a href="#" id="instance-modified-from">Before modification</a>
+                </li>
                 <li data-icon="arrow-d"><a href="#" id="instance-download-dicom">Download the DICOM file</a></li>
                 <li data-icon="arrow-d"><a href="#" id="instance-download-json">Download the JSON file</a></li>
                 <li data-icon="search"><a href="#" id="instance-preview">Preview the instance</a></li>
--- a/OrthancExplorer/explorer.js	Fri Aug 16 17:11:45 2013 +0200
+++ b/OrthancExplorer/explorer.js	Mon Aug 19 13:40:36 2013 +0200
@@ -361,14 +361,13 @@
 
 
 
-function SetupAnonymizedFrom(buttonSelector, resource, resourceType)
+function SetupAnonymizedOrModifiedFrom(buttonSelector, resource, resourceType, field)
 {
-  if ('AnonymizedFrom' in resource)
+  if (field in resource)
   {
     $(buttonSelector).closest('li').show();
     $(buttonSelector).click(function(e) {
-      window.location.assign('explorer.html#' + resourceType + '?uuid=' + resource.AnonymizedFrom);
-      //window.location.reload();
+      window.location.assign('explorer.html#' + resourceType + '?uuid=' + resource[field]);
     });
   }
   else
@@ -378,6 +377,7 @@
 }
 
 
+
 function RefreshPatient()
 {
   if ($.mobile.pageData) {
@@ -404,7 +404,8 @@
           target.append(FormatStudy(studies[i], '#study?uuid=' + studies[i].ID));
         }
 
-        SetupAnonymizedFrom('#patient-anonymized-from', patient, 'patient');
+        SetupAnonymizedOrModifiedFrom('#patient-anonymized-from', patient, 'patient', 'AnonymizedFrom');
+        SetupAnonymizedOrModifiedFrom('#patient-modified-from', patient, 'patient', 'ModifiedFrom');
 
         target.listview('refresh');
 
@@ -446,7 +447,8 @@
             .append(FormatStudy(study))
             .listview('refresh');
 
-          SetupAnonymizedFrom('#study-anonymized-from', study, 'study');
+          SetupAnonymizedOrModifiedFrom('#study-anonymized-from', study, 'study', 'AnonymizedFrom');
+          SetupAnonymizedOrModifiedFrom('#study-modified-from', study, 'study', 'ModifiedFrom');
 
           var target = $('#list-series');
           $('li', target).remove();
@@ -491,7 +493,8 @@
               .append(FormatSeries(series))
               .listview('refresh');
 
-            SetupAnonymizedFrom('#series-anonymized-from', series, 'series');
+            SetupAnonymizedOrModifiedFrom('#series-anonymized-from', series, 'series', 'AnonymizedFrom');
+            SetupAnonymizedOrModifiedFrom('#series-modified-from', series, 'series', 'ModifiedFrom');
 
             var target = $('#list-instances');
             $('li', target).remove();
@@ -595,6 +598,9 @@
               }
             });
 
+            SetupAnonymizedOrModifiedFrom('#instance-anonymized-from', instance, 'instance', 'AnonymizedFrom');
+            SetupAnonymizedOrModifiedFrom('#instance-modified-from', instance, 'instance', 'ModifiedFrom');
+
             currentPage = 'instance';
             currentUuid = $.mobile.pageData.uuid;
           });
--- a/OrthancServer/OrthancRestApi.cpp	Fri Aug 16 17:11:45 2013 +0200
+++ b/OrthancServer/OrthancRestApi.cpp	Mon Aug 19 13:40:36 2013 +0200
@@ -1031,8 +1031,6 @@
       throw OrthancException(ErrorCode_BadRequest);
     }
 
-    target.clear();
-
     for (Json::Value::ArrayIndex i = 0; i < removals.size(); i++)
     {
       std::string name = removals[i].asString();
@@ -1052,8 +1050,6 @@
       throw OrthancException(ErrorCode_BadRequest);
     }
 
-    target.clear();
-
     Json::Value::Members members = replacements.getMemberNames();
     for (size_t i = 0; i < members.size(); i++)
     {
@@ -1099,7 +1095,7 @@
     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
+    //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 
@@ -1115,8 +1111,8 @@
     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 => generated below
-    //removals.insert(DicomTag(0x0020, 0x000e));  // Series Instance UID => generated below
+    //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 
@@ -1128,6 +1124,14 @@
     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
@@ -1136,22 +1140,8 @@
     // 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
+    // Set the PatientIdentityRemoved tag
     replacements.insert(std::make_pair(DicomTag(0x0012, 0x0062), "YES"));
-
-    // 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)));
-    }
-
-    // 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)));
-    }
   }
 
 
@@ -1254,13 +1244,6 @@
         replacements.insert(std::make_pair(DicomTag(0x0010, 0x0010), GeneratePatientName(context)));
       }
 
-      // Generate random Patient's ID if none is specified
-      if (replacements.find(DICOM_TAG_PATIENT_ID) == replacements.end())
-      {
-        replacements.insert(std::make_pair(DICOM_TAG_PATIENT_ID, 
-                                           FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient)));
-      }
-
       return true;
     }
     else
@@ -1293,14 +1276,23 @@
                                 UidMap& uidMap)
   {
     std::auto_ptr<DicomTag> tag;
-    if (level == DicomRootLevel_Series)
+
+    switch (level)
     {
-      tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID));
-    }
-    else
-    {
-      assert(level == DicomRootLevel_Study);
-      tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID));
+      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;
@@ -1330,7 +1322,8 @@
   }
 
 
-  static void AnonymizeOrModifyResource(Removals& removals,
+  static void AnonymizeOrModifyResource(bool isAnonymization,
+                                        Removals& removals,
                                         Replacements& replacements,
                                         bool removePrivateTags,
                                         MetadataType metadataType,
@@ -1367,15 +1360,20 @@
       LOG(INFO) << "Modifying instance " << *it;
       ParsedDicomFile& original = context.GetDicomFile(*it);
 
-      bool isNewSeries = RetrieveMappedUid(original, DicomRootLevel_Series, replacements, uidMap);
+      DicomInstanceHasher originalHasher = original.GetHasher();
 
-      bool isNewStudy = false;
-      if (resourceType == ResourceType_Study ||
-          resourceType == ResourceType_Patient)
+      if (isFirst && !isAnonymization)
       {
-        isNewStudy = RetrieveMappedUid(original, DicomRootLevel_Study, replacements, uidMap);
+        // If modifying a study or a series, keep the original patient ID.
+        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.
        **/
@@ -1396,7 +1394,6 @@
        **/
 
       DicomInstanceHasher modifiedHasher = modified->GetHasher();
-      DicomInstanceHasher originalHasher = original.GetHasher();
 
       if (isNewSeries)
       {
@@ -1410,6 +1407,12 @@
                                        metadataType, originalHasher.HashStudy());
       }
 
+      if (isNewPatient)
+      {
+        context.GetIndex().SetMetadata(modifiedHasher.HashPatient(), 
+                                       metadataType, originalHasher.HashPatient());
+      }
+
       assert(*it == originalHasher.HashInstance());
       assert(modifiedInstance == modifiedHasher.HashInstance());
       context.GetIndex().SetMetadata(modifiedInstance, metadataType, *it);
@@ -1421,9 +1424,6 @@
 
       if (isFirst)
       {
-        context.GetIndex().SetMetadata(modifiedHasher.HashPatient(), 
-                                       metadataType, originalHasher.HashPatient());
-
         std::string newId;
 
         switch (resourceType)
@@ -1478,6 +1478,27 @@
 
     if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call))
     {
+      // 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)));
+      }
+
+      // 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);
     }
   }
@@ -1491,7 +1512,7 @@
 
     if (ParseModifyRequest(removals, replacements, removePrivateTags, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, 
+      AnonymizeOrModifyResource(false, removals, replacements, removePrivateTags, 
                                 MetadataType_ModifiedFrom, ChangeType_ModifiedSeries, 
                                 ResourceType_Series, call);
     }
@@ -1506,7 +1527,7 @@
 
     if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, 
+      AnonymizeOrModifyResource(true, removals, replacements, removePrivateTags, 
                                 MetadataType_AnonymizedFrom, ChangeType_AnonymizedSeries, 
                                 ResourceType_Series, call);
     }
@@ -1521,7 +1542,7 @@
 
     if (ParseModifyRequest(removals, replacements, removePrivateTags, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, 
+      AnonymizeOrModifyResource(false, removals, replacements, removePrivateTags, 
                                 MetadataType_ModifiedFrom, ChangeType_ModifiedStudy, 
                                 ResourceType_Study, call);
     }
@@ -1536,14 +1557,14 @@
 
     if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, 
+      AnonymizeOrModifyResource(true, removals, replacements, removePrivateTags, 
                                 MetadataType_AnonymizedFrom, ChangeType_AnonymizedStudy, 
                                 ResourceType_Study, call);
     }
   }
 
 
-  static void ModifyPatientInplace(RestApi::PostCall& call)
+  /*static void ModifyPatientInplace(RestApi::PostCall& call)
   {
     Removals removals;
     Replacements replacements;
@@ -1551,11 +1572,11 @@
 
     if (ParseModifyRequest(removals, replacements, removePrivateTags, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, 
+      AnonymizeOrModifyResource(false, removals, replacements, removePrivateTags, 
                                 MetadataType_ModifiedFrom, ChangeType_ModifiedPatient, 
                                 ResourceType_Patient, call);
     }
-  }
+    }*/
 
 
   static void AnonymizePatientInplace(RestApi::PostCall& call)
@@ -1566,7 +1587,7 @@
 
     if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call))
     {
-      AnonymizeOrModifyResource(removals, replacements, removePrivateTags, 
+      AnonymizeOrModifyResource(true, removals, replacements, removePrivateTags, 
                                 MetadataType_AnonymizedFrom, ChangeType_AnonymizedPatient, 
                                 ResourceType_Patient, call);
     }
@@ -1834,7 +1855,7 @@
     Register("/instances/{id}/modify", ModifyInstance);
     Register("/series/{id}/modify", ModifySeriesInplace);
     Register("/studies/{id}/modify", ModifyStudyInplace);
-    Register("/patients/{id}/modify", ModifyPatientInplace);
+    //Register("/patients/{id}/modify", ModifyPatientInplace);
 
     Register("/instances/{id}/anonymize", AnonymizeInstance);
     Register("/series/{id}/anonymize", AnonymizeSeriesInplace);