changeset 5826:d73dfb4548c6 find-refactoring

tools/find: ParentPatient and co ..
author Alain Mazy <am@orthanc.team>
date Mon, 07 Oct 2024 10:51:27 +0200
parents 881cd0965146
children 976872a99d39
files NEWS OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp OrthancServer/Sources/Search/ISqlLookupFormatter.cpp
diffstat 3 files changed, 92 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Fri Oct 04 19:03:14 2024 +0200
+++ b/NEWS	Mon Oct 07 10:51:27 2024 +0200
@@ -36,6 +36,8 @@
   example: /changes?type=StableStudy&to=7584&limit=100
 * With DB backend with "HasExtendedFind" support, /tools/find now supports new options:
   - 'OrderBy' to order by DICOM Tag or metadata value
+  - 'ParentPatient', 'ParentStudy', 'ParentSeries' to retrieve only descendants of an
+    Orthanc resource.
 
 
 Maintenance
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Fri Oct 04 19:03:14 2024 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Mon Oct 07 10:51:27 2024 +0200
@@ -3252,7 +3252,9 @@
     static const char* const KEY_ORDER_BY_KEY = "Key";                    // New in Orthanc 1.12.5
     static const char* const KEY_ORDER_BY_TYPE = "Type";                  // New in Orthanc 1.12.5
     static const char* const KEY_ORDER_BY_DIRECTION = "Direction";        // New in Orthanc 1.12.5
-
+    static const char* const KEY_PARENT_PATIENT = "ParentPatient";        // New in Orthanc 1.12.5
+    static const char* const KEY_PARENT_STUDY = "ParentStudy";            // New in Orthanc 1.12.5
+    static const char* const KEY_PARENT_SERIES = "ParentSeries";          // New in Orthanc 1.12.5
 
     if (call.IsDocumentation())
     {
@@ -3287,7 +3289,13 @@
         .SetRequestField(KEY_LABELS_CONSTRAINT, RestApiCallDocumentation::Type_String,
                          "Constraint on the labels, can be `All`, `Any`, or `None` (defaults to `All`, new in Orthanc 1.12.0)", true)
         .SetRequestField(KEY_ORDER_BY, RestApiCallDocumentation::Type_JsonListOfObjects,
-                         "Array of associative arrays containing the requested ordering", true)
+                         "Array of associative arrays containing the requested ordering (new in Orthanc 1.12.5)", true)
+        .SetRequestField(KEY_PARENT_PATIENT, RestApiCallDocumentation::Type_String,
+                         "Limit the reported resources to descendants of this patient (new in Orthanc 1.12.5)", true)
+        .SetRequestField(KEY_PARENT_STUDY, RestApiCallDocumentation::Type_String,
+                         "Limit the reported resources to descendants of this study (new in Orthanc 1.12.5)", true)
+        .SetRequestField(KEY_PARENT_SERIES, RestApiCallDocumentation::Type_String,
+                         "Limit the reported resources to descendants of this series (new in Orthanc 1.12.5)", true)
         .AddAnswerType(MimeType_Json, "JSON array containing either the Orthanc identifiers, or detailed information "
                        "about the reported resources (if `Expand` argument is `true`)");
       return;
@@ -3356,6 +3364,24 @@
       throw OrthancException(ErrorCode_BadRequest, 
                              "Field \"" + std::string(KEY_ORDER_BY) + "\" must be an array");
     }
