Mercurial > hg > orthanc
changeset 6620:e46d709aa0ca
new experimental config PatientLevelEnabled
| author | Alain Mazy <am@orthanc.team> |
|---|---|
| date | Mon, 16 Mar 2026 12:55:36 +0100 |
| parents | 94ce05ebc75c |
| children | 7c6c02b669a8 |
| files | NEWS OrthancServer/Resources/Configuration.json OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp OrthancServer/Sources/ServerContext.cpp OrthancServer/Sources/ServerContext.h OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp OrthancServer/Sources/main.cpp |
| diffstat | 10 files changed, 115 insertions(+), 24 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Mon Mar 16 10:47:50 2026 +0100 +++ b/NEWS Mon Mar 16 12:55:36 2026 +0100 @@ -4,6 +4,8 @@ General ------- +* New experimental configuration "PatientLevelEnabled" (TODO: work in progree) + REST API --------
--- a/OrthancServer/Resources/Configuration.json Mon Mar 16 10:47:50 2026 +0100 +++ b/OrthancServer/Resources/Configuration.json Mon Mar 16 12:55:36 2026 +0100 @@ -1106,5 +1106,14 @@ // Orthanc <= 1.12.10. This default value can be overwritten per archive, // by providing the "Utf8" field to the "{...}/archive" and // "/tools/create-archives" routes. (new in Orthanc 1.12.11) - "ZipUseUtf8" : false + "ZipUseUtf8" : false, + + // When set to false, this option disables all /patients routes and + // disables patients related sanity checks when performing resource + // modification. This is required e.g when your Orthanc stores + // resources from multiple sources in which the same PatientID might + // be attached to different patients because there is no centralized + // patient id management. (new in Orthanc 1.12.11) + // As of 1.12.11, this is an experimental configuration. + "PatientLevelEnabled": true }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp Mon Mar 16 10:47:50 2026 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp Mon Mar 16 12:55:36 2026 +0100 @@ -1306,18 +1306,22 @@ Register("/instances/{id}/modify", ModifyInstance); Register("/series/{id}/modify", ModifyResource<ResourceType_Series>); Register("/studies/{id}/modify", ModifyResource<ResourceType_Study>); - Register("/patients/{id}/modify", ModifyResource<ResourceType_Patient>); Register("/tools/bulk-modify", BulkModify); Register("/instances/{id}/anonymize", AnonymizeInstance); Register("/series/{id}/anonymize", AnonymizeResource<ResourceType_Series>); Register("/studies/{id}/anonymize", AnonymizeResource<ResourceType_Study>); - Register("/patients/{id}/anonymize", AnonymizeResource<ResourceType_Patient>); Register("/tools/bulk-anonymize", BulkAnonymize); Register("/tools/create-dicom", CreateDicom); Register("/studies/{id}/split", SplitStudy); Register("/studies/{id}/merge", MergeStudy); + + if (context_.IsPatientLevelEnabled()) + { + Register("/patients/{id}/modify", ModifyResource<ResourceType_Patient>); + Register("/patients/{id}/anonymize", AnonymizeResource<ResourceType_Patient>); + } } }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp Mon Mar 16 10:47:50 2026 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp Mon Mar 16 12:55:36 2026 +0100 @@ -862,10 +862,14 @@ void OrthancRestApi::RegisterArchive() { - Register("/patients/{id}/archive", CreateSingleGet<ResourceType_Patient, false /* ZIP */>); - Register("/patients/{id}/archive", CreateSinglePost<ResourceType_Patient, false /* ZIP */>); - Register("/patients/{id}/media", CreateSingleGet<ResourceType_Patient, true /* media */>); - Register("/patients/{id}/media", CreateSinglePost<ResourceType_Patient, true /* media */>); + if (context_.IsPatientLevelEnabled()) + { + Register("/patients/{id}/archive", CreateSingleGet<ResourceType_Patient, false /* ZIP */>); + Register("/patients/{id}/archive", CreateSinglePost<ResourceType_Patient, false /* ZIP */>); + Register("/patients/{id}/media", CreateSingleGet<ResourceType_Patient, true /* media */>); + Register("/patients/{id}/media", CreateSinglePost<ResourceType_Patient, true /* media */>); + } + Register("/series/{id}/archive", CreateSingleGet<ResourceType_Series, false /* ZIP */>); Register("/series/{id}/archive", CreateSinglePost<ResourceType_Series, false /* ZIP */>); Register("/series/{id}/media", CreateSingleGet<ResourceType_Series, true /* media */>);
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Mon Mar 16 10:47:50 2026 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Mon Mar 16 12:55:36 2026 +0100 @@ -4283,14 +4283,20 @@ void OrthancRestApi::RegisterResources() { Register("/instances", ListResources<ResourceType_Instance>); - Register("/patients", ListResources<ResourceType_Patient>); + if (context_.IsPatientLevelEnabled()) + { + Register("/patients", ListResources<ResourceType_Patient>); + } Register("/series", ListResources<ResourceType_Series>); Register("/studies", ListResources<ResourceType_Study>); if (!context_.IsReadOnly()) { Register("/instances/{id}", DeleteSingleResource<ResourceType_Instance>); - Register("/patients/{id}", DeleteSingleResource<ResourceType_Patient>); + if (context_.IsPatientLevelEnabled()) + { + Register("/patients/{id}", DeleteSingleResource<ResourceType_Patient>); + } Register("/series/{id}", DeleteSingleResource<ResourceType_Series>); Register("/studies/{id}", DeleteSingleResource<ResourceType_Study>); @@ -4302,21 +4308,33 @@ } Register("/instances/{id}", GetSingleResource<ResourceType_Instance>); - Register("/patients/{id}", GetSingleResource<ResourceType_Patient>); + if (context_.IsPatientLevelEnabled()) + { + Register("/patients/{id}", GetSingleResource<ResourceType_Patient>); + } Register("/series/{id}", GetSingleResource<ResourceType_Series>); Register("/studies/{id}", GetSingleResource<ResourceType_Study>); Register("/instances/{id}/statistics", GetResourceStatistics); - Register("/patients/{id}/statistics", GetResourceStatistics); + if (context_.IsPatientLevelEnabled()) + { + Register("/patients/{id}/statistics", GetResourceStatistics); + } Register("/studies/{id}/statistics", GetResourceStatistics); Register("/series/{id}/statistics", GetResourceStatistics); - Register("/patients/{id}/shared-tags", GetSharedTags); + if (context_.IsPatientLevelEnabled()) + { + Register("/patients/{id}/shared-tags", GetSharedTags); + } Register("/series/{id}/shared-tags", GetSharedTags); Register("/studies/{id}/shared-tags", GetSharedTags); Register("/instances/{id}/module", GetModule<ResourceType_Instance, DicomModule_Instance>); - Register("/patients/{id}/module", GetModule<ResourceType_Patient, DicomModule_Patient>); + if (context_.IsPatientLevelEnabled()) + { + Register("/patients/{id}/module", GetModule<ResourceType_Patient, DicomModule_Patient>); + } Register("/series/{id}/module", GetModule<ResourceType_Series, DicomModule_Series>); Register("/studies/{id}/module", GetModule<ResourceType_Study, DicomModule_Study>); Register("/studies/{id}/module-patient", GetModule<ResourceType_Study, DicomModule_Patient>); @@ -4347,11 +4365,17 @@ Register("/instances/{id}/header", GetInstanceHeader); Register("/instances/{id}/numpy", GetNumpyInstance); // New in Orthanc 1.10.0 - Register("/patients/{id}/protected", IsProtectedPatient); + if (context_.IsPatientLevelEnabled()) + { + Register("/patients/{id}/protected", IsProtectedPatient); + } if (!context_.IsReadOnly()) { - Register("/patients/{id}/protected", SetPatientProtection); + if (context_.IsPatientLevelEnabled()) + { + Register("/patients/{id}/protected", SetPatientProtection); + } } else { @@ -4367,6 +4391,11 @@ for (size_t i = 0; i < resourceTypes.size(); i++) { + if (resourceTypes[i] == "patients" && !context_.IsPatientLevelEnabled()) + { + continue; + } + Register("/" + resourceTypes[i] + "/{id}/metadata", ListMetadata); Register("/" + resourceTypes[i] + "/{id}/metadata/{name}", DeleteMetadata); Register("/" + resourceTypes[i] + "/{id}/metadata/{name}", GetMetadata); @@ -4418,9 +4447,12 @@ Register("/tools/find", Find<FindType_Find>); Register("/tools/count-resources", Find<FindType_Count>); - Register("/patients/{id}/studies", GetChildResources<ResourceType_Patient, ResourceType_Study>); - Register("/patients/{id}/series", GetChildResources<ResourceType_Patient, ResourceType_Series>); - Register("/patients/{id}/instances", GetChildResources<ResourceType_Patient, ResourceType_Instance>); + if (context_.IsPatientLevelEnabled()) + { + Register("/patients/{id}/studies", GetChildResources<ResourceType_Patient, ResourceType_Study>); + Register("/patients/{id}/series", GetChildResources<ResourceType_Patient, ResourceType_Series>); + Register("/patients/{id}/instances", GetChildResources<ResourceType_Patient, ResourceType_Instance>); + } Register("/studies/{id}/series", GetChildResources<ResourceType_Study, ResourceType_Series>); Register("/studies/{id}/instances", GetChildResources<ResourceType_Study, ResourceType_Instance>); Register("/series/{id}/instances", GetChildResources<ResourceType_Series, ResourceType_Instance>); @@ -4432,7 +4464,10 @@ Register("/instances/{id}/study", GetParentResource<ResourceType_Instance, ResourceType_Study>); Register("/instances/{id}/series", GetParentResource<ResourceType_Instance, ResourceType_Series>); - Register("/patients/{id}/instances-tags", GetChildInstancesTags); + if (context_.IsPatientLevelEnabled()) + { + Register("/patients/{id}/instances-tags", GetChildInstancesTags); + } Register("/studies/{id}/instances-tags", GetChildInstancesTags); Register("/series/{id}/instances-tags", GetChildInstancesTags); @@ -4443,7 +4478,10 @@ if (!context_.IsReadOnly()) { - Register("/patients/{id}/reconstruct", ReconstructResource<ResourceType_Patient>); + if (context_.IsPatientLevelEnabled()) + { + Register("/patients/{id}/reconstruct", ReconstructResource<ResourceType_Patient>); + } Register("/studies/{id}/reconstruct", ReconstructResource<ResourceType_Study>); Register("/series/{id}/reconstruct", ReconstructResource<ResourceType_Series>); Register("/instances/{id}/reconstruct", ReconstructResource<ResourceType_Instance>);
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp Mon Mar 16 10:47:50 2026 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp Mon Mar 16 12:55:36 2026 +0100 @@ -100,6 +100,7 @@ static const char* const HAS_EXTENDED_FIND = "HasExtendedFind"; static const char* const READ_ONLY = "ReadOnly"; static const char* const HAS_RESERVE_QUEUE_VALUE = "HasReserveQueueValue"; + static const char* const PATIENT_LEVEL_ENABLED = "PatientLevelEnabled"; if (call.IsDocumentation()) { @@ -153,6 +154,8 @@ "and 'HasReserveQueueValue' (new in Orthanc 1.12.10)") .SetAnswerField(READ_ONLY, RestApiCallDocumentation::Type_Boolean, "Whether Orthanc is running in read only mode (new in Orthanc 1.12.5)") + .SetAnswerField(PATIENT_LEVEL_ENABLED, RestApiCallDocumentation::Type_Boolean, + "Whether Patient level routes and sanity checks are enabled (new in Orthanc 1.12.11)") .SetHttpGetSample("https://orthanc.uclouvain.be/demo/system", true); return; } @@ -186,6 +189,7 @@ result[STORAGE_AREA_PLUGIN] = Json::nullValue; result[DATABASE_BACKEND_PLUGIN] = Json::nullValue; result[READ_ONLY] = context.IsReadOnly(); + result[PATIENT_LEVEL_ENABLED] = context.IsPatientLevelEnabled(); #if ORTHANC_ENABLE_PLUGINS == 1 result[PLUGINS_ENABLED] = true;
--- a/OrthancServer/Sources/ServerContext.cpp Mon Mar 16 10:47:50 2026 +0100 +++ b/OrthancServer/Sources/ServerContext.cpp Mon Mar 16 12:55:36 2026 +0100 @@ -400,6 +400,7 @@ ingestTranscodingOfCompressed_(true), preferredTransferSyntax_(DicomTransferSyntax_LittleEndianExplicit), readOnly_(readOnly), + patientLevelEnabled_(true), deidentifyLogs_(false), serverStartTimeUtc_(boost::posix_time::second_clock::universal_time()) { @@ -624,6 +625,17 @@ } + void ServerContext::SetPatientLevelEnabled(bool enabled) + { + if (enabled) + LOG(WARNING) << "Patient level is enabled"; + else + LOG(WARNING) << "Patient level is disabled"; + + patientLevelEnabled_ = enabled; + } + + void ServerContext::RemoveFile(const std::string& fileUuid, FileContentType type, const std::string& customData)
--- a/OrthancServer/Sources/ServerContext.h Mon Mar 16 10:47:50 2026 +0100 +++ b/OrthancServer/Sources/ServerContext.h Mon Mar 16 12:55:36 2026 +0100 @@ -271,7 +271,8 @@ std::set<DicomTransferSyntax> acceptedTransferSyntaxes_; std::list<std::string> acceptedSopClasses_; // ordered; the most 120 common ones first bool readOnly_; - + bool patientLevelEnabled_; + StoreResult StoreAfterTranscoding(std::string& resultPublicId, DicomInstanceToStore& dicom, StoreInstanceMode mode, @@ -342,6 +343,13 @@ return storageCache_.SetMaximumSize(size); } + void SetPatientLevelEnabled(bool enabled); + + bool IsPatientLevelEnabled() const + { + return patientLevelEnabled_; + } + void SetCompressionEnabled(bool enabled); bool IsCompressionEnabled() const
--- a/OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp Mon Mar 16 10:47:50 2026 +0100 +++ b/OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp Mon Mar 16 12:55:36 2026 +0100 @@ -727,7 +727,7 @@ } } - if (modificationLevel == ResourceType_Study && replacePatientMainDicomTags) + if (GetContext().IsPatientLevelEnabled() && modificationLevel == ResourceType_Study && replacePatientMainDicomTags) { for (std::set<std::string>::const_iterator studyId = parentResources_.begin(); studyId != parentResources_.end(); ++studyId) {
--- a/OrthancServer/Sources/main.cpp Mon Mar 16 10:47:50 2026 +0100 +++ b/OrthancServer/Sources/main.cpp Mon Mar 16 12:55:36 2026 +0100 @@ -1679,6 +1679,8 @@ { OrthancConfiguration::ReaderLock lock; + context.SetPatientLevelEnabled(lock.GetConfiguration().GetBooleanParameter("PatientLevelEnabled", true)); + if (context.IsReadOnly()) { LOG(WARNING) << "READ-ONLY SYSTEM: ignoring these configurations: StorageCompression, StoreMD5ForAttachments, OverwriteInstances, MaximumPatientCount, MaximumStorageSize, MaximumStorageMode, SaveJobs"; @@ -1691,9 +1693,17 @@ // New option in Orthanc 1.4.2 context.SetOverwriteInstances(lock.GetConfiguration().GetBooleanParameter("OverwriteInstances", false)); + unsigned int maximumPatientCount = lock.GetConfiguration().GetUnsignedIntegerParameter("MaximumPatientCount", 0); + unsigned int maximumStorageSize = lock.GetConfiguration().GetUnsignedIntegerParameter("MaximumStorageSize", 0); + + if (!context.IsPatientLevelEnabled() && (maximumPatientCount != 0 || maximumStorageSize != 0)) + { + LOG(ERROR) << "Can not set MaximumPatientCount or MaximumStorageSize when PatientLevelEnabled is set to false since these options require patient recycling."; + } + try { - context.GetIndex().SetMaximumPatientCount(lock.GetConfiguration().GetUnsignedIntegerParameter("MaximumPatientCount", 0)); + context.GetIndex().SetMaximumPatientCount(maximumPatientCount); } catch (...) { @@ -1702,7 +1712,7 @@ try { - uint64_t size = lock.GetConfiguration().GetUnsignedIntegerParameter("MaximumStorageSize", 0); + uint64_t size = maximumStorageSize; context.GetIndex().SetMaximumStorageSize(size * 1024 * 1024); } catch (...)
