Mercurial > hg > orthanc
changeset 6629:71400f1c6143
merge
| author | Alain Mazy <am@orthanc.team> |
|---|---|
| date | Wed, 18 Mar 2026 12:13:09 +0100 |
| parents | 640fc89647a6 (diff) 7f44bfb58e7c (current diff) |
| children | 742fb41bff59 dfa66ab964c9 |
| files | NEWS |
| diffstat | 12 files changed, 146 insertions(+), 39 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Tue Mar 17 16:07:57 2026 +0100 +++ b/NEWS Wed Mar 18 12:13:09 2026 +0100 @@ -25,6 +25,9 @@ * Support for OF and OD value representations in "/instances/{...}/file" with content type "application/dicom+json" (i.e. DICOMweb) and in "/instances/{...}/tags" (contribution by Yusuf Sayıta, Philips). +* New "Content.Resources" field in "DicomModalityStore", "OrthancPeerStore" and "ResourceModification" + jobs that contains JSON objects with "ID" and "Type" of each resource. The "Content.ParentResources" + field that only contains the IDs is preserved for backward compatibility. Lua --- @@ -1737,7 +1740,7 @@ * API version has been upgraded to 3 * "/modalities/{id}/query": New argument "Normalize" can be set to "false" to bypass the automated correction of outgoing C-FIND queries -* Reporting of "ParentResources" in "DicomModalityStore" and "DicomModalityStore" jobs +* Reporting of "ParentResources" in "DicomModalityStore", "OrthancPeerStore" and "ResourceModification" jobs Plugins -------
--- a/OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.cpp Tue Mar 17 16:07:57 2026 +0100 +++ b/OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.cpp Wed Mar 18 12:13:09 2026 +0100 @@ -27,6 +27,7 @@ #include "../OrthancException.h" #include "../SerializationToolbox.h" +#include "../Logging.h" #include <cassert> @@ -130,12 +131,11 @@ } - void SetOfInstancesJob::AddParentResource(const std::string &resource) + void SetOfInstancesJob::AddParentResource(const std::string &resource, ResourceType level) { - parentResources_.insert(resource); + parentResources_[resource] = level; } - void SetOfInstancesJob::AddInstance(const std::string& instance) { AddCommand(new InstanceCommand(*this, instance)); @@ -201,28 +201,46 @@ static const char* KEY_TRAILING_STEP = "TrailingStep"; static const char* KEY_FAILED_INSTANCES = "FailedInstances"; - static const char* KEY_PARENT_RESOURCES = "ParentResources"; + static const char* KEY_PARENT_RESOURCES = "ParentResources"; // old style but we keep it for backward compatibility + static const char* KEY_RESOURCES = "Resources"; // new style with the Resource type + + + static void SerializeResources(Json::Value& target, const std::map<std::string, ResourceType>& parentResources, bool includeParentResourcesField) + { + if (!parentResources.empty()) + { + target[KEY_RESOURCES] = Json::arrayValue; + SerializationToolbox::WriteMapOfResourcesAndTypes(target[KEY_RESOURCES], parentResources); + + if (includeParentResourcesField) + { + std::set<std::string> keys; + for (std::map<std::string, ResourceType>::const_iterator it = parentResources.begin(); it != parentResources.end(); ++it) + { + keys.insert(it->first); + } + + SerializationToolbox::WriteSetOfStrings(target, keys, KEY_PARENT_RESOURCES); + } + } + } void SetOfInstancesJob::GetPublicContent(Json::Value& target) const { SetOfCommandsJob::GetPublicContent(target); target["InstancesCount"] = static_cast<uint32_t>(GetInstancesCount()); target["FailedInstancesCount"] = static_cast<uint32_t>(failedInstances_.size()); - - if (!parentResources_.empty()) - { - SerializationToolbox::WriteSetOfStrings(target, parentResources_, KEY_PARENT_RESOURCES); - } + + SerializeResources(target, parentResources_, true); } - bool SetOfInstancesJob::Serialize(Json::Value& target) const { if (SetOfCommandsJob::Serialize(target)) { target[KEY_TRAILING_STEP] = hasTrailingStep_; SerializationToolbox::WriteSetOfStrings(target, failedInstances_, KEY_FAILED_INSTANCES); - SerializationToolbox::WriteSetOfStrings(target, parentResources_, KEY_PARENT_RESOURCES); + SerializeResources(target, parentResources_, false); return true; } else @@ -237,10 +255,13 @@ { SerializationToolbox::ReadSetOfStrings(failedInstances_, source, KEY_FAILED_INSTANCES); - if (source.isMember(KEY_PARENT_RESOURCES)) + if (source.isMember(KEY_PARENT_RESOURCES) && !source.isMember(KEY_RESOURCES)) { - // Backward compatibility with Orthanc <= 1.5.6 - SerializationToolbox::ReadSetOfStrings(parentResources_, source, KEY_PARENT_RESOURCES); + LOG(ERROR) << "Unable to read the " << KEY_PARENT_RESOURCES << " of a job that has been saved with the previous version of Orthanc"; + } + else if (source.isMember(KEY_RESOURCES) && source[KEY_RESOURCES].isArray()) + { + SerializationToolbox::ReadMapOfResourcesAndTypes(parentResources_, source, KEY_RESOURCES); } if (source.isMember(KEY_TRAILING_STEP))
--- a/OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.h Tue Mar 17 16:07:57 2026 +0100 +++ b/OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.h Wed Mar 18 12:13:09 2026 +0100 @@ -28,6 +28,7 @@ #include "SetOfCommandsJob.h" #include <set> +#include <map> namespace Orthanc { @@ -40,7 +41,7 @@ bool hasTrailingStep_; std::set<std::string> failedInstances_; - std::set<std::string> parentResources_; + std::map<std::string, ResourceType> parentResources_; protected: virtual bool HandleInstance(const std::string& instance) = 0; @@ -57,7 +58,7 @@ // Only used for reporting in the public content // https://groups.google.com/d/msg/orthanc-users/9GCV88GLEzw/6wAgP_PRAgAJ - void AddParentResource(const std::string& resource); + void AddParentResource(const std::string& resource, ResourceType level); void AddInstance(const std::string& instance);
--- a/OrthancFramework/Sources/SerializationToolbox.cpp Tue Mar 17 16:07:57 2026 +0100 +++ b/OrthancFramework/Sources/SerializationToolbox.cpp Wed Mar 18 12:13:09 2026 +0100 @@ -373,6 +373,35 @@ } } + void SerializationToolbox::ReadMapOfResourcesAndTypes(std::map<std::string, ResourceType>& target, + const Json::Value& value, + const std::string& field) + { + if (!value[field].isArray()) + { + throw OrthancException(ErrorCode_BadFileFormat, "Array expected in field: " + field); + } + + target.clear(); + + for (Json::ArrayIndex i = 0; i < value[field].size(); ++i) + { + target[value[field][i]["ID"].asString()] = StringToResourceType(value[field][i]["Type"].asString().c_str()); + } + } + + void SerializationToolbox::WriteMapOfResourcesAndTypes(Json::Value& targetArray, + const std::map<std::string, ResourceType>& values) + { + for (std::map<std::string, ResourceType>::const_iterator it = values.begin(); it != values.end(); ++it) + { + Json::Value resource; + resource["ID"] = it->first; + resource["Type"] = EnumerationToString(it->second); + targetArray.append(resource); + } + } + void SerializationToolbox::WriteArrayOfStrings(Json::Value& target, const std::vector<std::string>& values,
--- a/OrthancFramework/Sources/SerializationToolbox.h Tue Mar 17 16:07:57 2026 +0100 +++ b/OrthancFramework/Sources/SerializationToolbox.h Wed Mar 18 12:13:09 2026 +0100 @@ -90,6 +90,10 @@ const Json::Value& value, const std::string& field); + static void ReadMapOfResourcesAndTypes(std::map<std::string, ResourceType>& target, + const Json::Value& value, + const std::string& field); + static void WriteArrayOfStrings(Json::Value& target, const std::vector<std::string>& values, const std::string& field); @@ -105,6 +109,9 @@ static void WriteSetOfStrings(Json::Value& targetArray, const std::set<std::string>& values); + static void WriteMapOfResourcesAndTypes(Json::Value& targetArray, + const std::map<std::string, ResourceType>& values); + static void WriteSetOfTags(Json::Value& target, const std::set<DicomTag>& tags, const std::string& field);
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp Tue Mar 17 16:07:57 2026 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp Wed Mar 18 12:13:09 2026 +0100 @@ -433,10 +433,13 @@ it = resources.begin(); it != resources.end(); ++it) { std::list<std::string> instances; - context.GetIndex().GetChildInstances(instances, *it); + ResourceType level; + context.GetIndex().LookupResourceType(level, *it); + + context.GetIndex().GetChildInstances(instances, *it, level); job->AddInstances(instances); - job->AddParentResource(*it); + job->AddParentResource(*it, level); } job->PerformSanityChecks();
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp Tue Mar 17 16:07:57 2026 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp Wed Mar 18 12:13:09 2026 +0100 @@ -1446,9 +1446,12 @@ "This string is not a valid Orthanc identifier: " + stripped); } - job.AddParentResource(stripped); // New in Orthanc 1.5.7 + ResourceType level; + context.GetIndex().LookupResourceType(level, stripped); + + job.AddParentResource(stripped, level); // New in Orthanc 1.5.7 - context.AddChildInstances(job, stripped); + context.AddChildInstances(job, stripped, level); if (logExportedResources) {
--- a/OrthancServer/Sources/ServerContext.cpp Tue Mar 17 16:07:57 2026 +0100 +++ b/OrthancServer/Sources/ServerContext.cpp Wed Mar 18 12:13:09 2026 +0100 @@ -1810,10 +1810,11 @@ void ServerContext::AddChildInstances(SetOfInstancesJob& job, - const std::string& publicId) + const std::string& publicId, + ResourceType level) { std::list<std::string> instances; - GetIndex().GetChildInstances(instances, publicId); + GetIndex().GetChildInstances(instances, publicId, level); job.Reserve(job.GetInstancesCount() + instances.size());
--- a/OrthancServer/Sources/ServerContext.h Tue Mar 17 16:07:57 2026 +0100 +++ b/OrthancServer/Sources/ServerContext.h Wed Mar 18 12:13:09 2026 +0100 @@ -514,7 +514,8 @@ bool HasPlugins() const; void AddChildInstances(SetOfInstancesJob& job, - const std::string& publicId); + const std::string& publicId, + ResourceType level); void SignalUpdatedModalities();
--- a/OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp Tue Mar 17 16:07:57 2026 +0100 +++ b/OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp Wed Mar 18 12:13:09 2026 +0100 @@ -729,7 +729,7 @@ if (GetContext().IsPatientLevelEnabled() && modificationLevel == ResourceType_Study && replacePatientMainDicomTags) { - for (std::set<std::string>::const_iterator studyId = parentResources_.begin(); studyId != parentResources_.end(); ++studyId) + for (std::map<std::string, ResourceType>::const_iterator it = parentResources_.begin(); it != parentResources_.end(); ++it) { // When modifying a study, you may not modify patient tags as you wish. // - If this is the patient's only study, you may modify all patient tags. This could be performed in 2 steps (modify the patient and then, the study) but, @@ -737,6 +737,7 @@ // - If the patient already has other studies, you may only 'attach' the study to an existing patient by modifying // all patient tags from the study to match those of the target patient. // - Otherwise, you can't modify the patient tags + const std::string& studyId = it->first; std::string targetPatientId; if (modification_->IsReplaced(DICOM_TAG_PATIENT_ID)) @@ -746,7 +747,7 @@ else { FindRequest request(ResourceType_Study); - request.SetOrthancStudyId(*studyId); + request.SetOrthancStudyId(studyId); request.SetRetrieveMainDicomTags(true); FindResponse response; @@ -788,7 +789,7 @@ bool targetPatientHasOtherStudies = childrenIds.size() > 1; if (childrenIds.size() == 1) { - targetPatientHasOtherStudies = (childrenIds.find(*studyId) == childrenIds.end()); // if the patient has one study that is not the one being modified + targetPatientHasOtherStudies = (childrenIds.find(studyId) == childrenIds.end()); // if the patient has one study that is not the one being modified } if (targetPatientHasOtherStudies)
--- a/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.cpp Tue Mar 17 16:07:57 2026 +0100 +++ b/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.cpp Wed Mar 18 12:13:09 2026 +0100 @@ -371,6 +371,7 @@ static const char* KEY_FAILED_INSTANCES = "FailedInstances"; static const char* KEY_PARENT_RESOURCES = "ParentResources"; + static const char* KEY_RESOURCES = "Resources"; static const char* KEY_DESCRIPTION = "Description"; static const char* KEY_PERMISSIVE = "Permissive"; static const char* KEY_USER_DATA = "UserData"; @@ -382,6 +383,26 @@ static const char* KEY_KEEP_SOURCE = "KeepSource"; static const char* KEY_WORKERS_COUNT = "WorkersCount"; + static void SerializeResources(Json::Value& target, const std::map<std::string, ResourceType>& parentResources, bool includeParentResourcesField) + { + if (!parentResources.empty()) + { + target[KEY_RESOURCES] = Json::arrayValue; + SerializationToolbox::WriteMapOfResourcesAndTypes(target[KEY_RESOURCES], parentResources); + + if (includeParentResourcesField) + { + std::set<std::string> keys; + for (std::map<std::string, ResourceType>::const_iterator it = parentResources.begin(); it != parentResources.end(); ++it) + { + keys.insert(it->first); + } + + SerializationToolbox::WriteSetOfStrings(target, keys, KEY_PARENT_RESOURCES); + } + } + } + void ThreadedSetOfInstancesJob::GetPublicContent(Json::Value& target) const { @@ -391,10 +412,7 @@ target[KEY_INSTANCES_COUNT] = static_cast<uint32_t>(GetInstancesCount()); target[KEY_FAILED_INSTANCES_COUNT] = static_cast<uint32_t>(failedInstances_.size()); - if (!parentResources_.empty()) - { - SerializationToolbox::WriteSetOfStrings(target, parentResources_, KEY_PARENT_RESOURCES); - } + SerializeResources(target, parentResources_, true); } @@ -417,7 +435,7 @@ SerializationToolbox::WriteSetOfStrings(target, instancesToProcess_, KEY_INSTANCES); SerializationToolbox::WriteSetOfStrings(target, failedInstances_, KEY_FAILED_INSTANCES); - SerializationToolbox::WriteSetOfStrings(target, parentResources_, KEY_PARENT_RESOURCES); + SerializeResources(target, parentResources_, false); return true; } @@ -439,10 +457,29 @@ { SerializationToolbox::ReadSetOfStrings(failedInstances_, source, KEY_FAILED_INSTANCES); - if (source.isMember(KEY_PARENT_RESOURCES)) + if (source.isMember(KEY_PARENT_RESOURCES) && !source.isMember(KEY_RESOURCES)) { - // Backward compatibility with Orthanc <= 1.5.6 - SerializationToolbox::ReadSetOfStrings(parentResources_, source, KEY_PARENT_RESOURCES); + // Backward compatibility with Orthanc <= 1.12.11 (a KEY_PARENT_RESOURCES field with the resources ids) + std::set<std::string> parentResources; + SerializationToolbox::ReadSetOfStrings(parentResources, source, KEY_PARENT_RESOURCES); + + for (std::set<std::string>::const_iterator it = parentResources.begin(); it != parentResources.end(); ++it) + { + try + { + ResourceType level; + context.GetIndex().LookupResourceType(level, *it); + parentResources_[*it] = level; + } + catch(...) + { + // ignore errors, the resource might have disappear + } + } + } + else if (source.isMember(KEY_RESOURCES) && source[KEY_RESOURCES].isArray()) + { + SerializationToolbox::ReadMapOfResourcesAndTypes(parentResources_, source, KEY_RESOURCES); } if (source.isMember(KEY_KEEP_SOURCE)) @@ -635,11 +672,11 @@ } - void ThreadedSetOfInstancesJob::AddParentResource(const std::string &resource) + void ThreadedSetOfInstancesJob::AddParentResource(const std::string &resource, ResourceType level) { boost::recursive_mutex::scoped_lock lock(mutex_); - parentResources_.insert(resource); + parentResources_[resource] = level; } }
--- a/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.h Tue Mar 17 16:07:57 2026 +0100 +++ b/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.h Wed Mar 18 12:13:09 2026 +0100 @@ -71,7 +71,7 @@ protected: mutable boost::recursive_mutex mutex_; - std::set<std::string> parentResources_; + std::map<std::string, ResourceType> parentResources_; public: ThreadedSetOfInstancesJob(ServerContext& context, @@ -129,7 +129,7 @@ void AddInstances(const std::list<std::string>& instances); - void AddParentResource(const std::string &resource); + void AddParentResource(const std::string &resource, ResourceType level); bool IsPermissive() const;
