changeset 4699:facea16b055b

added the "Level" argument to "/tools/bulk-content"
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 18 Jun 2021 17:58:56 +0200
parents d16c3c7f11ef
children 863383e7e582
files OrthancServer/Resources/Configuration.json OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp
diffstat 3 files changed, 146 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/Resources/Configuration.json	Fri Jun 18 16:37:12 2021 +0200
+++ b/OrthancServer/Resources/Configuration.json	Fri Jun 18 17:58:56 2021 +0200
@@ -628,7 +628,7 @@
   // Load a set of external DICOM dictionaries in order to replace the
   // default dictionaries. This option must contain a set of files in
   // the DCMTK format. The order of the dictionaries *is*
-  // important. This option can be used to turn Orhanc into a DICONDE
+  // important. This option can be used to turn Orthanc into a DICONDE
   // server. (new in Orthanc 1.9.4)
   /**
      "ExternalDictionaries" : [
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Fri Jun 18 16:37:12 2021 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Fri Jun 18 17:58:56 2021 +0200
@@ -427,7 +427,7 @@
         .SetTag("System")
         .SetSummary("Modify a set of resources")
         .SetRequestField("Resources", RestApiCallDocumentation::Type_JsonListOfStrings,
-                         "List of the Orthanc identifiers of the patients/studies/series/instances of interest.", false)
+                         "List of the Orthanc identifiers of the patients/studies/series/instances of interest.", true)
         .SetDescription("Start a job that will modify all the DICOM patients, studies, series or instances "
                         "whose identifiers are provided in the `Resources` field.")
         .AddAnswerType(MimeType_Json, "The list of all the resources that have been altered by this modification");
@@ -485,7 +485,7 @@
         .SetTag("System")
         .SetSummary("Anonymize a set of resources")
         .SetRequestField("Resources", RestApiCallDocumentation::Type_JsonListOfStrings,
-                         "List of the Orthanc identifiers of the patients/studies/series/instances of interest.", false)
+                         "List of the Orthanc identifiers of the patients/studies/series/instances of interest.", true)
         .SetDescription("Start a job that will anonymize all the DICOM patients, studies, series or instances "
                         "whose identifiers are provided in the `Resources` field.")
         .AddAnswerType(MimeType_Json, "The list of all the resources that have been created by this anonymization");
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Fri Jun 18 16:37:12 2021 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Fri Jun 18 17:58:56 2021 +0200
@@ -3092,6 +3092,27 @@
   }
 
 
+  static void GetBulkChildren(std::set<std::string>& target,
+                              ServerIndex& index,
+                              const std::set<std::string>& source)
+  {
+    target.clear();
+
+    for (std::set<std::string>::const_iterator
+           it = source.begin(); it != source.end(); ++it)
+    {
+      std::list<std::string> children;
+      index.GetChildren(children, *it);
+
+      for (std::list<std::string>::const_iterator
+             child = children.begin(); child != children.end(); ++child)
+      {
+        target.insert(*child);
+      }
+    }
+  }
+
+
   static void BulkContent(RestApiPostCall& call)
   {
     if (call.IsDocumentation())
@@ -3102,7 +3123,11 @@
         .SetTag("System")
         .SetSummary("Describe a set of instances")
         .SetRequestField("Resources", RestApiCallDocumentation::Type_JsonListOfStrings,
-                         "List of the Orthanc identifiers of the patients/studies/series/instances of interest.", false)
+                         "List of the Orthanc identifiers of the patients/studies/series/instances of interest.", true)
+        .SetRequestField("Level", RestApiCallDocumentation::Type_String,
+                         "This optional argument specifies the level of interest (can be `Patient`, `Study`, `Series` or "
+                         "`Instance`). Orthanc will loop over the items inside `Resources`, and explorer upward or "
+                         "downward in the DICOM hierarchy in order to find the level of interest.", false)
         .SetDescription("Get the content all the DICOM patients, studies, series or instances "
                         "whose identifiers are provided in the `Resources` field, in one single call.");
       return;
@@ -3117,27 +3142,131 @@
     }
     else
     {
+      static const char* const LEVEL = "Level";      
+      
       const DicomToJsonFormat format = OrthancRestApi::GetDicomFormat(request, DicomToJsonFormat_Human);
 
       ServerIndex& index = OrthancRestApi::GetIndex(call);
       
-      std::list<std::string> resources;
-      SerializationToolbox::ReadListOfStrings(resources, request, "Resources");
-
       Json::Value answer = Json::arrayValue;
-      for (std::list<std::string>::const_iterator
-             it = resources.begin(); it != resources.end(); ++it)
+
+      if (request.isMember(LEVEL))
       {
-        ResourceType type;
-        Json::Value item;
-        if (index.LookupResourceType(type, *it) &&
-            index.ExpandResource(item, *it, type, format))
+        // Complex case: Need to explore the DICOM hierarchy
+        ResourceType level = StringToResourceType(SerializationToolbox::ReadString(request, LEVEL).c_str());
+
+        std::set<std::string> resources;
+        SerializationToolbox::ReadSetOfStrings(resources, request, "Resources");
+
+        std::set<std::string> interest;
+
+        assert(ResourceType_Patient < ResourceType_Study &&
+               ResourceType_Study < ResourceType_Series &&
+               ResourceType_Series < ResourceType_Instance);
+
+        for (std::set<std::string>::const_iterator
+               it = resources.begin(); it != resources.end(); ++it)
         {
-          answer.append(item);
+          ResourceType type;
+          if (index.LookupResourceType(type, *it))
+          {
+            if (type == level)
+            {
+              // This resource is already from the level of interest
+              interest.insert(*it);
+            }
+            else if (type < level)
+            {
+              // Need to explore children
+              std::set<std::string> current;
+              current.insert(*it);
+              
+              for (;;)
+              {
+                std::set<std::string> children;
+                GetBulkChildren(children, index, current);
+
+                type = GetChildResourceType(type);
+                if (type == level)
+                {
+                  for (std::set<std::string>::const_iterator
+                         it = children.begin(); it != children.end(); ++it)
+                  {
+                    interest.insert(*it);
+                  }
+
+                  break;  // done
+                }
+                else
+                {
+                  current.swap(children);
+                }
+              }
+            }
+            else
+            {
+              // Need to explore parents
+              std::string current = *it;
+              
+              for (;;)
+              {
+                std::string parent;
+                if (index.LookupParent(parent, current))
+                {
+                  type = GetParentResourceType(type);
+                  if (type == level)
+                  {
+                    interest.insert(parent);
+                    break;  // done
+                  }
+                  else
+                  {
+                    current = parent;
+                  }
+                }
+                else
+                {
+                  break;  // The resource has been deleted during the exploration
+                }
+              }
+            }
+          }
+          else
+          {
+            CLOG(INFO, HTTP) << "Unknown resource during a bulk content retrieval: " << *it;
+          }
         }
-        else
+        
+        for (std::set<std::string>::const_iterator
+               it = interest.begin(); it != interest.end(); ++it)
         {
-          CLOG(INFO, HTTP) << "Unknown resource during a bulk content retrieval: " << *it;
+          Json::Value item;
+          if (index.ExpandResource(item, *it, level, format))
+          {
+            answer.append(item);
+          }
+        }
+      }
+      else
+      {
+        // Simple case: We return the queried resources as such
+        std::list<std::string> resources;
+        SerializationToolbox::ReadListOfStrings(resources, request, "Resources");
+
+        for (std::list<std::string>::const_iterator
+               it = resources.begin(); it != resources.end(); ++it)
+        {
+          ResourceType type;
+          Json::Value item;
+          if (index.LookupResourceType(type, *it) &&
+              index.ExpandResource(item, *it, type, format))
+          {
+            answer.append(item);
+          }
+          else
+          {
+            CLOG(INFO, HTTP) << "Unknown resource during a bulk content retrieval: " << *it;
+          }
         }
       }
 
@@ -3154,7 +3283,7 @@
         .SetTag("System")
         .SetSummary("Delete a set of instances")
         .SetRequestField("Resources", RestApiCallDocumentation::Type_JsonListOfStrings,
-                         "List of the Orthanc identifiers of the patients/studies/series/instances of interest.", false)
+                         "List of the Orthanc identifiers of the patients/studies/series/instances of interest.", true)
         .SetDescription("Delete all the DICOM patients, studies, series or instances "
                         "whose identifiers are provided in the `Resources` field.");
       return;