# HG changeset patch # User Alain Mazy # Date 1728464780 -7200 # Node ID 79a497908b0404cba5de10f6ff59c1153ec34064 # Parent 79ac3924eff81eeda991e6aea4f60db4c1e151c1# Parent dd2af8692cbc322afe7c7d3596cd91392c3972cf merged find-refactoring -> attach-custom-data diff -r 79ac3924eff8 -r 79a497908b04 NEWS --- a/NEWS Wed Oct 02 11:41:01 2024 +0200 +++ b/NEWS Wed Oct 09 11:06:20 2024 +0200 @@ -42,12 +42,20 @@ * in /system, added a new field "Capabilities" with new values: - "HasExtendedChanges" for DB backend that provides this feature (the default SQLite DB or PostgreSQL vX.X, MySQL vX.X, ODBC vX.X). - - "HasExendedFind" for DB backend that provides this feature (the default SQLite DB + - "HasExtendedFind" for DB backend that provides this feature (the default SQLite DB or PostgreSQL vX.X, MySQL vX.X, ODBC vX.X). * With DB backend with "HasExtendedChanges" support, /changes now supports 2 more options: - 'type' to filter the changes returned by the query - 'to' to potentially cycle through changes in reverse order. 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. + - 'QueryMetadata' to filter results based on metadata values. + - 'ResponseContent' to define what shall be included in the response for each returned + resource (e.g: Metadata, Children, ...) + Maintenance ----------- @@ -67,6 +75,7 @@ - added 2 metrics: orthanc_storage_cache_miss_count & orthanc_storage_cache_hit_count * Upgraded dependencies for static builds: - curl 8.9.0 + - SQLite 3.46 * Added a new fallback when trying to decode a frame: transcode the file using the plugin before decoding the frame. This solves some issues with JP2K Lossy compression: https://discourse.orthanc-server.org/t/decoding-displaying-jpeg2000-lossy-images/5117 diff -r 79ac3924eff8 -r 79a497908b04 OrthancFramework/Resources/CMake/SQLiteConfiguration.cmake --- a/OrthancFramework/Resources/CMake/SQLiteConfiguration.cmake Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancFramework/Resources/CMake/SQLiteConfiguration.cmake Wed Oct 09 11:06:20 2024 +0200 @@ -37,11 +37,11 @@ if (SQLITE_STATIC) - SET(SQLITE_SOURCES_DIR ${CMAKE_BINARY_DIR}/sqlite-amalgamation-3270100) - SET(SQLITE_MD5 "16717b26358ba81f0bfdac07addc77da") - SET(SQLITE_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/sqlite-amalgamation-3270100.zip") + SET(SQLITE_SOURCES_DIR ${CMAKE_BINARY_DIR}/sqlite-amalgamation-3460100) + SET(SQLITE_MD5 "1fb0f7ebbee45752098cf453b6dffff3") + SET(SQLITE_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/sqlite-amalgamation-3460100.zip") - set(ORTHANC_SQLITE_VERSION 3027001) + set(ORTHANC_SQLITE_VERSION 3046001) DownloadPackage(${SQLITE_MD5} ${SQLITE_URL} "${SQLITE_SOURCES_DIR}") diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/CMakeLists.txt --- a/OrthancServer/CMakeLists.txt Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/CMakeLists.txt Wed Oct 09 11:06:20 2024 +0200 @@ -126,8 +126,9 @@ ${CMAKE_SOURCE_DIR}/Sources/OrthancWebDav.cpp ${CMAKE_SOURCE_DIR}/Sources/QueryRetrieveHandler.cpp ${CMAKE_SOURCE_DIR}/Sources/ResourceFinder.cpp - ${CMAKE_SOURCE_DIR}/Sources/Search/DatabaseConstraint.cpp - ${CMAKE_SOURCE_DIR}/Sources/Search/DatabaseConstraints.cpp + ${CMAKE_SOURCE_DIR}/Sources/Search/DatabaseDicomTagConstraint.cpp + ${CMAKE_SOURCE_DIR}/Sources/Search/DatabaseDicomTagConstraints.cpp + ${CMAKE_SOURCE_DIR}/Sources/Search/DatabaseMetadataConstraint.cpp ${CMAKE_SOURCE_DIR}/Sources/Search/DatabaseLookup.cpp ${CMAKE_SOURCE_DIR}/Sources/Search/DicomTagConstraint.cpp ${CMAKE_SOURCE_DIR}/Sources/Search/HierarchicalMatcher.cpp diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp --- a/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -566,7 +566,7 @@ virtual void ApplyLookupResources(std::list& resourcesId, std::list* instancesId, - const DatabaseConstraints& lookup, + const DatabaseDicomTagConstraints& lookup, ResourceType queryLevel, const std::set& labels, LabelsConstraint labelsConstraint, diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp --- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -800,7 +800,7 @@ virtual void ApplyLookupResources(std::list& resourcesId, std::list* instancesId, // Can be NULL if not needed - const DatabaseConstraints& lookup, + const DatabaseDicomTagConstraints& lookup, ResourceType queryLevel, const std::set& labels, LabelsConstraint labelsConstraint, diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp --- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -36,6 +36,7 @@ #include "../../Sources/Database/VoidDatabaseListener.h" #include "../../Sources/ServerToolbox.h" #include "PluginsEnumerations.h" +#include "../../Sources/Database/MainDicomTagsRegistry.h" #include "OrthancDatabasePlugin.pb.h" // Auto-generated file @@ -137,7 +138,7 @@ static void Convert(DatabasePluginMessages::DatabaseConstraint& target, - const DatabaseConstraint& source) + const DatabaseDicomTagConstraint& source) { target.set_level(Convert(source.GetLevel())); target.set_tag_group(source.GetTag().GetGroup()); @@ -180,6 +181,94 @@ } + static void Convert(DatabasePluginMessages::DatabaseMetadataConstraint& target, + const DatabaseMetadataConstraint& source) + { + target.set_metadata(source.GetMetadata()); + target.set_is_case_sensitive(source.IsCaseSensitive()); + target.set_is_mandatory(source.IsMandatory()); + + target.mutable_values()->Reserve(source.GetValuesCount()); + for (size_t j = 0; j < source.GetValuesCount(); j++) + { + target.add_values(source.GetValue(j)); + } + + switch (source.GetConstraintType()) + { + case ConstraintType_Equal: + target.set_type(DatabasePluginMessages::CONSTRAINT_EQUAL); + break; + + case ConstraintType_SmallerOrEqual: + target.set_type(DatabasePluginMessages::CONSTRAINT_SMALLER_OR_EQUAL); + break; + + case ConstraintType_GreaterOrEqual: + target.set_type(DatabasePluginMessages::CONSTRAINT_GREATER_OR_EQUAL); + break; + + case ConstraintType_Wildcard: + target.set_type(DatabasePluginMessages::CONSTRAINT_WILDCARD); + break; + + case ConstraintType_List: + target.set_type(DatabasePluginMessages::CONSTRAINT_LIST); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + static void Convert(DatabasePluginMessages::Find_Request_Ordering& target, + const FindRequest::Ordering& source) + { + switch (source.GetKeyType()) + { + case FindRequest::KeyType_DicomTag: + { + ResourceType tagLevel; + DicomTagType tagType; + MainDicomTagsRegistry registry; + + registry.LookupTag(tagLevel, tagType, source.GetDicomTag()); + + target.set_key_type(DatabasePluginMessages::ORDERING_KEY_TYPE_DICOM_TAG); + target.set_tag_group(source.GetDicomTag().GetGroup()); + target.set_tag_element(source.GetDicomTag().GetElement()); + target.set_is_identifier_tag(tagType == DicomTagType_Identifier); + target.set_tag_level(Convert(tagLevel)); + + }; break; + + case FindRequest::KeyType_Metadata: + target.set_key_type(DatabasePluginMessages::ORDERING_KEY_TYPE_METADATA); + target.set_metadata(source.GetMetadataType()); + + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + switch (source.GetDirection()) + { + case FindRequest::OrderingDirection_Ascending: + target.set_direction(DatabasePluginMessages::ORDERING_DIRECTION_ASC); + break; + + case FindRequest::OrderingDirection_Descending: + target.set_direction(DatabasePluginMessages::ORDERING_DIRECTION_DESC); + + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + static DatabasePluginMessages::LabelsConstraintType Convert(LabelsConstraint constraint) { switch (constraint) @@ -1139,7 +1228,7 @@ virtual void ApplyLookupResources(std::list& resourcesId, std::list* instancesId, // Can be NULL if not needed - const DatabaseConstraints& lookup, + const DatabaseDicomTagConstraints& lookup, ResourceType queryLevel, const std::set& labels, LabelsConstraint labelsConstraint, @@ -1422,6 +1511,16 @@ Convert(*dbRequest.mutable_find()->add_dicom_tag_constraints(), request.GetDicomTagConstraints().GetConstraint(i)); } + for (std::deque::const_iterator it = request.GetMetadataConstraint().begin(); it != request.GetMetadataConstraint().end(); ++it) + { + Convert(*dbRequest.mutable_find()->add_metadata_constraints(), *(*it)); + } + + for (std::deque::const_iterator it = request.GetOrdering().begin(); it != request.GetOrdering().end(); ++it) + { + Convert(*dbRequest.mutable_find()->add_ordering(), *(*it)); + } + if (request.HasLimits()) { dbRequest.mutable_find()->mutable_limits()->set_since(request.GetLimitsSince()); @@ -1435,9 +1534,6 @@ dbRequest.mutable_find()->set_labels_constraint(Convert(request.GetLabelsConstraint())); - // TODO-FIND: ordering_ - // TODO-FIND: metadataConstraints__ - dbRequest.mutable_find()->set_retrieve_main_dicom_tags(request.IsRetrieveMainDicomTags()); dbRequest.mutable_find()->set_retrieve_metadata(request.IsRetrieveMetadata()); dbRequest.mutable_find()->set_retrieve_labels(request.IsRetrieveLabels()); diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Plugins/Engine/OrthancPlugins.cpp --- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -59,7 +59,7 @@ #include "../../Sources/Database/VoidDatabaseListener.h" #include "../../Sources/OrthancConfiguration.h" #include "../../Sources/OrthancFindRequestHandler.h" -#include "../../Sources/Search/DatabaseConstraint.h" +#include "../../Sources/Search/IDatabaseConstraint.h" #include "../../Sources/Search/HierarchicalMatcher.h" #include "../../Sources/ServerContext.h" #include "../../Sources/ServerToolbox.h" diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto --- a/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto Wed Oct 09 11:06:20 2024 +0200 @@ -79,6 +79,16 @@ LABELS_CONSTRAINT_NONE = 2; } +enum OrderingKeyType { + ORDERING_KEY_TYPE_DICOM_TAG = 0; + ORDERING_KEY_TYPE_METADATA = 1; +} + +enum OrderingDirection { + ORDERING_DIRECTION_ASC = 0; + ORDERING_DIRECTION_DESC = 1; +} + message ServerIndexChange { int64 seq = 1; int32 change_type = 2; // opaque "ChangeType" in Orthanc @@ -110,6 +120,13 @@ repeated string values = 8; } +message DatabaseMetadataConstraint { + int32 metadata = 1; + bool is_case_sensitive = 2; + bool is_mandatory = 3; + ConstraintType type = 4; + repeated string values = 5; +} /** * Database-level operations. @@ -861,6 +878,15 @@ repeated int32 retrieve_metadata = 2; repeated Tag retrieve_main_dicom_tags = 3; } + message Ordering { + OrderingKeyType key_type = 1; + OrderingDirection direction = 2; + uint32 tag_group = 3; + uint32 tag_element = 4; + bool is_identifier_tag = 5; + ResourceType tag_level = 6; + int32 metadata = 7; + } // Part 1 of the request: Constraints ResourceType level = 1; @@ -872,9 +898,9 @@ Limits limits = 7; // optional repeated string labels = 8; LabelsConstraintType labels_constraint = 9; + repeated Ordering ordering = 10; + repeated DatabaseMetadataConstraint metadata_constraints = 11; - // TODO-FIND: ordering_ - // TODO-FIND: metadataConstraints_ // Part 2 of the request: What is to be retrieved bool retrieve_main_dicom_tags = 100; diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp --- a/OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -74,7 +74,7 @@ } } - void Add(const DatabaseConstraint& constraint) + void Add(const DatabaseDicomTagConstraint& constraint) { constraints_.push_back(new DicomTagConstraint(constraint)); } @@ -84,7 +84,7 @@ static void ApplyIdentifierConstraint(SetOfResources& candidates, ILookupResources& compatibility, - const DatabaseConstraint& constraint, + const DatabaseDicomTagConstraint& constraint, ResourceType level) { std::list matches; @@ -134,8 +134,8 @@ static void ApplyIdentifierRange(SetOfResources& candidates, ILookupResources& compatibility, - const DatabaseConstraint& smaller, - const DatabaseConstraint& greater, + const DatabaseDicomTagConstraint& smaller, + const DatabaseDicomTagConstraint& greater, ResourceType level) { assert(smaller.GetConstraintType() == ConstraintType_SmallerOrEqual && @@ -153,10 +153,10 @@ static void ApplyLevel(SetOfResources& candidates, IDatabaseWrapper::ITransaction& transaction, ILookupResources& compatibility, - const DatabaseConstraints& lookup, + const DatabaseDicomTagConstraints& lookup, ResourceType level) { - typedef std::set SetOfConstraints; + typedef std::set SetOfConstraints; typedef std::map Identifiers; // (1) Select which constraints apply to this level, and split @@ -168,7 +168,7 @@ for (size_t i = 0; i < lookup.GetSize(); i++) { - const DatabaseConstraint& constraint = lookup.GetConstraint(i); + const DatabaseDicomTagConstraint& constraint = lookup.GetConstraint(i); if (constraint.GetLevel() == level) { @@ -191,8 +191,8 @@ { // Check whether some range constraint over identifiers is // present at this level - const DatabaseConstraint* smaller = NULL; - const DatabaseConstraint* greater = NULL; + const DatabaseDicomTagConstraint* smaller = NULL; + const DatabaseDicomTagConstraint* greater = NULL; for (SetOfConstraints::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) @@ -308,7 +308,7 @@ void DatabaseLookup::ApplyLookupResources(std::list& resourcesId, std::list* instancesId, - const DatabaseConstraints& lookup, + const DatabaseDicomTagConstraints& lookup, ResourceType queryLevel, size_t limit) { diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Database/Compatibility/DatabaseLookup.h --- a/OrthancServer/Sources/Database/Compatibility/DatabaseLookup.h Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/Database/Compatibility/DatabaseLookup.h Wed Oct 09 11:06:20 2024 +0200 @@ -46,7 +46,7 @@ void ApplyLookupResources(std::list& resourcesId, std::list* instancesId, - const DatabaseConstraints& lookup, + const DatabaseDicomTagConstraints& lookup, ResourceType queryLevel, size_t limit); }; diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Database/Compatibility/ILookupResources.cpp --- a/OrthancServer/Sources/Database/Compatibility/ILookupResources.cpp Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/Database/Compatibility/ILookupResources.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -35,7 +35,7 @@ ILookupResources& compatibility, std::list& resourcesId, std::list* instancesId, - const DatabaseConstraints& lookup, + const DatabaseDicomTagConstraints& lookup, ResourceType queryLevel, size_t limit) { diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Database/Compatibility/ILookupResources.h --- a/OrthancServer/Sources/Database/Compatibility/ILookupResources.h Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/Database/Compatibility/ILookupResources.h Wed Oct 09 11:06:20 2024 +0200 @@ -60,7 +60,7 @@ ILookupResources& compatibility, std::list& resourcesId, std::list* instancesId, - const DatabaseConstraints& lookup, + const DatabaseDicomTagConstraints& lookup, ResourceType queryLevel, size_t limit); }; diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Database/FindRequest.cpp --- a/OrthancServer/Sources/Database/FindRequest.cpp Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/Database/FindRequest.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -100,8 +100,13 @@ FindRequest::~FindRequest() { + for (std::deque::iterator it = ordering_.begin(); it != ordering_.end(); ++it) + { + assert(*it != NULL); + delete *it; + } - for (std::deque::iterator it = ordering_.begin(); it != ordering_.end(); ++it) + for (std::deque::iterator it = metadataConstraints_.begin(); it != metadataConstraints_.end(); ++it) { assert(*it != NULL); delete *it; @@ -233,6 +238,12 @@ } + void FindRequest::AddMetadataConstraint(DatabaseMetadataConstraint* constraint) + { + metadataConstraints_.push_back(constraint); + } + + void FindRequest::SetRetrieveParentIdentifier(bool retrieve) { if (level_ == ResourceType_Patient) diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Database/FindRequest.h --- a/OrthancServer/Sources/Database/FindRequest.h Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/Database/FindRequest.h Wed Oct 09 11:06:20 2024 +0200 @@ -24,7 +24,8 @@ #pragma once #include "../../../OrthancFramework/Sources/DicomFormat/DicomTag.h" -#include "../Search/DatabaseConstraints.h" +#include "../Search/DatabaseDicomTagConstraints.h" +#include "../Search/DatabaseMetadataConstraint.h" #include "../Search/DicomTagConstraint.h" #include "../Search/ISqlLookupFormatter.h" #include "../ServerEnumerations.h" @@ -232,16 +233,15 @@ // filter & ordering fields ResourceType level_; // The level of the response (the filtering on tags, labels and metadata also happens at this level) OrthancIdentifiers orthancIdentifiers_; // The response must belong to this Orthanc resources hierarchy - DatabaseConstraints dicomTagConstraints_; // All tags filters (note: the order is not important) + DatabaseDicomTagConstraints dicomTagConstraints_; // All tags filters (note: the order is not important) bool hasLimits_; uint64_t limitsSince_; uint64_t limitsCount_; std::set labels_; LabelsConstraint labelsConstraint_; - // TODO-FIND std::deque ordering_; // The ordering criteria (note: the order is important !) - std::deque /* TODO-FIND */ metadataConstraints_; // All metadata filters (note: the order is not important) + std::deque metadataConstraints_; // All metadata filters (note: the order is not important) bool retrieveMainDicomTags_; bool retrieveMetadata_; @@ -284,12 +284,12 @@ return orthancIdentifiers_; } - DatabaseConstraints& GetDicomTagConstraints() + DatabaseDicomTagConstraints& GetDicomTagConstraints() { return dicomTagConstraints_; } - const DatabaseConstraints& GetDicomTagConstraints() const + const DatabaseDicomTagConstraints& GetDicomTagConstraints() const { return dicomTagConstraints_; } @@ -327,6 +327,13 @@ return ordering_; } + void AddMetadataConstraint(DatabaseMetadataConstraint* constraint); + + const std::deque& GetMetadataConstraint() const + { + return metadataConstraints_; + } + void SetLabels(const std::set& labels) { labels_ = labels; diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Database/IDatabaseWrapper.h --- a/OrthancServer/Sources/Database/IDatabaseWrapper.h Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/Database/IDatabaseWrapper.h Wed Oct 09 11:06:20 2024 +0200 @@ -39,7 +39,7 @@ namespace Orthanc { - class DatabaseConstraints; + class DatabaseDicomTagConstraints; class ResourcesContent; class IDatabaseWrapper : public boost::noncopyable @@ -320,7 +320,7 @@ virtual void ApplyLookupResources(std::list& resourcesId, std::list* instancesId, // Can be NULL if not needed - const DatabaseConstraints& lookup, + const DatabaseDicomTagConstraints& lookup, ResourceType queryLevel, const std::set& labels, LabelsConstraint labelsConstraint, diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Database/MainDicomTagsRegistry.cpp --- a/OrthancServer/Sources/Database/MainDicomTagsRegistry.cpp Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/Database/MainDicomTagsRegistry.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -98,7 +98,7 @@ } - bool MainDicomTagsRegistry::NormalizeLookup(DatabaseConstraints& target, + bool MainDicomTagsRegistry::NormalizeLookup(DatabaseDicomTagConstraints& target, const DatabaseLookup& source, ResourceType queryLevel) const { diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Database/MainDicomTagsRegistry.h --- a/OrthancServer/Sources/Database/MainDicomTagsRegistry.h Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/Database/MainDicomTagsRegistry.h Wed Oct 09 11:06:20 2024 +0200 @@ -24,7 +24,7 @@ #pragma once #include "../Search/DatabaseLookup.h" -#include "../Search/DatabaseConstraints.h" +#include "../Search/DatabaseDicomTagConstraints.h" #include @@ -82,7 +82,7 @@ * constraints are less strict than the original DatabaseLookup, * so more resources will match them. **/ - bool NormalizeLookup(DatabaseConstraints& target, + bool NormalizeLookup(DatabaseDicomTagConstraints& target, const DatabaseLookup& source, ResourceType queryLevel) const; }; diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp --- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -430,7 +430,7 @@ virtual void ApplyLookupResources(std::list& resourcesId, std::list* instancesId, - const DatabaseConstraints& lookup, + const DatabaseDicomTagConstraints& lookup, ResourceType queryLevel, const std::set& labels, LabelsConstraint labelsConstraint, diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp --- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -1593,7 +1593,7 @@ DicomTagConstraint c(tag, ConstraintType_Equal, value, true, true); - DatabaseConstraints query; + DatabaseDicomTagConstraints query; bool isIdentical; // unused query.AddConstraint(c.ConvertToDatabaseConstraint(isIdentical, level, DicomTagType_Identifier)); @@ -1602,12 +1602,12 @@ { private: std::vector& result_; - const DatabaseConstraints& query_; + const DatabaseDicomTagConstraints& query_; ResourceType level_; public: Operations(std::vector& result, - const DatabaseConstraints& query, + const DatabaseDicomTagConstraints& query, ResourceType level) : result_(result), query_(query), @@ -1893,7 +1893,7 @@ LabelsConstraint labelsConstraint, uint32_t limit) { - class Operations : public ReadOnlyOperationsT6&, LabelsConstraint, size_t> { private: @@ -1939,7 +1939,7 @@ ServerToolbox::CheckValidLabel(*it); } - DatabaseConstraints normalized; + DatabaseDicomTagConstraints normalized; assert(mainDicomTagsRegistry_.get() != NULL); mainDicomTagsRegistry_->NormalizeLookup(normalized, lookup, queryLevel); diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Database/StatelessDatabaseOperations.h --- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Wed Oct 09 11:06:20 2024 +0200 @@ -221,7 +221,7 @@ void ApplyLookupResources(std::list& resourcesId, std::list* instancesId, // Can be NULL if not needed - const DatabaseConstraints& lookup, + const DatabaseDicomTagConstraints& lookup, ResourceType queryLevel, const std::set& labels, // New in Orthanc 1.12.0 LabelsConstraint labelsConstraint, // New in Orthanc 1.12.0 diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/OrthancFindRequestHandler.cpp --- a/OrthancServer/Sources/OrthancFindRequestHandler.cpp Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/OrthancFindRequestHandler.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -631,7 +631,7 @@ * EXPERIMENTAL VERSION **/ - ResourceFinder finder(level, false /* don't expand */); + ResourceFinder finder(level, ResponseContentFlags_ID); finder.SetDatabaseLookup(lookup); finder.AddRequestedTags(requestedTags); diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -41,6 +41,7 @@ #include "../OrthancConfiguration.h" #include "../Search/DatabaseLookup.h" +#include "../Search/DatabaseMetadataConstraint.h" #include "../ServerContext.h" #include "../ServerToolbox.h" #include "../SliceOrdering.h" @@ -136,7 +137,14 @@ DicomToJsonFormat format, bool retrieveMetadata) { - ResourceFinder finder(level, true /* expand */); + ResponseContentFlags responseContent = ResponseContentFlags_ExpandTrue; + + if (retrieveMetadata) + { + responseContent = static_cast(static_cast(responseContent) | ResponseContentFlags_Metadata); + } + + ResourceFinder finder(level, responseContent); finder.SetOrthancId(level, identifier); finder.SetRetrieveMetadata(retrieveMetadata); @@ -257,7 +265,7 @@ std::set requestedTags; OrthancRestApi::GetRequestedTags(requestedTags, call); - ResourceFinder finder(resourceType, expand); + ResourceFinder finder(resourceType, (expand ? ResponseContentFlags_ExpandTrue : ResponseContentFlags_ID)); finder.AddRequestedTags(requestedTags); if (call.HasArgument("limit") || @@ -363,7 +371,7 @@ * EXPERIMENTAL VERSION **/ - ResourceFinder finder(resourceType, true /* expand */); + ResourceFinder finder(resourceType, ResponseContentFlags_ExpandTrue); finder.AddRequestedTags(requestedTags); finder.SetOrthancId(resourceType, call.GetUriComponent("id", "")); @@ -3250,6 +3258,15 @@ static const char* const KEY_SINCE = "Since"; static const char* const KEY_LABELS = "Labels"; // New in Orthanc 1.12.0 static const char* const KEY_LABELS_CONSTRAINT = "LabelsConstraint"; // New in Orthanc 1.12.0 + static const char* const KEY_ORDER_BY = "OrderBy"; // New in Orthanc 1.12.5 + 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 + static const char* const KEY_QUERY_METADATA = "QueryMetadata"; // New in Orthanc 1.12.5 + static const char* const KEY_RESPONSE_CONTENT = "ResponseContent"; // New in Orthanc 1.12.5 if (call.IsDocumentation()) { @@ -3283,6 +3300,20 @@ "List of strings specifying which labels to look for in the resources (new in Orthanc 1.12.0)", true) .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 (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) + .SetRequestField(KEY_QUERY_METADATA, RestApiCallDocumentation::Type_JsonObject, + "Associative array containing the filter on the values of the metadata (new in Orthanc 1.12.5)", true) + .SetRequestField(KEY_RESPONSE_CONTENT, RestApiCallDocumentation::Type_JsonListOfStrings, + "Defines the content of response for each returned resource. Allowed values are `MainDicomTags`, " + "`Metadata`, `Children`, `Parent`, `Labels`, `Status`, `IsStable`, `Attachments`. " + "(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; @@ -3333,6 +3364,12 @@ throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_REQUESTED_TAGS) + "\" must be an array"); } + else if (request.isMember(KEY_RESPONSE_CONTENT) && + request[KEY_RESPONSE_CONTENT].type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadRequest, + "Field \"" + std::string(KEY_RESPONSE_CONTENT) + "\" must be an array"); + } else if (request.isMember(KEY_LABELS) && request[KEY_LABELS].type() != Json::arrayValue) { @@ -3345,21 +3382,61 @@ throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_LABELS_CONSTRAINT) + "\" must be an array of strings"); } + else if (request.isMember(KEY_ORDER_BY) && + request[KEY_ORDER_BY].type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadRequest, + "Field \"" + std::string(KEY_ORDER_BY) + "\" must be an array"); + } + else if (request.isMember(KEY_QUERY_METADATA) && + request[KEY_QUERY_METADATA].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadRequest, + "Field \"" + std::string(KEY_QUERY_METADATA) + "\" must be an JSON object"); + } + 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) { /** * EXPERIMENTAL VERSION **/ - bool expand = false; - if (request.isMember(KEY_EXPAND)) + ResponseContentFlags responseContent = ResponseContentFlags_ID; + + if (request.isMember(KEY_RESPONSE_CONTENT)) { - expand = request[KEY_EXPAND].asBool(); + responseContent = ResponseContentFlags_Default; + + for (Json::ArrayIndex i = 0; i < request[KEY_RESPONSE_CONTENT].size(); ++i) + { + responseContent = static_cast(static_cast(responseContent) | StringToResponseContent(request[KEY_RESPONSE_CONTENT][i].asString())); + } + } + else if (request.isMember(KEY_EXPAND) && request[KEY_EXPAND].asBool()) + { + responseContent = ResponseContentFlags_ExpandTrue; } const ResourceType level = StringToResourceType(request[KEY_LEVEL].asCString()); - ResourceFinder finder(level, expand); + ResourceFinder finder(level, responseContent); finder.SetDatabaseLimits(context.GetDatabaseLimits(level)); const DicomToJsonFormat format = OrthancRestApi::GetDicomFormat(request, DicomToJsonFormat_Human); @@ -3399,30 +3476,66 @@ caseSensitive = request[KEY_CASE_SENSITIVE].asBool(); } - DatabaseLookup query; - - Json::Value::Members members = request[KEY_QUERY].getMemberNames(); - for (size_t i = 0; i < members.size(); i++) - { - if (request[KEY_QUERY][members[i]].type() != Json::stringValue) + { // DICOM Tag query + DatabaseLookup dicomTagLookup; + + Json::Value::Members members = request[KEY_QUERY].getMemberNames(); + for (size_t i = 0; i < members.size(); i++) { - throw OrthancException(ErrorCode_BadRequest, - "Tag \"" + members[i] + "\" must be associated with a string"); + if (request[KEY_QUERY][members[i]].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest, + "Tag \"" + members[i] + "\" must be associated with a string"); + } + + const std::string value = request[KEY_QUERY][members[i]].asString(); + + if (!value.empty()) + { + // An empty string corresponds to an universal constraint, + // so we ignore it. This mimics the behavior of class + // "OrthancFindRequestHandler" + dicomTagLookup.AddRestConstraint(FromDcmtkBridge::ParseTag(members[i]), + value, caseSensitive, true); + } } - const std::string value = request[KEY_QUERY][members[i]].asString(); - - if (!value.empty()) + finder.SetDatabaseLookup(dicomTagLookup); + } + + { // Metadata query + Json::Value::Members members = request[KEY_QUERY_METADATA].getMemberNames(); + for (size_t i = 0; i < members.size(); i++) { - // An empty string corresponds to an universal constraint, - // so we ignore it. This mimics the behavior of class - // "OrthancFindRequestHandler" - query.AddRestConstraint(FromDcmtkBridge::ParseTag(members[i]), - value, caseSensitive, true); + if (request[KEY_QUERY_METADATA][members[i]].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest, + "Tag \"" + members[i] + "\" must be associated with a string"); + } + MetadataType metadata = StringToMetadata(members[i]); + + const std::string value = request[KEY_QUERY_METADATA][members[i]].asString(); + + if (!value.empty()) + { + if (value.find('\\') != std::string::npos) + { + std::vector items; + Toolbox::TokenizeString(items, value, '\\'); + + finder.AddMetadataConstraint(new DatabaseMetadataConstraint(metadata, ConstraintType_List, items, caseSensitive)); + } + else if (value.find('*') != std::string::npos || value.find('?') != std::string::npos) + { + finder.AddMetadataConstraint(new DatabaseMetadataConstraint(metadata, ConstraintType_Wildcard, value, caseSensitive)); + } + else + { + finder.AddMetadataConstraint(new DatabaseMetadataConstraint(metadata, ConstraintType_Equal, value, caseSensitive)); + } + } } } - - finder.SetDatabaseLookup(query); } if (request.isMember(KEY_REQUESTED_TAGS)) @@ -3470,6 +3583,82 @@ } } + 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++) + { + if (request[KEY_ORDER_BY][i].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY) + "\" must contain objects"); + } + else + { + const Json::Value& order = request[KEY_ORDER_BY][i]; + FindRequest::OrderingDirection direction; + std::string directionString; + std::string typeString; + + if (!order.isMember(KEY_ORDER_BY_KEY) || order[KEY_ORDER_BY_KEY].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_KEY) + "\" must be a string"); + } + + if (!order.isMember(KEY_ORDER_BY_DIRECTION) || order[KEY_ORDER_BY_DIRECTION].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_DIRECTION) + "\" must be \"ASC\" or \"DESC\""); + } + + Toolbox::ToLowerCase(directionString, order[KEY_ORDER_BY_DIRECTION].asString()); + if (directionString == "asc") + { + direction = FindRequest::OrderingDirection_Ascending; + } + else if (directionString == "desc") + { + direction = FindRequest::OrderingDirection_Descending; + } + else + { + throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_DIRECTION) + "\" must be \"ASC\" or \"DESC\""); + } + + if (!order.isMember(KEY_ORDER_BY_TYPE) || order[KEY_ORDER_BY_TYPE].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_TYPE) + "\" must be \"DicomTag\" or \"Metadata\""); + } + + Toolbox::ToLowerCase(typeString, order[KEY_ORDER_BY_TYPE].asString()); + if (typeString == "dicomtag") + { + DicomTag tag = FromDcmtkBridge::ParseTag(order[KEY_ORDER_BY_KEY].asString()); + finder.AddOrdering(tag, direction); + } + else if (typeString == "metadata") + { + MetadataType metadata = StringToMetadata(order[KEY_ORDER_BY_KEY].asString()); + finder.AddOrdering(metadata, direction); + } + else + { + throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_TYPE) + "\" must be \"DicomTag\" or \"Metadata\""); + } + } + } + } + Json::Value answer; finder.Execute(answer, context, format, false /* no "Metadata" field */); call.GetOutput().AnswerJson(answer); @@ -3637,7 +3826,7 @@ * EXPERIMENTAL VERSION **/ - ResourceFinder finder(end, expand); + ResourceFinder finder(end, (expand ? ResponseContentFlags_ExpandTrue : ResponseContentFlags_ID)); finder.SetOrthancId(start, call.GetUriComponent("id", "")); finder.AddRequestedTags(requestedTags); diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/OrthancWebDav.cpp --- a/OrthancServer/Sources/OrthancWebDav.cpp Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/OrthancWebDav.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -1142,7 +1142,7 @@ Visitor visitor(resources); - ResourceFinder finder(ResourceType_Study, false /* no expand */); + ResourceFinder finder(ResourceType_Study, ResponseContentFlags_ID); finder.SetDatabaseLookup(query); finder.Execute(visitor, GetContext()); } @@ -1220,7 +1220,7 @@ Visitor visitor; - ResourceFinder finder(ResourceType_Study, false /* no expand */); + ResourceFinder finder(ResourceType_Study, ResponseContentFlags_ID); finder.SetDatabaseLookup(query); finder.Execute(visitor, context_); @@ -1605,7 +1605,7 @@ * EXPERIMENTAL VERSION **/ - ResourceFinder finder(level, false /* don't expand */); + ResourceFinder finder(level, ResponseContentFlags_ID); finder.SetDatabaseLookup(query); finder.SetRetrieveMetadata(true); @@ -1666,7 +1666,7 @@ ResourceType level, const DatabaseLookup& query) { - ResourceFinder finder(level, true /* expand */); + ResourceFinder finder(level, ResponseContentFlags_ExpandTrue); finder.SetDatabaseLookup(query); Json::Value expanded; @@ -1773,7 +1773,7 @@ /** * EXPERIMENTAL VERSION **/ - ResourceFinder finder(ResourceType_Instance, false /* no expand */); + ResourceFinder finder(ResourceType_Instance, ResponseContentFlags_ID); finder.SetDatabaseLookup(query); finder.SetRetrieveMetadata(true); finder.SetRetrieveAttachments(true); @@ -1913,7 +1913,7 @@ DicomDeleteVisitor visitor(context_, level); - ResourceFinder finder(level, false /* no expand */); + ResourceFinder finder(level, ResponseContentFlags_ID); finder.SetDatabaseLookup(query); finder.Execute(visitor, context_); return true; diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/ResourceFinder.cpp --- a/OrthancServer/Sources/ResourceFinder.cpp Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/ResourceFinder.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -210,15 +210,14 @@ void ResourceFinder::Expand(Json::Value& target, const FindResponse::Resource& resource, ServerIndex& index, - DicomToJsonFormat format, - bool includeAllMetadata) const + DicomToJsonFormat format) const { /** * This method closely follows "SerializeExpandedResource()" in * "ServerContext.cpp" from Orthanc 1.12.4. **/ - if (!expand_) + if (responseContent_ == ResponseContentFlags_ID) { throw OrthancException(ErrorCode_BadSequenceOfCalls); } @@ -233,28 +232,31 @@ target["Type"] = GetResourceTypeText(resource.GetLevel(), false, true); target["ID"] = resource.GetIdentifier(); - switch (resource.GetLevel()) + if (responseContent_ & ResponseContentFlags_Parent) { - case ResourceType_Patient: - break; + switch (resource.GetLevel()) + { + case ResourceType_Patient: + break; - case ResourceType_Study: - target["ParentPatient"] = resource.GetParentIdentifier(); - break; + case ResourceType_Study: + target["ParentPatient"] = resource.GetParentIdentifier(); + break; - case ResourceType_Series: - target["ParentStudy"] = resource.GetParentIdentifier(); - break; + case ResourceType_Series: + target["ParentStudy"] = resource.GetParentIdentifier(); + break; - case ResourceType_Instance: - target["ParentSeries"] = resource.GetParentIdentifier(); - break; + case ResourceType_Instance: + target["ParentSeries"] = resource.GetParentIdentifier(); + break; - default: - throw OrthancException(ErrorCode_InternalError); + default: + throw OrthancException(ErrorCode_InternalError); + } } - if (resource.GetLevel() != ResourceType_Instance) + if ((responseContent_ & ResponseContentFlags_Children) && (resource.GetLevel() != ResourceType_Instance)) { const std::set& children = resource.GetChildrenIdentifiers(GetChildResourceType(resource.GetLevel())); @@ -292,52 +294,65 @@ case ResourceType_Series: { - uint32_t expectedNumberOfInstances; - SeriesStatus status = GetSeriesStatus(expectedNumberOfInstances, resource); - - target["Status"] = EnumerationToString(status); - - static const char* const EXPECTED_NUMBER_OF_INSTANCES = "ExpectedNumberOfInstances"; + if ((responseContent_ & ResponseContentFlags_Status) || (responseContent_ & ResponseContentFlags_MetadataLegacy) ) + { + uint32_t expectedNumberOfInstances; + SeriesStatus status = GetSeriesStatus(expectedNumberOfInstances, resource); + + if (responseContent_ & ResponseContentFlags_Status ) + { + target["Status"] = EnumerationToString(status); + } - if (status == SeriesStatus_Unknown) - { - target[EXPECTED_NUMBER_OF_INSTANCES] = Json::nullValue; + if (responseContent_ & ResponseContentFlags_MetadataLegacy) + { + static const char* const EXPECTED_NUMBER_OF_INSTANCES = "ExpectedNumberOfInstances"; + + if (status == SeriesStatus_Unknown) + { + target[EXPECTED_NUMBER_OF_INSTANCES] = Json::nullValue; + } + else + { + target[EXPECTED_NUMBER_OF_INSTANCES] = expectedNumberOfInstances; + } + } } - else - { - target[EXPECTED_NUMBER_OF_INSTANCES] = expectedNumberOfInstances; - } - break; } case ResourceType_Instance: { - FileInfo info; - if (resource.LookupAttachment(info, FileContentType_Dicom)) + if (responseContent_ & ResponseContentFlags_AttachmentsLegacy) { - target["FileSize"] = static_cast(info.GetUncompressedSize()); - target["FileUuid"] = info.GetUuid(); - } - else - { - throw OrthancException(ErrorCode_InternalError); + FileInfo info; + if (resource.LookupAttachment(info, FileContentType_Dicom)) + { + target["FileSize"] = static_cast(info.GetUncompressedSize()); + target["FileUuid"] = info.GetUuid(); + } + else + { + throw OrthancException(ErrorCode_InternalError); + } } - static const char* const INDEX_IN_SERIES = "IndexInSeries"; - - std::string s; - uint32_t indexInSeries; - if (resource.LookupMetadata(s, ResourceType_Instance, MetadataType_Instance_IndexInSeries) && - SerializationToolbox::ParseUnsignedInteger32(indexInSeries, s)) + if (responseContent_ & ResponseContentFlags_MetadataLegacy) { - target[INDEX_IN_SERIES] = indexInSeries; + static const char* const INDEX_IN_SERIES = "IndexInSeries"; + + std::string s; + uint32_t indexInSeries; + if (resource.LookupMetadata(s, ResourceType_Instance, MetadataType_Instance_IndexInSeries) && + SerializationToolbox::ParseUnsignedInteger32(indexInSeries, s)) + { + target[INDEX_IN_SERIES] = indexInSeries; + } + else + { + target[INDEX_IN_SERIES] = Json::nullValue; + } } - else - { - target[INDEX_IN_SERIES] = Json::nullValue; - } - break; } @@ -346,28 +361,40 @@ } std::string s; - if (resource.LookupMetadata(s, resource.GetLevel(), MetadataType_AnonymizedFrom)) + if (responseContent_ & ResponseContentFlags_MetadataLegacy) { - target["AnonymizedFrom"] = s; - } - - if (resource.LookupMetadata(s, resource.GetLevel(), MetadataType_ModifiedFrom)) - { - target["ModifiedFrom"] = s; - } + if (resource.LookupMetadata(s, resource.GetLevel(), MetadataType_AnonymizedFrom)) + { + target["AnonymizedFrom"] = s; + } - if (resource.GetLevel() == ResourceType_Patient || - resource.GetLevel() == ResourceType_Study || - resource.GetLevel() == ResourceType_Series) - { - target["IsStable"] = !index.IsUnstableResource(resource.GetLevel(), resource.GetInternalId()); + if (resource.LookupMetadata(s, resource.GetLevel(), MetadataType_ModifiedFrom)) + { + target["ModifiedFrom"] = s; + } - if (resource.LookupMetadata(s, resource.GetLevel(), MetadataType_LastUpdate)) + if (resource.GetLevel() == ResourceType_Patient || + resource.GetLevel() == ResourceType_Study || + resource.GetLevel() == ResourceType_Series) { - target["LastUpdate"] = s; + if (resource.LookupMetadata(s, resource.GetLevel(), MetadataType_LastUpdate)) + { + target["LastUpdate"] = s; + } } } + if (responseContent_ & ResponseContentFlags_IsStable) + { + if (resource.GetLevel() == ResourceType_Patient || + resource.GetLevel() == ResourceType_Study || + resource.GetLevel() == ResourceType_Series) + { + target["IsStable"] = !index.IsUnstableResource(resource.GetLevel(), resource.GetInternalId()); + } + } + + if (responseContent_ & ResponseContentFlags_MainDicomTags) { DicomMap allMainDicomTags; resource.GetMainDicomTags(allMainDicomTags, resource.GetLevel()); @@ -396,6 +423,7 @@ } } + if (responseContent_ & ResponseContentFlags_Labels) { Json::Value labels = Json::arrayValue; @@ -408,7 +436,7 @@ target["Labels"] = labels; } - if (includeAllMetadata) // new in Orthanc 1.12.4 + if (responseContent_ & ResponseContentFlags_Metadata) // new in Orthanc 1.12.4 { const std::map& m = resource.GetMetadata(resource.GetLevel()); @@ -421,6 +449,26 @@ target["Metadata"] = metadata; } + + if (responseContent_ & ResponseContentFlags_Attachments) // new in Orthanc 1.12.5 + { + const std::map& attachments = resource.GetAttachments(); + + target["Attachments"] = Json::arrayValue; + + for (std::map::const_iterator it = attachments.begin(); it != attachments.end(); ++it) + { + Json::Value attachment = Json::objectValue; + attachment["Uuid"] = it->second.GetUuid(); + attachment["ContentType"] = it->second.GetContentType(); + attachment["UncompressedSize"] = Json::Value::UInt64(it->second.GetUncompressedSize()); + attachment["CompressedSize"] = Json::Value::UInt64(it->second.GetCompressedSize()); + attachment["UncompressedMD5"] = it->second.GetUncompressedMD5(); + attachment["CompressedMD5"] = it->second.GetCompressedMD5(); + + target["Attachments"].append(attachment); + } + } } @@ -473,7 +521,7 @@ ResourceFinder::ResourceFinder(ResourceType level, - bool expand) : + ResponseContentFlags responseContent) : request_(level), databaseLimits_(0), isSimpleLookup_(true), @@ -482,7 +530,7 @@ hasLimitsCount_(false), limitsSince_(0), limitsCount_(0), - expand_(expand), + responseContent_(responseContent), allowStorageAccess_(true), isWarning002Enabled_(false), isWarning004Enabled_(false), @@ -495,40 +543,43 @@ isWarning005Enabled_ = lock.GetConfiguration().IsWarningEnabled(Warnings_005_RequestingTagFromLowerResourceLevel); } - UpdateRequestLimits(); - if (expand) - { - request_.SetRetrieveMainDicomTags(true); - request_.SetRetrieveMetadata(true); - request_.SetRetrieveLabels(true); + request_.SetRetrieveMainDicomTags(responseContent_ & ResponseContentFlags_MainDicomTags); + request_.SetRetrieveMetadata((responseContent_ & ResponseContentFlags_Metadata) || (responseContent_ & ResponseContentFlags_MetadataLegacy)); + request_.SetRetrieveLabels(responseContent_ & ResponseContentFlags_Labels); - switch (level) - { - case ResourceType_Patient: - request_.GetChildrenSpecification(ResourceType_Study).SetRetrieveIdentifiers(true); - break; + switch (level) + { + case ResourceType_Patient: + request_.GetChildrenSpecification(ResourceType_Study).SetRetrieveIdentifiers(responseContent_ & ResponseContentFlags_Children); + request_.SetRetrieveAttachments(responseContent_ & ResponseContentFlags_Attachments); + break; + + case ResourceType_Study: + request_.GetChildrenSpecification(ResourceType_Series).SetRetrieveIdentifiers(responseContent_ & ResponseContentFlags_Children); + request_.SetRetrieveParentIdentifier(responseContent_ & ResponseContentFlags_Parent); + request_.SetRetrieveAttachments(responseContent_ & ResponseContentFlags_Attachments); + break; - case ResourceType_Study: - request_.GetChildrenSpecification(ResourceType_Series).SetRetrieveIdentifiers(true); - request_.SetRetrieveParentIdentifier(true); - break; - - case ResourceType_Series: + case ResourceType_Series: + if (responseContent_ & ResponseContentFlags_Status) + { request_.GetChildrenSpecification(ResourceType_Instance).AddMetadata(MetadataType_Instance_IndexInSeries); // required for the SeriesStatus - request_.GetChildrenSpecification(ResourceType_Instance).SetRetrieveIdentifiers(true); - request_.SetRetrieveParentIdentifier(true); - break; + } + request_.GetChildrenSpecification(ResourceType_Instance).SetRetrieveIdentifiers(responseContent_ & ResponseContentFlags_Children); + request_.SetRetrieveParentIdentifier(responseContent_ & ResponseContentFlags_Parent); + request_.SetRetrieveAttachments(responseContent_ & ResponseContentFlags_Attachments); + break; - case ResourceType_Instance: - request_.SetRetrieveAttachments(true); // for FileSize & FileUuid - request_.SetRetrieveParentIdentifier(true); - break; + case ResourceType_Instance: + request_.SetRetrieveAttachments((responseContent_ & ResponseContentFlags_AttachmentsLegacy) // for FileSize & FileUuid + || (responseContent_ & ResponseContentFlags_Attachments)); + request_.SetRetrieveParentIdentifier(true); + break; - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); } } @@ -601,7 +652,7 @@ for (size_t i = 0; i < request_.GetDicomTagConstraints().GetSize(); i++) { - const DatabaseConstraint& constraint = request_.GetDicomTagConstraints().GetConstraint(i); + const DatabaseDicomTagConstraint& constraint = request_.GetDicomTagConstraints().GetConstraint(i); if (constraint.GetLevel() == request_.GetLevel()) { request_.SetRetrieveMainDicomTags(true); @@ -1126,10 +1177,10 @@ virtual void Apply(const FindResponse::Resource& resource, const DicomMap& requestedTags) ORTHANC_OVERRIDE { - if (that_.expand_) + if (that_.responseContent_ != ResponseContentFlags_ID) { Json::Value item; - that_.Expand(item, resource, index_, format_, includeAllMetadata_); + that_.Expand(item, resource, index_, format_); if (hasRequestedTags_) { diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/ResourceFinder.h --- a/OrthancServer/Sources/ResourceFinder.h Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/ResourceFinder.h Wed Oct 09 11:06:20 2024 +0200 @@ -65,7 +65,7 @@ bool hasLimitsCount_; uint64_t limitsSince_; uint64_t limitsCount_; - bool expand_; + ResponseContentFlags responseContent_; bool allowStorageAccess_; std::set requestedTags_; std::set requestedComputedTags_; @@ -103,7 +103,7 @@ public: ResourceFinder(ResourceType level, - bool expand); + ResponseContentFlags responseContent); void SetDatabaseLimits(uint64_t limits); @@ -133,6 +133,23 @@ void AddRequestedTags(const std::set& tags); + void AddOrdering(const DicomTag& tag, + FindRequest::OrderingDirection direction) + { + request_.AddOrdering(tag, direction); + } + + void AddOrdering(MetadataType metadataType, + FindRequest::OrderingDirection direction) + { + request_.AddOrdering(metadataType, direction); + } + + void AddMetadataConstraint(DatabaseMetadataConstraint* constraint) + { + request_.AddMetadataConstraint(constraint); + } + void SetLabels(const std::set& labels) { request_.SetLabels(labels); @@ -167,8 +184,7 @@ void Expand(Json::Value& target, const FindResponse::Resource& resource, ServerIndex& index, - DicomToJsonFormat format, - bool includeAllMetadata /* Same as: ExpandResourceFlags_IncludeAllMetadata */) const; + DicomToJsonFormat format) const; void Execute(IVisitor& visitor, ServerContext& context) const; diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Search/DatabaseConstraint.cpp --- a/OrthancServer/Sources/Search/DatabaseConstraint.cpp Wed Oct 02 11:41:01 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,112 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2023 Osimis S.A., Belgium - * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium - * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "DatabaseConstraint.h" - -#include "../../../OrthancFramework/Sources/OrthancException.h" - -#if ORTHANC_ENABLE_PLUGINS == 1 -# include "../../Plugins/Engine/PluginsEnumerations.h" -#endif - -#include -#include - - -namespace Orthanc -{ - DatabaseConstraint::DatabaseConstraint(ResourceType level, - const DicomTag& tag, - bool isIdentifier, - ConstraintType type, - const std::vector& values, - bool caseSensitive, - bool mandatory) : - level_(level), - tag_(tag), - isIdentifier_(isIdentifier), - constraintType_(type), - values_(values), - caseSensitive_(caseSensitive), - mandatory_(mandatory) - { - if (type != ConstraintType_List && - values_.size() != 1) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const std::string& DatabaseConstraint::GetValue(size_t index) const - { - if (index >= values_.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - return values_[index]; - } - } - - - const std::string& DatabaseConstraint::GetSingleValue() const - { - if (values_.size() != 1) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - return values_[0]; - } - } - - -#if ORTHANC_ENABLE_PLUGINS == 1 - void DatabaseConstraint::EncodeForPlugins(OrthancPluginDatabaseConstraint& constraint, - std::vector& tmpValues) const - { - memset(&constraint, 0, sizeof(constraint)); - - tmpValues.resize(values_.size()); - - for (size_t i = 0; i < values_.size(); i++) - { - tmpValues[i] = values_[i].c_str(); - } - - constraint.level = Plugins::Convert(level_); - constraint.tagGroup = tag_.GetGroup(); - constraint.tagElement = tag_.GetElement(); - constraint.isIdentifierTag = isIdentifier_; - constraint.isCaseSensitive = caseSensitive_; - constraint.isMandatory = mandatory_; - constraint.type = Plugins::Convert(constraintType_); - constraint.valuesCount = values_.size(); - constraint.values = (tmpValues.empty() ? NULL : &tmpValues[0]); - } -#endif -} diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Search/DatabaseConstraint.h --- a/OrthancServer/Sources/Search/DatabaseConstraint.h Wed Oct 02 11:41:01 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,101 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2023 Osimis S.A., Belgium - * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium - * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../../../OrthancFramework/Sources/DicomFormat/DicomMap.h" -#include "../ServerEnumerations.h" - -#if ORTHANC_ENABLE_PLUGINS == 1 -# include "../../Plugins/Include/orthanc/OrthancCDatabasePlugin.h" -#endif - -namespace Orthanc -{ - class DatabaseConstraint : public boost::noncopyable - { - private: - ResourceType level_; - DicomTag tag_; - bool isIdentifier_; - ConstraintType constraintType_; - std::vector values_; - bool caseSensitive_; - bool mandatory_; - - public: - DatabaseConstraint(ResourceType level, - const DicomTag& tag, - bool isIdentifier, - ConstraintType type, - const std::vector& values, - bool caseSensitive, - bool mandatory); - - ResourceType GetLevel() const - { - return level_; - } - - const DicomTag& GetTag() const - { - return tag_; - } - - bool IsIdentifier() const - { - return isIdentifier_; - } - - ConstraintType GetConstraintType() const - { - return constraintType_; - } - - size_t GetValuesCount() const - { - return values_.size(); - } - - const std::string& GetValue(size_t index) const; - - const std::string& GetSingleValue() const; - - bool IsCaseSensitive() const - { - return caseSensitive_; - } - - bool IsMandatory() const - { - return mandatory_; - } - - bool IsMatch(const DicomMap& dicom) const; - -#if ORTHANC_ENABLE_PLUGINS == 1 - void EncodeForPlugins(OrthancPluginDatabaseConstraint& constraint, - std::vector& tmpValues) const; -#endif - }; -} diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Search/DatabaseConstraints.cpp --- a/OrthancServer/Sources/Search/DatabaseConstraints.cpp Wed Oct 02 11:41:01 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,132 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2023 Osimis S.A., Belgium - * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium - * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "DatabaseConstraints.h" - -#include "../../../OrthancFramework/Sources/OrthancException.h" - -#include -#include - - -namespace Orthanc -{ - void DatabaseConstraints::Clear() - { - for (size_t i = 0; i < constraints_.size(); i++) - { - assert(constraints_[i] != NULL); - delete constraints_[i]; - } - - constraints_.clear(); - } - - - void DatabaseConstraints::AddConstraint(DatabaseConstraint* constraint) - { - if (constraint == NULL) - { - throw OrthancException(ErrorCode_NullPointer); - } - else - { - constraints_.push_back(constraint); - } - } - - - const DatabaseConstraint& DatabaseConstraints::GetConstraint(size_t index) const - { - if (index >= constraints_.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - assert(constraints_[index] != NULL); - return *constraints_[index]; - } - } - - - std::string DatabaseConstraints::Format() const - { - std::string s; - - for (size_t i = 0; i < constraints_.size(); i++) - { - assert(constraints_[i] != NULL); - const DatabaseConstraint& constraint = *constraints_[i]; - s += "Constraint " + boost::lexical_cast(i) + " at " + EnumerationToString(constraint.GetLevel()) + - ": " + constraint.GetTag().Format(); - - switch (constraint.GetConstraintType()) - { - case ConstraintType_Equal: - s += " == " + constraint.GetSingleValue(); - break; - - case ConstraintType_SmallerOrEqual: - s += " <= " + constraint.GetSingleValue(); - break; - - case ConstraintType_GreaterOrEqual: - s += " >= " + constraint.GetSingleValue(); - break; - - case ConstraintType_Wildcard: - s += " ~~ " + constraint.GetSingleValue(); - break; - - case ConstraintType_List: - { - s += " in [ "; - bool first = true; - for (size_t j = 0; j < constraint.GetValuesCount(); j++) - { - if (first) - { - first = false; - } - else - { - s += ", "; - } - s += constraint.GetValue(j); - } - s += "]"; - break; - } - - default: - throw OrthancException(ErrorCode_InternalError); - } - - s += "\n"; - } - - return s; - } -} diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Search/DatabaseConstraints.h --- a/OrthancServer/Sources/Search/DatabaseConstraints.h Wed Oct 02 11:41:01 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2023 Osimis S.A., Belgium - * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium - * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "DatabaseConstraint.h" - -#include - -namespace Orthanc -{ - class DatabaseConstraints : public boost::noncopyable - { - private: - std::deque constraints_; - - public: - ~DatabaseConstraints() - { - Clear(); - } - - void Clear(); - - void AddConstraint(DatabaseConstraint* constraint); // Takes ownership - - bool IsEmpty() const - { - return constraints_.empty(); - } - - size_t GetSize() const - { - return constraints_.size(); - } - - const DatabaseConstraint& GetConstraint(size_t index) const; - - std::string Format() const; - }; -} diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Search/DatabaseDicomTagConstraint.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Sources/Search/DatabaseDicomTagConstraint.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -0,0 +1,112 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "DatabaseDicomTagConstraint.h" + +#include "../../../OrthancFramework/Sources/OrthancException.h" + +#if ORTHANC_ENABLE_PLUGINS == 1 +# include "../../Plugins/Engine/PluginsEnumerations.h" +#endif + +#include +#include + + +namespace Orthanc +{ + DatabaseDicomTagConstraint::DatabaseDicomTagConstraint(ResourceType level, + const DicomTag& tag, + bool isIdentifier, + ConstraintType type, + const std::vector& values, + bool caseSensitive, + bool mandatory) : + level_(level), + tag_(tag), + isIdentifier_(isIdentifier), + constraintType_(type), + values_(values), + caseSensitive_(caseSensitive), + mandatory_(mandatory) + { + if (type != ConstraintType_List && + values_.size() != 1) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + const std::string& DatabaseDicomTagConstraint::GetValue(size_t index) const + { + if (index >= values_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + return values_[index]; + } + } + + + const std::string& DatabaseDicomTagConstraint::GetSingleValue() const + { + if (values_.size() != 1) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return values_[0]; + } + } + + +#if ORTHANC_ENABLE_PLUGINS == 1 + void DatabaseDicomTagConstraint::EncodeForPlugins(OrthancPluginDatabaseConstraint& constraint, + std::vector& tmpValues) const + { + memset(&constraint, 0, sizeof(constraint)); + + tmpValues.resize(values_.size()); + + for (size_t i = 0; i < values_.size(); i++) + { + tmpValues[i] = values_[i].c_str(); + } + + constraint.level = Plugins::Convert(level_); + constraint.tagGroup = tag_.GetGroup(); + constraint.tagElement = tag_.GetElement(); + constraint.isIdentifierTag = isIdentifier_; + constraint.isCaseSensitive = caseSensitive_; + constraint.isMandatory = mandatory_; + constraint.type = Plugins::Convert(constraintType_); + constraint.valuesCount = values_.size(); + constraint.values = (tmpValues.empty() ? NULL : &tmpValues[0]); + } +#endif +} diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Search/DatabaseDicomTagConstraint.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Sources/Search/DatabaseDicomTagConstraint.h Wed Oct 09 11:06:20 2024 +0200 @@ -0,0 +1,101 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../../../OrthancFramework/Sources/DicomFormat/DicomMap.h" +#include "../ServerEnumerations.h" +#include "IDatabaseConstraint.h" + +#if ORTHANC_ENABLE_PLUGINS == 1 +# include "../../Plugins/Include/orthanc/OrthancCDatabasePlugin.h" +#endif + +namespace Orthanc +{ + class DatabaseDicomTagConstraint : public IDatabaseConstraint + { + private: + ResourceType level_; + DicomTag tag_; + bool isIdentifier_; + ConstraintType constraintType_; + std::vector values_; + bool caseSensitive_; + bool mandatory_; + + public: + DatabaseDicomTagConstraint(ResourceType level, + const DicomTag& tag, + bool isIdentifier, + ConstraintType type, + const std::vector& values, + bool caseSensitive, + bool mandatory); + + ResourceType GetLevel() const + { + return level_; + } + + const DicomTag& GetTag() const + { + return tag_; + } + + bool IsIdentifier() const + { + return isIdentifier_; + } + + virtual ConstraintType GetConstraintType() const ORTHANC_OVERRIDE + { + return constraintType_; + } + + virtual size_t GetValuesCount() const ORTHANC_OVERRIDE + { + return values_.size(); + } + + virtual const std::string& GetValue(size_t index) const ORTHANC_OVERRIDE; + + virtual const std::string& GetSingleValue() const ORTHANC_OVERRIDE; + + virtual bool IsCaseSensitive() const ORTHANC_OVERRIDE + { + return caseSensitive_; + } + + virtual bool IsMandatory() const ORTHANC_OVERRIDE + { + return mandatory_; + } + + +#if ORTHANC_ENABLE_PLUGINS == 1 + void EncodeForPlugins(OrthancPluginDatabaseConstraint& constraint, + std::vector& tmpValues) const; +#endif + }; +} diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Search/DatabaseDicomTagConstraints.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Sources/Search/DatabaseDicomTagConstraints.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -0,0 +1,132 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "DatabaseDicomTagConstraints.h" + +#include "../../../OrthancFramework/Sources/OrthancException.h" + +#include +#include + + +namespace Orthanc +{ + void DatabaseDicomTagConstraints::Clear() + { + for (size_t i = 0; i < constraints_.size(); i++) + { + assert(constraints_[i] != NULL); + delete constraints_[i]; + } + + constraints_.clear(); + } + + + void DatabaseDicomTagConstraints::AddConstraint(DatabaseDicomTagConstraint* constraint) + { + if (constraint == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else + { + constraints_.push_back(constraint); + } + } + + + const DatabaseDicomTagConstraint& DatabaseDicomTagConstraints::GetConstraint(size_t index) const + { + if (index >= constraints_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + assert(constraints_[index] != NULL); + return *constraints_[index]; + } + } + + + std::string DatabaseDicomTagConstraints::Format() const + { + std::string s; + + for (size_t i = 0; i < constraints_.size(); i++) + { + assert(constraints_[i] != NULL); + const DatabaseDicomTagConstraint& constraint = *constraints_[i]; + s += "Constraint " + boost::lexical_cast(i) + " at " + EnumerationToString(constraint.GetLevel()) + + ": " + constraint.GetTag().Format(); + + switch (constraint.GetConstraintType()) + { + case ConstraintType_Equal: + s += " == " + constraint.GetSingleValue(); + break; + + case ConstraintType_SmallerOrEqual: + s += " <= " + constraint.GetSingleValue(); + break; + + case ConstraintType_GreaterOrEqual: + s += " >= " + constraint.GetSingleValue(); + break; + + case ConstraintType_Wildcard: + s += " ~~ " + constraint.GetSingleValue(); + break; + + case ConstraintType_List: + { + s += " in [ "; + bool first = true; + for (size_t j = 0; j < constraint.GetValuesCount(); j++) + { + if (first) + { + first = false; + } + else + { + s += ", "; + } + s += constraint.GetValue(j); + } + s += "]"; + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + + s += "\n"; + } + + return s; + } +} diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Search/DatabaseDicomTagConstraints.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Sources/Search/DatabaseDicomTagConstraints.h Wed Oct 09 11:06:20 2024 +0200 @@ -0,0 +1,61 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "DatabaseDicomTagConstraint.h" + +#include + +namespace Orthanc +{ + class DatabaseDicomTagConstraints : public boost::noncopyable + { + private: + std::deque constraints_; + + public: + ~DatabaseDicomTagConstraints() + { + Clear(); + } + + void Clear(); + + void AddConstraint(DatabaseDicomTagConstraint* constraint); // Takes ownership + + bool IsEmpty() const + { + return constraints_.empty(); + } + + size_t GetSize() const + { + return constraints_.size(); + } + + const DatabaseDicomTagConstraint& GetConstraint(size_t index) const; + + std::string Format() const; + }; +} diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Search/DatabaseMetadataConstraint.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Sources/Search/DatabaseMetadataConstraint.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -0,0 +1,93 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "DatabaseMetadataConstraint.h" + +#include "../../../OrthancFramework/Sources/OrthancException.h" + +#include +#include + + +namespace Orthanc +{ + DatabaseMetadataConstraint::DatabaseMetadataConstraint(MetadataType metadata, + ConstraintType type, + const std::vector& values, + bool caseSensitive) : + metadata_(metadata), + constraintType_(type), + values_(values), + caseSensitive_(caseSensitive) + { + if (type != ConstraintType_List && + values_.size() != 1) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + DatabaseMetadataConstraint::DatabaseMetadataConstraint(MetadataType metadata, + ConstraintType type, + const std::string& value, + bool caseSensitive) : + metadata_(metadata), + constraintType_(type), + caseSensitive_(caseSensitive) + { + if (type == ConstraintType_List) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + values_.push_back(value); + } + + const std::string& DatabaseMetadataConstraint::GetValue(size_t index) const + { + if (index >= values_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + return values_[index]; + } + } + + + const std::string& DatabaseMetadataConstraint::GetSingleValue() const + { + if (values_.size() != 1) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return values_[0]; + } + } + +} diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Search/DatabaseMetadataConstraint.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Sources/Search/DatabaseMetadataConstraint.h Wed Oct 09 11:06:20 2024 +0200 @@ -0,0 +1,81 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../../../OrthancFramework/Sources/DicomFormat/DicomMap.h" +#include "../ServerEnumerations.h" +#include "IDatabaseConstraint.h" + +namespace Orthanc +{ + class DatabaseMetadataConstraint : public IDatabaseConstraint + { + private: + MetadataType metadata_; + ConstraintType constraintType_; + std::vector values_; + bool caseSensitive_; + + public: + DatabaseMetadataConstraint(MetadataType metadata, + ConstraintType type, + const std::string& value, + bool caseSensitive); + + DatabaseMetadataConstraint(MetadataType metadata, + ConstraintType type, + const std::vector& values, + bool caseSensitive); + + const MetadataType& GetMetadata() const + { + return metadata_; + } + + virtual ConstraintType GetConstraintType() const ORTHANC_OVERRIDE + { + return constraintType_; + } + + virtual size_t GetValuesCount() const ORTHANC_OVERRIDE + { + return values_.size(); + } + + virtual const std::string& GetValue(size_t index) const ORTHANC_OVERRIDE; + + virtual const std::string& GetSingleValue() const ORTHANC_OVERRIDE; + + virtual bool IsCaseSensitive() const ORTHANC_OVERRIDE + { + return caseSensitive_; + } + + virtual bool IsMandatory() const ORTHANC_OVERRIDE + { + return true; + } + + }; +} diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Search/DicomTagConstraint.cpp --- a/OrthancServer/Sources/Search/DicomTagConstraint.cpp Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/Search/DicomTagConstraint.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -30,7 +30,7 @@ #include "../../../OrthancFramework/Sources/OrthancException.h" #include "../../../OrthancFramework/Sources/Toolbox.h" -#include "DatabaseConstraint.h" +#include "DatabaseDicomTagConstraint.h" #include @@ -154,7 +154,7 @@ } - DicomTagConstraint::DicomTagConstraint(const DatabaseConstraint& constraint) : + DicomTagConstraint::DicomTagConstraint(const DatabaseDicomTagConstraint& constraint) : tag_(constraint.GetTag()), constraintType_(constraint.GetConstraintType()), caseSensitive_(constraint.IsCaseSensitive()), @@ -369,9 +369,9 @@ } - DatabaseConstraint* DicomTagConstraint::ConvertToDatabaseConstraint(bool& isIdentical, - ResourceType level, - DicomTagType tagType) const + DatabaseDicomTagConstraint* DicomTagConstraint::ConvertToDatabaseConstraint(bool& isIdentical, + ResourceType level, + DicomTagType tagType) const { bool isIdentifier, caseSensitive; @@ -415,7 +415,7 @@ } } - return new DatabaseConstraint(level, tag_, isIdentifier, constraintType_, - values, caseSensitive, mandatory_); + return new DatabaseDicomTagConstraint(level, tag_, isIdentifier, constraintType_, + values, caseSensitive, mandatory_); } } diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Search/DicomTagConstraint.h --- a/OrthancServer/Sources/Search/DicomTagConstraint.h Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/Search/DicomTagConstraint.h Wed Oct 09 11:06:20 2024 +0200 @@ -25,7 +25,7 @@ #include "../ServerEnumerations.h" #include "../../../OrthancFramework/Sources/DicomFormat/DicomMap.h" -#include "DatabaseConstraint.h" +#include "DatabaseDicomTagConstraint.h" #include @@ -62,7 +62,7 @@ explicit DicomTagConstraint(const DicomTagConstraint& other); - explicit DicomTagConstraint(const DatabaseConstraint& constraint); + explicit DicomTagConstraint(const DatabaseDicomTagConstraint& constraint); const DicomTag& GetTag() const { @@ -109,8 +109,8 @@ std::string Format() const; - DatabaseConstraint* ConvertToDatabaseConstraint(bool& isIdentical /* out */, - ResourceType level, - DicomTagType tagType) const; + DatabaseDicomTagConstraint* ConvertToDatabaseConstraint(bool& isIdentical /* out */, + ResourceType level, + DicomTagType tagType) const; }; } diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Search/IDatabaseConstraint.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Sources/Search/IDatabaseConstraint.h Wed Oct 09 11:06:20 2024 +0200 @@ -0,0 +1,50 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../ServerEnumerations.h" +#include + +namespace Orthanc +{ + class IDatabaseConstraint : public boost::noncopyable + { + public: + virtual ~IDatabaseConstraint() + { + } + + virtual ConstraintType GetConstraintType() const = 0; + + virtual size_t GetValuesCount() const = 0; + + virtual const std::string& GetValue(size_t index) const = 0; + + virtual const std::string& GetSingleValue() const = 0; + + virtual bool IsCaseSensitive() const = 0; + + virtual bool IsMandatory() const = 0; + }; +} diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Search/ISqlLookupFormatter.cpp --- a/OrthancServer/Sources/Search/ISqlLookupFormatter.cpp Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/Search/ISqlLookupFormatter.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -27,7 +27,8 @@ #include "../../../OrthancFramework/Sources/OrthancException.h" #include "../../../OrthancFramework/Sources/Toolbox.h" #include "../Database/FindRequest.h" -#include "DatabaseConstraint.h" +#include "DatabaseDicomTagConstraint.h" +#include "../Database/MainDicomTagsRegistry.h" #include #include @@ -56,11 +57,32 @@ 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, - const DatabaseConstraint& constraint, + const IDatabaseConstraint& constraint, size_t index, bool escapeBrackets) { @@ -232,7 +254,7 @@ static void FormatJoin(std::string& target, - const DatabaseConstraint& constraint, + const DatabaseDicomTagConstraint& constraint, size_t index) { std::string tag = "t" + boost::lexical_cast(index); @@ -262,6 +284,99 @@ boost::lexical_cast(constraint.GetTag().GetElement())); } + static void FormatJoin(std::string& target, + const DatabaseMetadataConstraint& constraint, + ResourceType level, + size_t index) + { + std::string tag = "t" + boost::lexical_cast(index); + + if (constraint.IsMandatory()) + { + target = " INNER JOIN "; + } + else + { + target = " LEFT JOIN "; + } + + target += "Metadata "; + + target += tag + " ON " + tag + ".id = " + FormatLevel(level) + + ".internalId AND " + tag + ".type = " + + boost::lexical_cast(constraint.GetMetadata()); + } + + + static void FormatJoinForOrdering(std::string& target, + const DicomTag& tag, + size_t index, + ResourceType requestLevel) + { + std::string orderArg = "order" + boost::lexical_cast(index); + + target.clear(); + + ResourceType tagLevel; + DicomTagType tagType; + MainDicomTagsRegistry registry; + + registry.LookupTag(tagLevel, tagType, tag); + + if (tagLevel == ResourceType_Patient && requestLevel == ResourceType_Study) + { // Patient tags are copied at study level + tagLevel = ResourceType_Study; + } + + std::string tagTable; + if (tagType == DicomTagType_Identifier) + { + tagTable = "DicomIdentifiers "; + } + else + { + tagTable = "MainDicomTags "; + } + + std::string tagFilter = orderArg + ".tagGroup = " + boost::lexical_cast(tag.GetGroup()) + " AND " + orderArg + ".tagElement = " + boost::lexical_cast(tag.GetElement()); + + if (tagLevel == requestLevel) + { + target = " LEFT JOIN " + tagTable + " " + orderArg + " ON " + orderArg + ".id = " + FormatLevel(requestLevel) + + ".internalId AND " + tagFilter; + } + else if (static_cast(requestLevel) - static_cast(tagLevel) == 1) + { + target = " INNER JOIN Resources " + orderArg + "parent ON " + orderArg + "parent.internalId = " + FormatLevel(requestLevel) + ".parentId " + " LEFT JOIN " + tagTable + " " + orderArg + " ON " + orderArg + ".id = " + orderArg + "parent.internalId AND " + tagFilter; + } + else if (static_cast(requestLevel) - static_cast(tagLevel) == 2) + { + target = " INNER JOIN Resources " + orderArg + "parent ON " + orderArg + "parent.internalId = " + FormatLevel(requestLevel) + ".parentId " + " INNER JOIN Resources " + orderArg + "grandparent ON " + orderArg + "grandparent.internalId = " + orderArg + "parent.parentId " + " LEFT JOIN " + tagTable + " " + orderArg + " ON " + orderArg + ".id = " + orderArg + "grandparent.internalId AND " + tagFilter; + } + else if (static_cast(requestLevel) - static_cast(tagLevel) == 3) + { + target = " INNER JOIN Resources " + orderArg + "parent ON " + orderArg + "parent.internalId = " + FormatLevel(requestLevel) + ".parentId " + " INNER JOIN Resources " + orderArg + "grandparent ON " + orderArg + "grandparent.internalId = " + orderArg + "parent.parentId " + " INNER JOIN Resources " + orderArg + "grandgrandparent ON " + orderArg + "grandgrandparent.internalId = " + orderArg + "grandparent.parentId " + " LEFT JOIN " + tagTable + " " + orderArg + " ON " + orderArg + ".id = " + orderArg + "grandgrandparent.internalId AND " + tagFilter; + } + } + + static void FormatJoinForOrdering(std::string& target, + const MetadataType& metadata, + size_t index, + ResourceType requestLevel) + { + std::string arg = "order" + boost::lexical_cast(index); + + + target = " INNER JOIN Metadata " + arg + " ON " + arg + ".id = " + FormatLevel(requestLevel) + + ".internalId AND " + arg + ".type = " + + boost::lexical_cast(metadata); + } static std::string Join(const std::list& values, const std::string& prefix, @@ -296,7 +411,7 @@ static bool FormatComparison2(std::string& target, ISqlLookupFormatter& formatter, - const DatabaseConstraint& constraint, + const DatabaseDicomTagConstraint& constraint, bool escapeBrackets) { std::string comparison; @@ -466,7 +581,7 @@ void ISqlLookupFormatter::GetLookupLevels(ResourceType& lowerLevel, ResourceType& upperLevel, const ResourceType& queryLevel, - const DatabaseConstraints& lookup) + const DatabaseDicomTagConstraints& lookup) { assert(ResourceType_Patient < ResourceType_Study && ResourceType_Study < ResourceType_Series && @@ -494,12 +609,13 @@ void ISqlLookupFormatter::Apply(std::string& sql, ISqlLookupFormatter& formatter, - const DatabaseConstraints& lookup, + const DatabaseDicomTagConstraints& lookup, ResourceType queryLevel, const std::set& labels, LabelsConstraint labelsConstraint, size_t limit) { + // get the limit levels of the DICOM Tags lookup ResourceType lowerLevel, upperLevel; GetLookupLevels(lowerLevel, upperLevel, queryLevel, lookup); @@ -514,7 +630,7 @@ for (size_t i = 0; i < lookup.GetSize(); i++) { - const DatabaseConstraint& constraint = lookup.GetConstraint(i); + const DatabaseDicomTagConstraint& constraint = lookup.GetConstraint(i); std::string comparison; @@ -619,8 +735,64 @@ assert(upperLevel <= queryLevel && queryLevel <= lowerLevel); - std::string ordering = "row_number() over (order by " + strQueryLevel + ".publicId) as rowNumber"; // we need a default ordering in order to make default queries repeatable when using since&limit + std::string ordering; + std::string orderingJoins; + + if (request.GetOrdering().size() > 0) + { + int counter = 0; + std::vector orderByFields; + for (std::deque::const_iterator it = request.GetOrdering().begin(); it != request.GetOrdering().end(); ++it) + { + std::string orderingJoin; + + switch ((*it)->GetKeyType()) + { + case FindRequest::KeyType_DicomTag: + FormatJoinForOrdering(orderingJoin, (*it)->GetDicomTag(), counter, request.GetLevel()); + break; + case FindRequest::KeyType_Metadata: + FormatJoinForOrdering(orderingJoin, (*it)->GetMetadataType(), counter, request.GetLevel()); + break; + default: + throw OrthancException(ErrorCode_InternalError); + } + orderingJoins += orderingJoin; + + std::string orderByField; +#if ORTHANC_SQLITE_VERSION < 3030001 + // this is a way to push NULL values at the end before "NULLS LAST" was introduced: + // first filter by 0/1 and then by the column value itself + orderByField += "order" + boost::lexical_cast(counter) + ".value IS NULL, "; +#endif + orderByField += "order" + boost::lexical_cast(counter) + ".value"; + + if ((*it)->GetDirection() == FindRequest::OrderingDirection_Ascending) + { + orderByField += " ASC"; + } + else + { + orderByField += " DESC"; + } + orderByFields.push_back(orderByField); + ++counter; + } + + std::string orderByFieldsString; + Toolbox::JoinStrings(orderByFieldsString, orderByFields, ", "); + + ordering = "ROW_NUMBER() OVER (ORDER BY " + orderByFieldsString; +#if ORTHANC_SQLITE_VERSION >= 3030001 + ordering += " NULLS LAST"; +#endif + ordering += ") AS rowNumber"; + } + else + { + ordering = "ROW_NUMBER() OVER (ORDER BY " + strQueryLevel + ".publicId) AS rowNumber"; // we need a default ordering in order to make default queries repeatable when using since&limit + } sql = ("SELECT " + strQueryLevel + ".publicId, " + @@ -631,50 +803,76 @@ std::string joins, comparisons; + // handle parent constraints 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); + 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--) - { - sql += (" INNER JOIN Resources " + - FormatLevel(static_cast(level - 1)) + " ON " + - FormatLevel(static_cast(level - 1)) + ".internalId=" + - FormatLevel(static_cast(level)) + ".parentId"); + 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"; + } + } } } - else + + size_t count = 0; + + const DatabaseDicomTagConstraints& dicomTagsConstraints = request.GetDicomTagConstraints(); + for (size_t i = 0; i < dicomTagsConstraints.GetSize(); i++) { - size_t count = 0; + const DatabaseDicomTagConstraint& constraint = dicomTagsConstraints.GetConstraint(i); + + std::string comparison; - const DatabaseConstraints& dicomTagsConstraints = request.GetDicomTagConstraints(); - for (size_t i = 0; i < dicomTagsConstraints.GetSize(); i++) + if (FormatComparison(comparison, formatter, constraint, count, escapeBrackets)) { - const DatabaseConstraint& constraint = dicomTagsConstraints.GetConstraint(i); + std::string join; + FormatJoin(join, constraint, count); + joins += join; - std::string comparison; + if (!comparison.empty()) + { + comparisons += " AND " + comparison; + } - if (FormatComparison(comparison, formatter, constraint, count, escapeBrackets)) - { - std::string join; - FormatJoin(join, constraint, count); - joins += join; + count ++; + } + } - if (!comparison.empty()) - { - comparisons += " AND " + comparison; - } - - count ++; + for (std::deque::const_iterator it = request.GetMetadataConstraint().begin(); it != request.GetMetadataConstraint().end(); ++it) + { + std::string comparison; + + if (FormatComparison(comparison, formatter, *(*it), count, escapeBrackets)) + { + std::string join; + FormatJoin(join, *(*it), request.GetLevel(), count); + joins += join; + + if (!comparison.empty()) + { + comparisons += " AND " + comparison; } + + count ++; } } @@ -738,7 +936,7 @@ ".internalId AND selectedLabels.label IN (" + Join(formattedLabels, "", ", ") + ")) " + condition); } - sql += joins + Join(where, " WHERE ", " AND "); + sql += joins + orderingJoins + Join(where, " WHERE ", " AND "); if (request.HasLimits()) { @@ -749,7 +947,7 @@ void ISqlLookupFormatter::ApplySingleLevel(std::string& sql, ISqlLookupFormatter& formatter, - const DatabaseConstraints& lookup, + const DatabaseDicomTagConstraints& lookup, ResourceType queryLevel, const std::set& labels, LabelsConstraint labelsConstraint, @@ -768,7 +966,7 @@ for (size_t i = 0; i < lookup.GetSize(); i++) { - const DatabaseConstraint& constraint = lookup.GetConstraint(i); + const DatabaseDicomTagConstraint& constraint = lookup.GetConstraint(i); std::string comparison; diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/Search/ISqlLookupFormatter.h --- a/OrthancServer/Sources/Search/ISqlLookupFormatter.h Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/Search/ISqlLookupFormatter.h Wed Oct 09 11:06:20 2024 +0200 @@ -30,7 +30,7 @@ namespace Orthanc { - class DatabaseConstraints; + class DatabaseDicomTagConstraints; class FindRequest; enum LabelsConstraint @@ -65,11 +65,11 @@ static void GetLookupLevels(ResourceType& lowerLevel, ResourceType& upperLevel, const ResourceType& queryLevel, - const DatabaseConstraints& lookup); + const DatabaseDicomTagConstraints& lookup); static void Apply(std::string& sql, ISqlLookupFormatter& formatter, - const DatabaseConstraints& lookup, + const DatabaseDicomTagConstraints& lookup, ResourceType queryLevel, const std::set& labels, // New in Orthanc 1.12.0 LabelsConstraint labelsConstraint, // New in Orthanc 1.12.0 @@ -77,7 +77,7 @@ static void ApplySingleLevel(std::string& sql, ISqlLookupFormatter& formatter, - const DatabaseConstraints& lookup, + const DatabaseDicomTagConstraints& lookup, ResourceType queryLevel, const std::set& labels, // New in Orthanc 1.12.0 LabelsConstraint labelsConstraint, // New in Orthanc 1.12.0 diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/ServerEnumerations.cpp --- a/OrthancServer/Sources/ServerEnumerations.cpp Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/ServerEnumerations.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -616,4 +616,45 @@ throw OrthancException(ErrorCode_ParameterOutOfRange); } } -} + + ResponseContentFlags StringToResponseContent(const std::string& value) + { + if (value == "MainDicomTags") + { + return ResponseContentFlags_MainDicomTags; + } + else if (value == "Metadata") + { + return ResponseContentFlags_Metadata; + } + else if (value == "Status") + { + return ResponseContentFlags_Status; + } + else if (value == "Parent") + { + return ResponseContentFlags_Parent; + } + else if (value == "Children") + { + return ResponseContentFlags_Children; + } + else if (value == "Labels") + { + return ResponseContentFlags_Labels; + } + else if (value == "Attachments") + { + return ResponseContentFlags_Attachments; + } + else if (value == "IsStable") + { + return ResponseContentFlags_IsStable; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Unrecognized value for \"ResponseContent\": " + value); + } + } +} \ No newline at end of file diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/Sources/ServerEnumerations.h --- a/OrthancServer/Sources/ServerEnumerations.h Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/ServerEnumerations.h Wed Oct 09 11:06:20 2024 +0200 @@ -121,6 +121,40 @@ ConstraintType_List }; + enum ResponseContentFlags + { + ResponseContentFlags_ID = (1 << 0), + ResponseContentFlags_Type = (1 << 1), + ResponseContentFlags_RequestedTags = (1 << 2), + ResponseContentFlags_MainDicomTags = (1 << 3), + ResponseContentFlags_MetadataLegacy = (1 << 4), // when "Expand": true -> all metadata are included at root level + ResponseContentFlags_AttachmentsLegacy = (1 << 5), // when "Expand": true -> include attachments info at instance level + ResponseContentFlags_Metadata = (1 << 6), // all metadata are listed in a "Metadata" field + ResponseContentFlags_Attachments = (1 << 7), // all attachments are listed in a "Attachments" field + ResponseContentFlags_Status = (1 << 8), + ResponseContentFlags_Parent = (1 << 9), + ResponseContentFlags_Children = (1 << 10), + ResponseContentFlags_Labels = (1 << 11), + ResponseContentFlags_IsStable = (1 << 12), + + // Some predefined combinations + ResponseContentFlags_ExpandTrue = (ResponseContentFlags_ID | + ResponseContentFlags_Type | + ResponseContentFlags_RequestedTags | + ResponseContentFlags_MainDicomTags | + ResponseContentFlags_MetadataLegacy | + ResponseContentFlags_AttachmentsLegacy | + ResponseContentFlags_Status | + ResponseContentFlags_Parent | + ResponseContentFlags_Children | + ResponseContentFlags_Labels | + ResponseContentFlags_IsStable), // equivalent to "Expand": true + + ResponseContentFlags_Default = (ResponseContentFlags_ID | + ResponseContentFlags_Type | + ResponseContentFlags_RequestedTags) // minimal content as soon as you have a "ResponseContent" + + }; /** * WARNING: Do not change the explicit values in the enumerations @@ -251,6 +285,8 @@ Verbosity StringToVerbosity(const std::string& str); + ResponseContentFlags StringToResponseContent(const std::string& str); + std::string EnumerationToString(FileContentType type); std::string GetFileContentMime(FileContentType type); diff -r 79ac3924eff8 -r 79a497908b04 OrthancServer/UnitTestsSources/ServerIndexTests.cpp --- a/OrthancServer/UnitTestsSources/ServerIndexTests.cpp Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/UnitTestsSources/ServerIndexTests.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -166,7 +166,7 @@ DicomTagConstraint c(tag, type, value, true, true); - DatabaseConstraints lookup; + DatabaseDicomTagConstraints lookup; bool isEquivalent; // unused lookup.AddConstraint(c.ConvertToDatabaseConstraint(isEquivalent, level, DicomTagType_Identifier)); @@ -187,7 +187,7 @@ DicomTagConstraint c1(tag, type1, value1, true, true); DicomTagConstraint c2(tag, type2, value2, true, true); - DatabaseConstraints lookup; + DatabaseDicomTagConstraints lookup; bool isEquivalent; // unused lookup.AddConstraint(c1.ConvertToDatabaseConstraint(isEquivalent, level, DicomTagType_Identifier)); lookup.AddConstraint(c2.ConvertToDatabaseConstraint(isEquivalent, level, DicomTagType_Identifier));