# HG changeset patch # User Alain Mazy # Date 1728291087 -7200 # Node ID d73dfb4548c67cd70765d5270ed0acf9b08ca423 # Parent 881cd0965146dea382c28394cdb02c6878348edb tools/find: ParentPatient and co .. diff -r 881cd0965146 -r d73dfb4548c6 NEWS --- 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 diff -r 881cd0965146 -r d73dfb4548c6 OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp --- 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++) diff -r 881cd0965146 -r d73dfb4548c6 OrthancServer/Sources/Search/ISqlLookupFormatter.cpp --- 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(level - 1)) + " ON " + + FormatLevel("parent", static_cast(level - 1)) + ".internalId = "; + if (level == queryLevel) + { + joins += FormatLevel(static_cast(level)) + ".parentId"; + } + else + { + joins += FormatLevel("parent", static_cast(level)) + ".parentId"; + } + } + } + } - for (int level = queryLevel; level > topParentLevel; level--) - { - sql += (" INNER JOIN Resources " + - FormatLevel(static_cast(level - 1)) + " ON " + - FormatLevel(static_cast(level - 1)) + ".internalId=" + - FormatLevel(static_cast(level)) + ".parentId"); - } - } - else - { size_t count = 0; const DatabaseConstraints& dicomTagsConstraints = request.GetDicomTagConstraints();