+    else if (request.isMember(KEY_PARENT_PATIENT) &&
+             request[KEY_PARENT_PATIENT].type() != Json::stringValue)
+    {
+      throw OrthancException(ErrorCode_BadRequest, 
+                             "Field \"" + std::string(KEY_PARENT_PATIENT) + "\" must be a string");
+    }
+    else if (request.isMember(KEY_PARENT_STUDY) &&
+             request[KEY_PARENT_STUDY].type() != Json::stringValue)
+    {
+      throw OrthancException(ErrorCode_BadRequest, 
+                             "Field \"" + std::string(KEY_PARENT_STUDY) + "\" must be a string");
+    }
+    else if (request.isMember(KEY_PARENT_SERIES) &&
+             request[KEY_PARENT_SERIES].type() != Json::stringValue)
+    {
+      throw OrthancException(ErrorCode_BadRequest, 
+                             "Field \"" + std::string(KEY_PARENT_SERIES) + "\" must be a string");
+    }
     else if (true)
     {
       /**
@@ -3481,6 +3507,19 @@
         }
       }
 
+      if (request.isMember(KEY_PARENT_PATIENT)) // New in Orthanc 1.12.5
+      {
+        finder.SetOrthancId(ResourceType_Patient, request[KEY_PARENT_PATIENT].asString());
+      }
+      else if (request.isMember(KEY_PARENT_STUDY))
+      {
+        finder.SetOrthancId(ResourceType_Study, request[KEY_PARENT_STUDY].asString());
+      }
+      else if (request.isMember(KEY_PARENT_SERIES))
+      {
+        finder.SetOrthancId(ResourceType_Series, request[KEY_PARENT_SERIES].asString());
+     }
+
       if (request.isMember(KEY_ORDER_BY))  // New in Orthanc 1.12.5
       {
         for (Json::Value::ArrayIndex i = 0; i < request[KEY_ORDER_BY].size(); i++)
--- a/OrthancServer/Sources/Search/ISqlLookupFormatter.cpp	Fri Oct 04 19:03:14 2024 +0200
+++ b/OrthancServer/Sources/Search/ISqlLookupFormatter.cpp	Mon Oct 07 10:51:27 2024 +0200
@@ -57,7 +57,28 @@
         throw OrthancException(ErrorCode_InternalError);
     }
   }      
-  
+
+  static std::string FormatLevel(const char* prefix, ResourceType level)
+  {
+    switch (level)
+    {
+      case ResourceType_Patient:
+        return std::string(prefix) + "patients";
+        
+      case ResourceType_Study:
+        return std::string(prefix) + "studies";
+        
+      case ResourceType_Series:
+        return std::string(prefix) + "series";
+        
+      case ResourceType_Instance:
+        return std::string(prefix) + "instances";
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+  }      
+
 
   static bool FormatComparison(std::string& target,
                                ISqlLookupFormatter& formatter,
@@ -745,28 +766,37 @@
 
     std::string joins, comparisons;
 
-    if (request.GetOrthancIdentifiers().IsDefined() && request.GetOrthancIdentifiers().DetectLevel() <= queryLevel)
     {
-      // single child resource matching, there should not be other constraints (at least for now)
-      assert(request.GetDicomTagConstraints().GetSize() == 0);
-      assert(request.GetLabels().size() == 0);
-      assert(request.HasLimits() == false);
+      // handle parent constraints
+      if (request.GetOrthancIdentifiers().IsDefined() && request.GetOrthancIdentifiers().DetectLevel() <= queryLevel)
+      {
+        ResourceType topParentLevel = request.GetOrthancIdentifiers().DetectLevel();
 
-      ResourceType topParentLevel = request.GetOrthancIdentifiers().DetectLevel();
-      const std::string& strTopParentLevel = FormatLevel(topParentLevel);
+        if (topParentLevel == queryLevel)
+        {
+          comparisons += " AND " + FormatLevel(topParentLevel) + ".publicId = " + formatter.GenerateParameter(request.GetOrthancIdentifiers().GetLevel(topParentLevel));
+        }
+        else
+        {
+          comparisons += " AND " + FormatLevel("parent", topParentLevel) + ".publicId = " + formatter.GenerateParameter(request.GetOrthancIdentifiers().GetLevel(topParentLevel));
 
-      comparisons = " AND " + strTopParentLevel + ".publicId = " + formatter.GenerateParameter(request.GetOrthancIdentifiers().GetLevel(topParentLevel));
+          for (int level = queryLevel; level > topParentLevel; level--)
+          {
+            joins += " INNER JOIN Resources " +
+                    FormatLevel("parent", static_cast<ResourceType>(level - 1)) + " ON " +
+                    FormatLevel("parent", static_cast<ResourceType>(level - 1)) + ".internalId = ";
+            if (level == queryLevel)
+            {
+              joins += FormatLevel(static_cast<ResourceType>(level)) + ".parentId";
+            }
+            else
+            {
+              joins += FormatLevel("parent", static_cast<ResourceType>(level)) + ".parentId";
+            }
+          }
+        }
+      }
 
-      for (int level = queryLevel; level > topParentLevel; level--)
-      {
-        sql += (" INNER JOIN Resources " +
-                FormatLevel(static_cast<ResourceType>(level - 1)) + " ON " +
-                FormatLevel(static_cast<ResourceType>(level - 1)) + ".internalId=" +
-                FormatLevel(static_cast<ResourceType>(level)) + ".parentId");
-      }
-    }
-    else
-    {
       size_t count = 0;
       
       const DatabaseConstraints& dicomTagsConstraints = request.GetDicomTagConstraints();