# HG changeset patch # User Alain Mazy # Date 1647016696 -3600 # Node ID 8422e4f99a18979bedb8e4d6a0c2f983381fb9b8 # Parent acd3f72e2a21b0a90565ceedb252dda43e68f762 Handling RequestedTags in ExpandResource -> read parent main dicom tags if required. Not yet getting missing tags from file. Integration tests ok diff -r acd3f72e2a21 -r 8422e4f99a18 OrthancFramework/Sources/DicomFormat/DicomMap.cpp --- a/OrthancFramework/Sources/DicomFormat/DicomMap.cpp Thu Mar 10 19:00:43 2022 +0100 +++ b/OrthancFramework/Sources/DicomFormat/DicomMap.cpp Fri Mar 11 17:38:16 2022 +0100 @@ -403,7 +403,8 @@ } - static void ExtractTags(DicomMap& result, + // MORE_TAGS: TODO: we can probably remove the std::string from MainDicomTags (not used here !!!) + static void ExtractTagsInternal(DicomMap& result, const DicomMap::Content& source, const std::map& mainDicomTags) { @@ -420,10 +421,25 @@ } } + void DicomMap::ExtractTags(DicomMap& result, const std::set& tags) const + { + result.Clear(); + + for (std::set::const_iterator itmt = tags.begin(); + itmt != tags.end(); itmt++) + { + DicomMap::Content::const_iterator it = content_.find(*itmt); + if (it != content_.end()) + { + result.SetValue(it->first, *it->second /* value will be cloned */); + } + } + } + void DicomMap::ExtractResourceInformation(DicomMap& result, ResourceType level) const { const std::map& mainDicomTags = DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTags(level); - ExtractTags(result, content_, mainDicomTags); + ExtractTagsInternal(result, content_, mainDicomTags); } void DicomMap::ExtractPatientInformation(DicomMap& result) const diff -r acd3f72e2a21 -r 8422e4f99a18 OrthancFramework/Sources/DicomFormat/DicomMap.h --- a/OrthancFramework/Sources/DicomFormat/DicomMap.h Thu Mar 10 19:00:43 2022 +0100 +++ b/OrthancFramework/Sources/DicomFormat/DicomMap.h Fri Mar 11 17:38:16 2022 +0100 @@ -120,6 +120,8 @@ void ExtractResourceInformation(DicomMap& result, ResourceType level) const; + void ExtractTags(DicomMap& result, const std::set& tags) const; + static void SetupFindPatientTemplate(DicomMap& result); static void SetupFindStudyTemplate(DicomMap& result); diff -r acd3f72e2a21 -r 8422e4f99a18 OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp --- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Thu Mar 10 19:00:43 2022 +0100 +++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Fri Mar 11 17:38:16 2022 +0100 @@ -1332,6 +1332,23 @@ } + void FromDcmtkBridge::ParseListOfTags(std::set& result, const Json::Value& source) + { + result.clear(); + + if (!source.isArray()) + { + throw OrthancException(ErrorCode_BadRequest, "List of tags is not an array"); + } + + for (Json::ArrayIndex i = 0; i < source.size(); i++) + { + const std::string& value = source[i].asString(); + DicomTag tag = FromDcmtkBridge::ParseTag(value); + result.insert(tag); + } + } + const DicomValue &FromDcmtkBridge::GetValue(const DicomMap &fields, const std::string &tagName) { diff -r acd3f72e2a21 -r 8422e4f99a18 OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h --- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h Thu Mar 10 19:00:43 2022 +0100 +++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h Fri Mar 11 17:38:16 2022 +0100 @@ -178,6 +178,8 @@ // parses a list like "0010,0010;PatientBirthDate;0020,0020" static void ParseListOfTags(std::set& result, const std::string& source); + static void ParseListOfTags(std::set& result, const Json::Value& source); + static bool HasTag(const DicomMap& fields, const std::string& tagName); diff -r acd3f72e2a21 -r 8422e4f99a18 OrthancFramework/Sources/Toolbox.h --- a/OrthancFramework/Sources/Toolbox.h Thu Mar 10 19:00:43 2022 +0100 +++ b/OrthancFramework/Sources/Toolbox.h Fri Mar 11 17:38:16 2022 +0100 @@ -185,6 +185,48 @@ const std::string& source, char separator); + // returns true if all element of 'needles' are found in 'haystack' + template static bool IsSetInSet(const std::set& needles, const std::set& haystack) + { + for (typename std::set::const_iterator it = needles.begin(); + it != needles.end(); it++) + { + if (haystack.count(*it) == 0) + { + return false; + } + } + + return true; + } + + // returns the set of elements from 'needles' that are not in 'haystack' + template static size_t GetMissingsFromSet(std::set& missings, const std::set& needles, const std::set& haystack) + { + missings.clear(); + + for (typename std::set::const_iterator it = needles.begin(); + it != needles.end(); it++) + { + if (haystack.count(*it) == 0) + { + missings.insert(*it); + } + } + + return missings.size(); + } + + template static void AppendSets(std::set& target, const std::set& toAppend) + { + for (typename std::set::const_iterator it = toAppend.begin(); + it != toAppend.end(); it++) + { + target.insert(*it); + } + } + + #if ORTHANC_ENABLE_PUGIXML == 1 static void JsonToXml(std::string& target, const Json::Value& source, diff -r acd3f72e2a21 -r 8422e4f99a18 OrthancFramework/UnitTestsSources/ToolboxTests.cpp --- a/OrthancFramework/UnitTestsSources/ToolboxTests.cpp Thu Mar 10 19:00:43 2022 +0100 +++ b/OrthancFramework/UnitTestsSources/ToolboxTests.cpp Fri Mar 11 17:38:16 2022 +0100 @@ -209,3 +209,73 @@ std::unique_ptr > j(new SingleValueObject(42)); ASSERT_EQ(42, j->GetValue()); } + +TEST(Toolbox, IsSetInSet) +{ + { + std::set needles; + std::set haystack; + std::set missings; + + ASSERT_TRUE(Toolbox::IsSetInSet(needles, haystack)); + ASSERT_EQ(0, Toolbox::GetMissingsFromSet(missings, needles, haystack)); + } + + { + std::set needles; + std::set haystack; + std::set missings; + + haystack.insert(5); + ASSERT_TRUE(Toolbox::IsSetInSet(needles, haystack)); + ASSERT_EQ(0, Toolbox::GetMissingsFromSet(missings, needles, haystack)); + } + + { + std::set needles; + std::set haystack; + std::set missings; + + needles.insert(5); + haystack.insert(5); + ASSERT_TRUE(Toolbox::IsSetInSet(needles, haystack)); + ASSERT_EQ(0, Toolbox::GetMissingsFromSet(missings, needles, haystack)); + } + + { + std::set needles; + std::set haystack; + std::set missings; + + needles.insert(5); + + ASSERT_FALSE(Toolbox::IsSetInSet(needles, haystack)); + ASSERT_EQ(1, Toolbox::GetMissingsFromSet(missings, needles, haystack)); + ASSERT_TRUE(missings.count(5) == 1); + } + + { + std::set needles; + std::set haystack; + std::set missings; + + needles.insert(6); + haystack.insert(5); + ASSERT_FALSE(Toolbox::IsSetInSet(needles, haystack)); + ASSERT_EQ(1, Toolbox::GetMissingsFromSet(missings, needles, haystack)); + ASSERT_TRUE(missings.count(6) == 1); + } + + { + std::set needles; + std::set haystack; + std::set missings; + + needles.insert(5); + needles.insert(6); + haystack.insert(5); + haystack.insert(6); + ASSERT_TRUE(Toolbox::IsSetInSet(needles, haystack)); + ASSERT_EQ(0, Toolbox::GetMissingsFromSet(missings, needles, haystack)); + } +} diff -r acd3f72e2a21 -r 8422e4f99a18 OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp --- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Thu Mar 10 19:00:43 2022 +0100 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Fri Mar 11 17:38:16 2022 +0100 @@ -713,10 +713,11 @@ bool StatelessDatabaseOperations::ExpandResource(ExpandedResource& target, const std::string& publicId, ResourceType level, - DicomToJsonFormat format) + DicomToJsonFormat format, + const std::set& requestedTags) { - class Operations : public ReadOnlyOperationsT5< - bool&, ExpandedResource&, const std::string&, ResourceType, DicomToJsonFormat> + class Operations : public ReadOnlyOperationsT6< + bool&, ExpandedResource&, const std::string&, ResourceType, DicomToJsonFormat, const std::set&> { private: @@ -765,7 +766,7 @@ const Tuple& tuple) ORTHANC_OVERRIDE { // Lookup for the requested resource - int64_t internalId; // unused + int64_t internalId; ResourceType type; std::string parent; if (!transaction.LookupResourceAndParent(internalId, type, parent, tuple.get<2>()) || @@ -866,7 +867,58 @@ // read all tags from DB transaction.GetMainDicomTags(target.tags_, internalId); - // MORE_TAGS: TODO: eventualy get parent dicom tags if requested .... + // check if we have access to all requestedTags or if we must get tags from parents + const std::set& requestedTags = tuple.get<5>(); + + if (requestedTags.size() > 0) + { + std::set savedMainDicomTags; + + FromDcmtkBridge::ParseListOfTags(savedMainDicomTags, target.mainDicomTagsSignature_); + + // read parent main dicom tags as long as we don't have gathered all requested tags + ResourceType currentLevel = target.type_; + int64_t currentInternalId = internalId; + Toolbox::GetMissingsFromSet(target.missingRequestedTags_, requestedTags, savedMainDicomTags); + + while ((target.missingRequestedTags_.size() > 0) + && currentLevel != ResourceType_Patient) + { + currentLevel = GetParentResourceType(currentLevel); + + int64_t currentParentId; + if (!transaction.LookupParent(currentParentId, currentInternalId)) + { + break; + } + + std::map parentMetadata; + transaction.GetAllMetadata(parentMetadata, currentParentId); + + std::string parentMainDicomTagsSignature = DicomMap::GetDefaultMainDicomTagsSignature(currentLevel); + LookupStringMetadata(parentMainDicomTagsSignature, parentMetadata, MetadataType_MainDicomTagsSignature); + + std::set parentSavedMainDicomTags; + FromDcmtkBridge::ParseListOfTags(parentSavedMainDicomTags, parentMainDicomTagsSignature); + + size_t previousMissingCount = target.missingRequestedTags_.size(); + Toolbox::AppendSets(savedMainDicomTags, parentSavedMainDicomTags); + Toolbox::GetMissingsFromSet(target.missingRequestedTags_, requestedTags, savedMainDicomTags); + + // read the parent tags from DB only if it reduces the number of missing tags + if (target.missingRequestedTags_.size() < previousMissingCount) + { + Toolbox::AppendSets(savedMainDicomTags, parentSavedMainDicomTags); + + DicomMap parentTags; + transaction.GetMainDicomTags(parentTags, currentParentId); + + target.tags_.Merge(parentTags); + } + + currentInternalId = currentParentId; + } + } std::string tmp; @@ -903,7 +955,7 @@ bool found; Operations operations; - operations.Apply(*this, found, target, publicId, level, format); + operations.Apply(*this, found, target, publicId, level, format, requestedTags); return found; } diff -r acd3f72e2a21 -r 8422e4f99a18 OrthancServer/Sources/Database/StatelessDatabaseOperations.h --- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Thu Mar 10 19:00:43 2022 +0100 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Fri Mar 11 17:38:16 2022 +0100 @@ -49,6 +49,7 @@ std::string anonymizedFrom_; std::string modifiedFrom_; std::string lastUpdate_; + std::set missingRequestedTags_; // for patients/studies/series bool isStable_; @@ -478,7 +479,8 @@ bool ExpandResource(ExpandedResource& target, const std::string& publicId, ResourceType level, - DicomToJsonFormat format); + DicomToJsonFormat format, + const std::set& requestedTags); void GetAllMetadata(std::map& target, const std::string& publicId, diff -r acd3f72e2a21 -r 8422e4f99a18 OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp Thu Mar 10 19:00:43 2022 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp Fri Mar 11 17:38:16 2022 +0100 @@ -28,6 +28,7 @@ #include "../../../OrthancFramework/Sources/Logging.h" #include "../../../OrthancFramework/Sources/MetricsRegistry.h" #include "../../../OrthancFramework/Sources/SerializationToolbox.h" +#include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" #include "../OrthancConfiguration.h" #include "../ServerContext.h" @@ -463,10 +464,12 @@ static const std::string GET_SIMPLIFY = "simplify"; static const std::string GET_FULL = "full"; static const std::string GET_SHORT = "short"; + static const std::string GET_REQUESTED_TAGS = "requestedTags"; static const std::string POST_SIMPLIFY = "Simplify"; static const std::string POST_FULL = "Full"; static const std::string POST_SHORT = "Short"; + static const std::string POST_REQUESTED_TAGS = "RequestedTags"; static const std::string DOCUMENT_SIMPLIFY = "report the DICOM tags in human-readable format (using the symbolic name of the tags)"; @@ -525,7 +528,6 @@ } } - void OrthancRestApi::DocumentDicomFormat(RestApiGetCall& call, DicomToJsonFormat defaultFormat) { @@ -570,4 +572,34 @@ "If set to `true`, " + DOCUMENT_FULL, false); } } + + void OrthancRestApi::GetRequestedTags(std::set& requestedTags, + const RestApiGetCall& call) + { + requestedTags.clear(); + + if (call.HasArgument(GET_REQUESTED_TAGS)) + { + try + { + FromDcmtkBridge::ParseListOfTags(requestedTags, call.GetArgument("requestedTags", "")); + } + catch (OrthancException& ex) + { + throw OrthancException(ErrorCode_BadRequest, std::string("Invalid requestedTags argument: ") + ex.What() + " " + ex.GetDetails()); + } + } + + } + + void OrthancRestApi::DocumentRequestedTags(RestApiGetCall& call) + { + call.GetDocumentation().SetHttpGetArgument(GET_REQUESTED_TAGS, RestApiCallDocumentation::Type_String, + "If present, list the DICOM Tags you want to list in the response. This argument is a semi-column separated list " + "of DICOM Tags identifiers; e.g: 'requestedTags=0010,0010;PatientBirthDate'. " + "The tags requested tags are returned in the 'RequestedTags' field in the response. " + "Note that, if you are requesting tags that are not listed in the Main Dicom Tags stored in DB, building the response " + "might be slow since Orthanc will need to access the DICOM files. If not specified, Orthanc will return ", false); + } + } diff -r acd3f72e2a21 -r 8422e4f99a18 OrthancServer/Sources/OrthancRestApi/OrthancRestApi.h --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.h Thu Mar 10 19:00:43 2022 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.h Fri Mar 11 17:38:16 2022 +0100 @@ -145,5 +145,10 @@ static void DocumentDicomFormat(RestApiPostCall& call, DicomToJsonFormat defaultFormat); + + static void GetRequestedTags(std::set& requestedTags, + const RestApiGetCall& call); + + static void DocumentRequestedTags(RestApiGetCall& call); }; } diff -r acd3f72e2a21 -r 8422e4f99a18 OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Thu Mar 10 19:00:43 2022 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Fri Mar 11 17:38:16 2022 +0100 @@ -130,7 +130,8 @@ const std::list& resources, ResourceType level, bool expand, - DicomToJsonFormat format) + DicomToJsonFormat format, + const std::set& requestedTags) { Json::Value answer = Json::arrayValue; @@ -140,7 +141,7 @@ if (expand) { Json::Value expanded; - if (context.ExpandResource(expanded, *resource, level, format)) + if (context.ExpandResource(expanded, *resource, level, format, requestedTags)) { answer.append(expanded); } @@ -161,6 +162,7 @@ if (call.IsDocumentation()) { OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Human); + OrthancRestApi::DocumentRequestedTags(call); const std::string resources = GetResourceTypeText(resourceType, true /* plural */, false /* lower case */); call.GetDocumentation() @@ -182,6 +184,9 @@ std::list result; + std::set requestedTags; + OrthancRestApi::GetRequestedTags(requestedTags, call); + if (call.HasArgument("limit") || call.HasArgument("since")) { @@ -209,7 +214,8 @@ } AnswerListOfResources(call.GetOutput(), context, result, resourceType, call.HasArgument("expand"), - OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human)); + OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human), + requestedTags); } @@ -220,6 +226,7 @@ if (call.IsDocumentation()) { OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Human); + OrthancRestApi::DocumentRequestedTags(call); const std::string resource = GetResourceTypeText(resourceType, false /* plural */, false /* lower case */); call.GetDocumentation() @@ -227,12 +234,6 @@ .SetSummary("Get information about some " + resource) .SetDescription("Get detailed information about the DICOM " + resource + " whose Orthanc identifier is provided in the URL") .SetUriArgument("id", "Orthanc identifier of the " + resource + " of interest") - .SetHttpGetArgument("requestedTags", RestApiCallDocumentation::Type_String, - "If present, list the DICOM Tags you want to list in the response. This argument is a semi-column separated list " - "of DICOM Tags identifiers; e.g: 'requestedTags=0010,0010;PatientBirthDate'. " - "The tags requested tags are returned in the 'RequestedTags' field in the response. " - "Note that, if you are requesting tags that are not listed in the Main Dicom Tags stored in DB, building the response " - "might be slow since Orthanc will need to access the DICOM files. If not specified, Orthanc will return ", false) .AddAnswerType(MimeType_Json, "Information about the DICOM " + resource) .SetHttpGetSample(GetDocumentationSampleResource(resourceType), true); return; @@ -240,22 +241,12 @@ const DicomToJsonFormat format = OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human); - std::set responseTags; - if (call.HasArgument("requestedTags")) - { - try - { - FromDcmtkBridge::ParseListOfTags(responseTags, call.GetArgument("requestedTags", "")); - } - catch (OrthancException& ex) - { - throw OrthancException(ErrorCode_BadRequest, std::string("Invalid requestedTags argument: ") + ex.What() + " " + ex.GetDetails()); - } - } + std::set requestedTags; + OrthancRestApi::GetRequestedTags(requestedTags, call); Json::Value json; if (OrthancRestApi::GetContext(call).ExpandResource( - json, call.GetUriComponent("id", ""), resourceType, format)) // TODO, requestedTags)) + json, call.GetUriComponent("id", ""), resourceType, format, requestedTags)) { call.GetOutput().AnswerJson(json); } @@ -2867,9 +2858,10 @@ void Answer(RestApiOutput& output, ServerContext& context, ResourceType level, - bool expand) const + bool expand, + const std::set& requestedTags) const { - AnswerListOfResources(output, context, resources_, level, expand, format_); + AnswerListOfResources(output, context, resources_, level, expand, format_, requestedTags); } }; } @@ -2911,7 +2903,6 @@ "Note that, if you are requesting tags that are not listed in the Main Dicom Tags stored in DB, building the response " "might be slow since Orthanc will need to access the DICOM files. If not specified, Orthanc will return " "all Main Dicom Tags to keep backward compatibility with Orthanc prior to 1.11.0.", false) - .SetRequestField(KEY_QUERY, RestApiCallDocumentation::Type_JsonObject, "Associative array containing the filter on the values of the DICOM tags", true) .AddAnswerType(MimeType_Json, "JSON array containing either the Orthanc identifiers, or detailed information " @@ -2921,8 +2912,6 @@ ServerContext& context = OrthancRestApi::GetContext(call); - // MORE_TAGS: TODO: handle RequestedTags - Json::Value request; if (!call.ParseJsonRequest(request) || request.type() != Json::objectValue) @@ -2960,6 +2949,12 @@ throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_SINCE) + "\" should be an integer"); } + else if (request.isMember(KEY_REQUESTED_TAGS) && + request[KEY_REQUESTED_TAGS].type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadRequest, + "Field \"" + std::string(KEY_REQUESTED_TAGS) + "\" should be an array"); + } else { bool expand = false; @@ -3000,6 +2995,13 @@ since = static_cast(tmp); } + std::set requestedTags; + + if (request.isMember(KEY_REQUESTED_TAGS)) + { + FromDcmtkBridge::ParseListOfTags(requestedTags, request[KEY_REQUESTED_TAGS]); + } + ResourceType level = StringToResourceType(request[KEY_LEVEL].asCString()); DatabaseLookup query; @@ -3027,7 +3029,7 @@ FindVisitor visitor(OrthancRestApi::GetDicomFormat(request, DicomToJsonFormat_Human)); context.Apply(visitor, query, level, since, limit); - visitor.Answer(call.GetOutput(), context, level, expand); + visitor.Answer(call.GetOutput(), context, level, expand, requestedTags); } } @@ -3039,6 +3041,7 @@ if (call.IsDocumentation()) { OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Human); + OrthancRestApi::DocumentRequestedTags(call); const std::string children = GetResourceTypeText(end, true /* plural */, false /* lower case */); const std::string resource = GetResourceTypeText(start, false /* plural */, false /* lower case */); @@ -3055,6 +3058,9 @@ ServerIndex& index = OrthancRestApi::GetIndex(call); + std::set requestedTags; + OrthancRestApi::GetRequestedTags(requestedTags, call); + std::list a, b, c; a.push_back(call.GetUriComponent("id", "")); @@ -3084,7 +3090,7 @@ it = a.begin(); it != a.end(); ++it) { Json::Value resource; - if (OrthancRestApi::GetContext(call).ExpandResource(resource, *it, end, format)) + if (OrthancRestApi::GetContext(call).ExpandResource(resource, *it, end, format, requestedTags)) { result.append(resource); } @@ -3162,6 +3168,7 @@ if (call.IsDocumentation()) { OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Human); + OrthancRestApi::DocumentRequestedTags(call); const std::string parent = GetResourceTypeText(end, false /* plural */, false /* lower case */); const std::string resource = GetResourceTypeText(start, false /* plural */, false /* lower case */); @@ -3177,7 +3184,10 @@ } ServerIndex& index = OrthancRestApi::GetIndex(call); - + + std::set requestedTags; + OrthancRestApi::GetRequestedTags(requestedTags, call); + std::string current = call.GetUriComponent("id", ""); ResourceType currentType = start; while (currentType > end) @@ -3199,7 +3209,7 @@ const DicomToJsonFormat format = OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human); Json::Value resource; - if (OrthancRestApi::GetContext(call).ExpandResource(resource, current, end, format)) + if (OrthancRestApi::GetContext(call).ExpandResource(resource, current, end, format, requestedTags)) { call.GetOutput().AnswerJson(resource); } @@ -3438,7 +3448,7 @@ { static const char* const LEVEL = "Level"; static const char* const METADATA = "Metadata"; - + if (call.IsDocumentation()) { OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Human); @@ -3450,7 +3460,7 @@ "List of the Orthanc identifiers of the patients/studies/series/instances of interest.", true) .SetRequestField(LEVEL, RestApiCallDocumentation::Type_String, "This optional argument specifies the level of interest (can be `Patient`, `Study`, `Series` or " - "`Instance`). Orthanc will loop over the items inside `Resources`, and explorer upward or " + "`Instance`). Orthanc will loop over the items inside `Resources`, and explore upward or " "downward in the DICOM hierarchy in order to find the level of interest.", false) .SetRequestField(METADATA, RestApiCallDocumentation::Type_Boolean, "If set to `true` (default value), the metadata associated with the resources will also be retrieved.", false) @@ -3571,7 +3581,9 @@ it = interest.begin(); it != interest.end(); ++it) { Json::Value item; - if (OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format)) + std::set emptyRequestedTags; // not supported for bulk content + + if (OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format, emptyRequestedTags)) { if (metadata) { @@ -3593,8 +3605,10 @@ { ResourceType level; Json::Value item; + std::set emptyRequestedTags; // not supported for bulk content + if (index.LookupResourceType(level, *it) && - OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format)) + OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format, emptyRequestedTags)) { if (metadata) { diff -r acd3f72e2a21 -r 8422e4f99a18 OrthancServer/Sources/OrthancWebDav.cpp --- a/OrthancServer/Sources/OrthancWebDav.cpp Thu Mar 10 19:00:43 2022 +0100 +++ b/OrthancServer/Sources/OrthancWebDav.cpp Fri Mar 11 17:38:16 2022 +0100 @@ -259,7 +259,9 @@ const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE { Json::Value resource; - if (context_.ExpandResource(resource, publicId, level_, DicomToJsonFormat_Human)) + std::set emptyRequestedTags; // not supported for webdav + + if (context_.ExpandResource(resource, publicId, level_, DicomToJsonFormat_Human, emptyRequestedTags)) { if (success_) { diff -r acd3f72e2a21 -r 8422e4f99a18 OrthancServer/Sources/ServerContext.cpp --- a/OrthancServer/Sources/ServerContext.cpp Thu Mar 10 19:00:43 2022 +0100 +++ b/OrthancServer/Sources/ServerContext.cpp Fri Mar 11 17:38:16 2022 +0100 @@ -2096,7 +2096,8 @@ static void SerializeExpandedResource(Json::Value& target, const ExpandedResource& resource, - DicomToJsonFormat format) + DicomToJsonFormat format, + const std::set& requestedTags) { target = Json::objectValue; @@ -2241,17 +2242,29 @@ FromDcmtkBridge::ToJson(target[PATIENT_MAIN_DICOM_TAGS], patientMainDicomTags, format); } + if (requestedTags.size() > 0) + { + static const char* const REQUESTED_TAGS = "RequestedTags"; + + DicomMap tags; + resource.tags_.ExtractTags(tags, requestedTags); + + target[REQUESTED_TAGS] = Json::objectValue; + FromDcmtkBridge::ToJson(target[REQUESTED_TAGS], tags, format); + } + } bool ServerContext::ExpandResource(Json::Value& target, const std::string& publicId, ResourceType level, - DicomToJsonFormat format) + DicomToJsonFormat format, + const std::set& requestedTags) { ExpandedResource resource; - if (GetIndex().ExpandResource(resource, publicId, level, format)) + if (GetIndex().ExpandResource(resource, publicId, level, format, requestedTags)) { // check the main dicom tags list has not changed since the resource was stored @@ -2260,13 +2273,15 @@ OrthancConfiguration::ReaderLock lock; if (lock.GetConfiguration().IsInconsistentDicomTagsLogsEnabled()) { - LOG(WARNING) << Orthanc::GetResourceTypeText(resource.type_, false , false) << " has been stored with another version of Main Dicom Tags list, you should POST to /" << Orthanc::GetResourceTypeText(resource.type_, true, false) << "/" << resource.id_ << "/reconstruct to update the list of tags saved in DB. Some tags might be missing from this answer."; + LOG(WARNING) << Orthanc::GetResourceTypeText(resource.type_, false , false) << " has been stored with another version of Main Dicom Tags list, you should POST to /" << Orthanc::GetResourceTypeText(resource.type_, true, false) << "/" << resource.id_ << "/reconstruct to update the list of tags saved in DB. Some MainDicomTags might be missing from this answer."; } } // MORE_TAGS: TODO: possibly merge missing requested tags from /tags + // log warning + // use resource.missingRequestedTags_ - SerializeExpandedResource(target, resource, format); + SerializeExpandedResource(target, resource, format, requestedTags); return true; } diff -r acd3f72e2a21 -r 8422e4f99a18 OrthancServer/Sources/ServerContext.h --- a/OrthancServer/Sources/ServerContext.h Thu Mar 10 19:00:43 2022 +0100 +++ b/OrthancServer/Sources/ServerContext.h Fri Mar 11 17:38:16 2022 +0100 @@ -543,7 +543,8 @@ bool ExpandResource(Json::Value& target, const std::string& publicId, ResourceType level, - DicomToJsonFormat format); + DicomToJsonFormat format, + const std::set& requestedTags); }; }