# HG changeset patch # User Alain Mazy # Date 1646935243 -3600 # Node ID acd3f72e2a21b0a90565ceedb252dda43e68f762 # Parent 94a7b681b340d4fc17a17f2f42c872c1190fbf1e split ExpandResource in 2: read from DB and serialize to json. This will allow us to merge requested tags from both the DB and the file system diff -r 94a7b681b340 -r acd3f72e2a21 OrthancFramework/Sources/DicomFormat/DicomMap.cpp --- a/OrthancFramework/Sources/DicomFormat/DicomMap.cpp Thu Mar 10 09:03:24 2022 +0100 +++ b/OrthancFramework/Sources/DicomFormat/DicomMap.cpp Thu Mar 10 19:00:43 2022 +0100 @@ -209,11 +209,9 @@ tagsIds.insert(it->Format()); } - std::string signatureText = boost::algorithm::join(tagsIds, "|"); - std::string signatureMD5; - Toolbox::ComputeMD5(signatureMD5, signatureText); + std::string signatureText = boost::algorithm::join(tagsIds, ";"); - return signatureMD5; + return signatureText; } void LoadDefaultMainDicomTags(ResourceType level) @@ -422,29 +420,30 @@ } } + void DicomMap::ExtractResourceInformation(DicomMap& result, ResourceType level) const + { + const std::map& mainDicomTags = DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTags(level); + ExtractTags(result, content_, mainDicomTags); + } void DicomMap::ExtractPatientInformation(DicomMap& result) const { - const std::map& mainDicomTags = DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTags(ResourceType_Patient); - ExtractTags(result, content_, mainDicomTags); + ExtractResourceInformation(result, ResourceType_Patient); } void DicomMap::ExtractStudyInformation(DicomMap& result) const { - const std::map& mainDicomTags = DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTags(ResourceType_Study); - ExtractTags(result, content_, mainDicomTags); + ExtractResourceInformation(result, ResourceType_Study); } void DicomMap::ExtractSeriesInformation(DicomMap& result) const { - const std::map& mainDicomTags = DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTags(ResourceType_Series); - ExtractTags(result, content_, mainDicomTags); + ExtractResourceInformation(result, ResourceType_Series); } void DicomMap::ExtractInstanceInformation(DicomMap& result) const { - const std::map& mainDicomTags = DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTags(ResourceType_Instance); - ExtractTags(result, content_, mainDicomTags); + ExtractResourceInformation(result, ResourceType_Instance); } diff -r 94a7b681b340 -r acd3f72e2a21 OrthancFramework/Sources/DicomFormat/DicomMap.h --- a/OrthancFramework/Sources/DicomFormat/DicomMap.h Thu Mar 10 09:03:24 2022 +0100 +++ b/OrthancFramework/Sources/DicomFormat/DicomMap.h Thu Mar 10 19:00:43 2022 +0100 @@ -118,6 +118,8 @@ void ExtractInstanceInformation(DicomMap& result) const; + void ExtractResourceInformation(DicomMap& result, ResourceType level) const; + static void SetupFindPatientTemplate(DicomMap& result); static void SetupFindStudyTemplate(DicomMap& result); diff -r 94a7b681b340 -r acd3f72e2a21 OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp --- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Thu Mar 10 09:03:24 2022 +0100 +++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Thu Mar 10 19:00:43 2022 +0100 @@ -1296,7 +1296,7 @@ else { CLOG(INFO, DICOM) << "Unknown DICOM tag: \"" << name << "\""; - throw OrthancException(ErrorCode_UnknownDicomTag); + throw OrthancException(ErrorCode_UnknownDicomTag, name, false); } #endif } @@ -1311,6 +1311,27 @@ return fields.HasTag(ParseTag(tagName)); } + + // parses a list like "0010,0010;PatientBirthDate;0020,0020" + void FromDcmtkBridge::ParseListOfTags(std::set& result, const std::string& source) + { + result.clear(); + + std::vector tokens; + Toolbox::TokenizeString(tokens, source, ';'); + + for (std::vector::const_iterator it = tokens.begin(); + it != tokens.end(); it++) + { + if (it->size() > 0) + { + DicomTag tag = FromDcmtkBridge::ParseTag(*it); + result.insert(tag); + } + } + } + + const DicomValue &FromDcmtkBridge::GetValue(const DicomMap &fields, const std::string &tagName) { diff -r 94a7b681b340 -r acd3f72e2a21 OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h --- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h Thu Mar 10 09:03:24 2022 +0100 +++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h Thu Mar 10 19:00:43 2022 +0100 @@ -175,6 +175,9 @@ static DicomTag ParseTag(const std::string& name); + // parses a list like "0010,0010;PatientBirthDate;0020,0020" + static void ParseListOfTags(std::set& result, const std::string& source); + static bool HasTag(const DicomMap& fields, const std::string& tagName); diff -r 94a7b681b340 -r acd3f72e2a21 OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp --- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp Thu Mar 10 09:03:24 2022 +0100 +++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp Thu Mar 10 19:00:43 2022 +0100 @@ -358,6 +358,44 @@ } +TEST(FromDcmtkBridge, ParseListOfTags) +{ + {// nominal test + std::string source = "0010,0010;PatientBirthDate;0020,0020"; + std::set result; + FromDcmtkBridge::ParseListOfTags(result, source); + + ASSERT_TRUE(result.find(DICOM_TAG_PATIENT_NAME) != result.end()); + ASSERT_TRUE(result.find(DICOM_TAG_PATIENT_BIRTH_DATE) != result.end()); + ASSERT_TRUE(result.find(DICOM_TAG_PATIENT_ORIENTATION) != result.end()); + ASSERT_TRUE(result.find(DICOM_TAG_PATIENT_ID) == result.end()); + } + + {// no tag + std::string source = ""; + std::set result; + FromDcmtkBridge::ParseListOfTags(result, source); + + ASSERT_EQ(0, result.size()); + } + + {// invalid tag + std::string source = "0010,0010;Patient-BirthDate;0020,0020"; + std::set result; + + ASSERT_THROW(FromDcmtkBridge::ParseListOfTags(result, source), OrthancException); + } + + {// duplicate tag only once + std::string source = "0010,0010;PatientName"; + std::set result; + + FromDcmtkBridge::ParseListOfTags(result, source); + + ASSERT_EQ(1, result.size()); + } + +} static const DicomTag REFERENCED_STUDY_SEQUENCE(0x0008, 0x1110); static const DicomTag REFERENCED_PATIENT_SEQUENCE(0x0008, 0x1120); diff -r 94a7b681b340 -r acd3f72e2a21 OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp --- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Thu Mar 10 09:03:24 2022 +0100 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Thu Mar 10 19:00:43 2022 +0100 @@ -710,46 +710,15 @@ } - bool StatelessDatabaseOperations::ExpandResource(Json::Value& target, + bool StatelessDatabaseOperations::ExpandResource(ExpandedResource& target, const std::string& publicId, ResourceType level, DicomToJsonFormat format) { class Operations : public ReadOnlyOperationsT5< - bool&, Json::Value&, const std::string&, ResourceType, DicomToJsonFormat> + bool&, ExpandedResource&, const std::string&, ResourceType, DicomToJsonFormat> { private: - static void MainDicomTagsToJson(ReadOnlyTransaction& transaction, - Json::Value& target, - int64_t resourceId, - ResourceType resourceType, - DicomToJsonFormat format) - { - static const char* const MAIN_DICOM_TAGS = "MainDicomTags"; - static const char* const PATIENT_MAIN_DICOM_TAGS = "PatientMainDicomTags"; - - DicomMap tags; - transaction.GetMainDicomTags(tags, resourceId); - - if (resourceType == ResourceType_Study) - { - DicomMap t1, t2; - tags.ExtractStudyInformation(t1); - tags.ExtractPatientInformation(t2); - - target[MAIN_DICOM_TAGS] = Json::objectValue; - FromDcmtkBridge::ToJson(target[MAIN_DICOM_TAGS], t1, format); - - target[PATIENT_MAIN_DICOM_TAGS] = Json::objectValue; - FromDcmtkBridge::ToJson(target[PATIENT_MAIN_DICOM_TAGS], t2, format); - } - else - { - target[MAIN_DICOM_TAGS] = Json::objectValue; - FromDcmtkBridge::ToJson(target[MAIN_DICOM_TAGS], tags, format); - } - } - static bool LookupStringMetadata(std::string& result, const std::map& metadata, @@ -806,9 +775,8 @@ } else { - Json::Value& target = tuple.get<1>(); - target = Json::objectValue; - + ExpandedResource& target = tuple.get<1>(); + // Set information about the parent resource (if it exists) if (type == ResourceType_Patient) { @@ -824,87 +792,36 @@ throw OrthancException(ErrorCode_DatabasePlugin); } - switch (type) - { - case ResourceType_Study: - target["ParentPatient"] = parent; - break; - - case ResourceType_Series: - target["ParentStudy"] = parent; - break; - - case ResourceType_Instance: - target["ParentSeries"] = parent; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } + target.parentId_ = parent; } // List the children resources - std::list children; - transaction.GetChildrenPublicId(children, internalId); - - if (type != ResourceType_Instance) - { - Json::Value c = Json::arrayValue; - - for (std::list::const_iterator - it = children.begin(); it != children.end(); ++it) - { - c.append(*it); - } - - switch (type) - { - case ResourceType_Patient: - target["Studies"] = c; - break; - - case ResourceType_Study: - target["Series"] = c; - break; - - case ResourceType_Series: - target["Instances"] = c; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } + transaction.GetChildrenPublicId(target.childrenIds_, internalId); // Extract the metadata - std::map metadata; - transaction.GetAllMetadata(metadata, internalId); + transaction.GetAllMetadata(target.metadata_, internalId); // Set the resource type + target.type_ = type; + switch (type) { case ResourceType_Patient: - target["Type"] = "Patient"; - break; - case ResourceType_Study: - target["Type"] = "Study"; break; case ResourceType_Series: { - target["Type"] = "Series"; - int64_t i; - if (LookupIntegerMetadata(i, metadata, MetadataType_Series_ExpectedNumberOfInstances)) + if (LookupIntegerMetadata(i, target.metadata_, MetadataType_Series_ExpectedNumberOfInstances)) { - target["ExpectedNumberOfInstances"] = static_cast(i); - target["Status"] = EnumerationToString(transaction.GetSeriesStatus(internalId, i)); + target.expectedNumberOfInstances_ = static_cast(i); + target.status_ = EnumerationToString(transaction.GetSeriesStatus(internalId, i)); } else { - target["ExpectedNumberOfInstances"] = Json::nullValue; - target["Status"] = EnumerationToString(SeriesStatus_Unknown); + target.expectedNumberOfInstances_ = -1; + target.status_ = EnumerationToString(SeriesStatus_Unknown); } break; @@ -912,8 +829,6 @@ case ResourceType_Instance: { - target["Type"] = "Instance"; - FileInfo attachment; int64_t revision; // ignored if (!transaction.LookupAttachment(attachment, revision, internalId, FileContentType_Dicom)) @@ -921,17 +836,17 @@ throw OrthancException(ErrorCode_InternalError); } - target["FileSize"] = static_cast(attachment.GetUncompressedSize()); - target["FileUuid"] = attachment.GetUuid(); + target.fileSize_ = static_cast(attachment.GetUncompressedSize()); + target.fileUuid_ = attachment.GetUuid(); int64_t i; - if (LookupIntegerMetadata(i, metadata, MetadataType_Instance_IndexInSeries)) + if (LookupIntegerMetadata(i, target.metadata_, MetadataType_Instance_IndexInSeries)) { - target["IndexInSeries"] = static_cast(i); + target.indexInSeries_ = static_cast(i); } else { - target["IndexInSeries"] = Json::nullValue; + target.indexInSeries_ = -1; } break; @@ -942,46 +857,44 @@ } // check the main dicom tags list has not changed since the resource was stored - std::string resourceMainDicomTagsSignature = DicomMap::GetDefaultMainDicomTagsSignature(type); - LookupStringMetadata(resourceMainDicomTagsSignature, metadata, MetadataType_MainDicomTagsSignature); - - if (resourceMainDicomTagsSignature != DicomMap::GetMainDicomTagsSignature(type)) - { - OrthancConfiguration::ReaderLock lock; - if (lock.GetConfiguration().IsInconsistentDicomTagsLogsEnabled()) - { - LOG(WARNING) << Orthanc::GetResourceTypeText(type, false , false) << " has been stored with another version of Main Dicom Tags list, you should POST to /" << Orthanc::GetResourceTypeText(type, true, false) << "/" << tuple.get<2>() << "/reconstruct to update the list of tags saved in DB. Some tags might be missing from this answer."; - } - } - + target.mainDicomTagsSignature_ = DicomMap::GetDefaultMainDicomTagsSignature(type); + LookupStringMetadata(target.mainDicomTagsSignature_, target.metadata_, MetadataType_MainDicomTagsSignature); // Record the remaining information - target["ID"] = tuple.get<2>(); - MainDicomTagsToJson(transaction, target, internalId, type, tuple.get<4>()); + target.id_ = tuple.get<2>(); + + // read all tags from DB + transaction.GetMainDicomTags(target.tags_, internalId); + + // MORE_TAGS: TODO: eventualy get parent dicom tags if requested .... std::string tmp; - if (LookupStringMetadata(tmp, metadata, MetadataType_AnonymizedFrom)) + if (LookupStringMetadata(tmp, target.metadata_, MetadataType_AnonymizedFrom)) { - target["AnonymizedFrom"] = tmp; + target.anonymizedFrom_ = tmp; } - if (LookupStringMetadata(tmp, metadata, MetadataType_ModifiedFrom)) + if (LookupStringMetadata(tmp, target.metadata_, MetadataType_ModifiedFrom)) { - target["ModifiedFrom"] = tmp; + target.modifiedFrom_ = tmp; } if (type == ResourceType_Patient || type == ResourceType_Study || type == ResourceType_Series) { - target["IsStable"] = !transaction.GetTransactionContext().IsUnstableResource(internalId); - - if (LookupStringMetadata(tmp, metadata, MetadataType_LastUpdate)) + target.isStable_ = !transaction.GetTransactionContext().IsUnstableResource(internalId); + + if (LookupStringMetadata(tmp, target.metadata_, MetadataType_LastUpdate)) { - target["LastUpdate"] = tmp; + target.lastUpdate_ = tmp; } } + else + { + target.isStable_ = false; + } tuple.get<0>() = true; } diff -r 94a7b681b340 -r acd3f72e2a21 OrthancServer/Sources/Database/StatelessDatabaseOperations.h --- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Thu Mar 10 09:03:24 2022 +0100 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Thu Mar 10 19:00:43 2022 +0100 @@ -37,6 +37,33 @@ class ParsedDicomFile; struct ServerIndexChange; + struct ExpandedResource : public boost::noncopyable + { + std::string id_; + DicomMap tags_; // all tags from DB + std::string mainDicomTagsSignature_; + std::string parentId_; + std::list childrenIds_; + std::map metadata_; + ResourceType type_; + std::string anonymizedFrom_; + std::string modifiedFrom_; + std::string lastUpdate_; + + // for patients/studies/series + bool isStable_; + + // for series only + int expectedNumberOfInstances_; + std::string status_; + + // for instances only + size_t fileSize_; + std::string fileUuid_; + int indexInSeries_; + }; + + class StatelessDatabaseOperations : public boost::noncopyable { public: @@ -448,7 +475,7 @@ void Apply(IReadWriteOperations& operations); - bool ExpandResource(Json::Value& target, + bool ExpandResource(ExpandedResource& target, const std::string& publicId, ResourceType level, DicomToJsonFormat format); diff -r 94a7b681b340 -r acd3f72e2a21 OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Thu Mar 10 09:03:24 2022 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Thu Mar 10 19:00:43 2022 +0100 @@ -126,7 +126,7 @@ // List all the patients, studies, series or instances ---------------------- static void AnswerListOfResources(RestApiOutput& output, - ServerIndex& index, + ServerContext& context, const std::list& resources, ResourceType level, bool expand, @@ -140,7 +140,7 @@ if (expand) { Json::Value expanded; - if (index.ExpandResource(expanded, *resource, level, format)) + if (context.ExpandResource(expanded, *resource, level, format)) { answer.append(expanded); } @@ -178,6 +178,7 @@ } ServerIndex& index = OrthancRestApi::GetIndex(call); + ServerContext& context = OrthancRestApi::GetContext(call); std::list result; @@ -207,7 +208,7 @@ index.GetAllUuids(result, resourceType); } - AnswerListOfResources(call.GetOutput(), index, result, resourceType, call.HasArgument("expand"), + AnswerListOfResources(call.GetOutput(), context, result, resourceType, call.HasArgument("expand"), OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human)); } @@ -226,6 +227,12 @@ .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; @@ -233,9 +240,22 @@ 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()); + } + } + Json::Value json; - if (OrthancRestApi::GetIndex(call).ExpandResource( - json, call.GetUriComponent("id", ""), resourceType, format)) + if (OrthancRestApi::GetContext(call).ExpandResource( + json, call.GetUriComponent("id", ""), resourceType, format)) // TODO, requestedTags)) { call.GetOutput().AnswerJson(json); } @@ -2845,11 +2865,11 @@ } void Answer(RestApiOutput& output, - ServerIndex& index, + ServerContext& context, ResourceType level, bool expand) const { - AnswerListOfResources(output, index, resources_, level, expand, format_); + AnswerListOfResources(output, context, resources_, level, expand, format_); } }; } @@ -2862,6 +2882,7 @@ static const char* const KEY_LEVEL = "Level"; static const char* const KEY_LIMIT = "Limit"; static const char* const KEY_QUERY = "Query"; + static const char* const KEY_REQUESTED_TAGS = "RequestedTags"; static const char* const KEY_SINCE = "Since"; if (call.IsDocumentation()) @@ -2884,6 +2905,13 @@ "Limit the number of reported resources", false) .SetRequestField(KEY_SINCE, RestApiCallDocumentation::Type_Number, "Show only the resources since the provided index (in conjunction with `Limit`)", false) + .SetRequestField(KEY_REQUESTED_TAGS, RestApiCallDocumentation::Type_JsonListOfStrings, + "A list of DICOM tags to include in the response (applicable only if \"Expand\" is set to true). " + "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 " + "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 " @@ -2893,6 +2921,8 @@ ServerContext& context = OrthancRestApi::GetContext(call); + // MORE_TAGS: TODO: handle RequestedTags + Json::Value request; if (!call.ParseJsonRequest(request) || request.type() != Json::objectValue) @@ -2997,7 +3027,7 @@ FindVisitor visitor(OrthancRestApi::GetDicomFormat(request, DicomToJsonFormat_Human)); context.Apply(visitor, query, level, since, limit); - visitor.Answer(call.GetOutput(), context.GetIndex(), level, expand); + visitor.Answer(call.GetOutput(), context, level, expand); } } @@ -3054,7 +3084,7 @@ it = a.begin(); it != a.end(); ++it) { Json::Value resource; - if (OrthancRestApi::GetIndex(call).ExpandResource(resource, *it, end, format)) + if (OrthancRestApi::GetContext(call).ExpandResource(resource, *it, end, format)) { result.append(resource); } @@ -3169,7 +3199,7 @@ const DicomToJsonFormat format = OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human); Json::Value resource; - if (OrthancRestApi::GetIndex(call).ExpandResource(resource, current, end, format)) + if (OrthancRestApi::GetContext(call).ExpandResource(resource, current, end, format)) { call.GetOutput().AnswerJson(resource); } @@ -3541,7 +3571,7 @@ it = interest.begin(); it != interest.end(); ++it) { Json::Value item; - if (index.ExpandResource(item, *it, level, format)) + if (OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format)) { if (metadata) { @@ -3564,7 +3594,7 @@ ResourceType level; Json::Value item; if (index.LookupResourceType(level, *it) && - index.ExpandResource(item, *it, level, format)) + OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format)) { if (metadata) { diff -r 94a7b681b340 -r acd3f72e2a21 OrthancServer/Sources/OrthancWebDav.cpp --- a/OrthancServer/Sources/OrthancWebDav.cpp Thu Mar 10 09:03:24 2022 +0100 +++ b/OrthancServer/Sources/OrthancWebDav.cpp Thu Mar 10 19:00:43 2022 +0100 @@ -259,7 +259,7 @@ const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE { Json::Value resource; - if (context_.GetIndex().ExpandResource(resource, publicId, level_, DicomToJsonFormat_Human)) + if (context_.ExpandResource(resource, publicId, level_, DicomToJsonFormat_Human)) { if (success_) { diff -r 94a7b681b340 -r acd3f72e2a21 OrthancServer/Sources/ServerContext.cpp --- a/OrthancServer/Sources/ServerContext.cpp Thu Mar 10 09:03:24 2022 +0100 +++ b/OrthancServer/Sources/ServerContext.cpp Thu Mar 10 19:00:43 2022 +0100 @@ -2092,4 +2092,186 @@ boost::mutex::scoped_lock lock(dynamicOptionsMutex_); isUnknownSopClassAccepted_ = accepted; } + + + static void SerializeExpandedResource(Json::Value& target, + const ExpandedResource& resource, + DicomToJsonFormat format) + { + target = Json::objectValue; + + target["Type"] = GetResourceTypeText(resource.type_, false, true); + target["ID"] = resource.id_; + + switch (resource.type_) + { + case ResourceType_Patient: + break; + + case ResourceType_Study: + target["ParentPatient"] = resource.parentId_; + break; + + case ResourceType_Series: + target["ParentStudy"] = resource.parentId_; + break; + + case ResourceType_Instance: + target["ParentSeries"] = resource.parentId_; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + switch (resource.type_) + { + case ResourceType_Patient: + case ResourceType_Study: + case ResourceType_Series: + { + Json::Value c = Json::arrayValue; + + for (std::list::const_iterator + it = resource.childrenIds_.begin(); it != resource.childrenIds_.end(); ++it) + { + c.append(*it); + } + + if (resource.type_ == ResourceType_Patient) + { + target["Studies"] = c; + } + else if (resource.type_ == ResourceType_Study) + { + target["Series"] = c; + } + else + { + target["Instances"] = c; + } + break; + } + + case ResourceType_Instance: + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + switch (resource.type_) + { + case ResourceType_Patient: + case ResourceType_Study: + break; + + case ResourceType_Series: + if (resource.expectedNumberOfInstances_ < 0) + { + target["ExpectedNumberOfInstances"] = Json::nullValue; + } + else + { + target["ExpectedNumberOfInstances"] = resource.expectedNumberOfInstances_; + } + target["Status"] = resource.status_; + break; + + case ResourceType_Instance: + { + target["FileSize"] = static_cast(resource.fileSize_); + target["FileUuid"] = resource.fileUuid_; + + if (resource.indexInSeries_ < 0) + { + target["IndexInSeries"] = Json::nullValue; + } + else + { + target["IndexInSeries"] = resource.indexInSeries_; + } + + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + + if (!resource.anonymizedFrom_.empty()) + { + target["AnonymizedFrom"] = resource.anonymizedFrom_; + } + + if (!resource.modifiedFrom_.empty()) + { + target["ModifiedFrom"] = resource.modifiedFrom_; + } + + if (resource.type_ == ResourceType_Patient || + resource.type_ == ResourceType_Study || + resource.type_ == ResourceType_Series) + { + target["IsStable"] = resource.isStable_; + + if (!resource.lastUpdate_.empty()) + { + target["LastUpdate"] = resource.lastUpdate_; + } + } + + // serialize tags + + static const char* const MAIN_DICOM_TAGS = "MainDicomTags"; + static const char* const PATIENT_MAIN_DICOM_TAGS = "PatientMainDicomTags"; + + DicomMap mainDicomTags; + resource.tags_.ExtractResourceInformation(mainDicomTags, resource.type_); + + target[MAIN_DICOM_TAGS] = Json::objectValue; + FromDcmtkBridge::ToJson(target[MAIN_DICOM_TAGS], mainDicomTags, format); + + if (resource.type_ == ResourceType_Study) + { + DicomMap patientMainDicomTags; + resource.tags_.ExtractPatientInformation(patientMainDicomTags); + + target[PATIENT_MAIN_DICOM_TAGS] = Json::objectValue; + FromDcmtkBridge::ToJson(target[PATIENT_MAIN_DICOM_TAGS], patientMainDicomTags, format); + } + + } + + + bool ServerContext::ExpandResource(Json::Value& target, + const std::string& publicId, + ResourceType level, + DicomToJsonFormat format) + { + ExpandedResource resource; + + if (GetIndex().ExpandResource(resource, publicId, level, format)) + { + // check the main dicom tags list has not changed since the resource was stored + + if (resource.mainDicomTagsSignature_ != DicomMap::GetMainDicomTagsSignature(resource.type_)) + { + 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."; + } + } + + // MORE_TAGS: TODO: possibly merge missing requested tags from /tags + + SerializeExpandedResource(target, resource, format); + + return true; + } + + return false; + } + } diff -r 94a7b681b340 -r acd3f72e2a21 OrthancServer/Sources/ServerContext.h --- a/OrthancServer/Sources/ServerContext.h Thu Mar 10 09:03:24 2022 +0100 +++ b/OrthancServer/Sources/ServerContext.h Thu Mar 10 19:00:43 2022 +0100 @@ -539,5 +539,11 @@ bool IsUnknownSopClassAccepted(); void SetUnknownSopClassAccepted(bool accepted); + + bool ExpandResource(Json::Value& target, + const std::string& publicId, + ResourceType level, + DicomToJsonFormat format); + }; }