# HG changeset patch # User Sebastien Jodogne # Date 1734363097 -3600 # Node ID 280980f6dda3b6cb725d80bf58e4ebbd07c07d7f # Parent a5241defb36f800c7dc362985b61ed773f1881a5# Parent 9e43494cb0543185880164d075331c7078e0cd25 integration find-refactoring->mainline diff -r a5241defb36f -r 280980f6dda3 NEWS --- a/NEWS Wed Oct 09 16:04:41 2024 +0200 +++ b/NEWS Mon Dec 16 16:31:37 2024 +0100 @@ -1,12 +1,14 @@ Pending changes in the mainline =============================== -* Added a "Server" entry in the DICOMWeb job content. -* Fixed parsing of numerical values in QIDO-RS response that prevented, amongst other, - the retrieval of NumberOfStudyRelatedInstances, NumberOfStudyRelatedSeries, ... -* Fixed non latin PatientName values that were empty in some QIDO-RS responses. +* Added a "Server" entry in the DICOMweb job content. +* Fixed parsing of numerical values in QIDO-RS response that prevented, among other, + the retrieval of "NumberOfStudyRelatedInstances", "NumberOfStudyRelatedSeries",... +* Fixed non-Latin "PatientName" values that were empty in some QIDO-RS responses. * Optimized the retrieval of single frame in WADO-RS when no transcoding is required. This greatly improves the download time of multi-frame images in OHIF. +* Optimization when running with an Orthanc that supports the "ExtendedFind" primitive. +* Added support for Orthanc running in "ReadOnly" mode. Version 1.17 (2024-06-05) diff -r a5241defb36f -r 280980f6dda3 Plugin/Configuration.cpp --- a/Plugin/Configuration.cpp Wed Oct 09 16:04:41 2024 +0200 +++ b/Plugin/Configuration.cpp Mon Dec 16 16:31:37 2024 +0100 @@ -278,6 +278,8 @@ Orthanc::ResourceType level = Orthanc::StringToResourceType(levels[i].c_str()); const Json::Value& content = configuration[EXTRA_MAIN_DICOM_TAGS][levels[i]]; + std::set defaultTags; + Orthanc::DicomMap::GetMainDicomTags(defaultTags, level); if (content.size() > 0) { @@ -286,7 +288,10 @@ const std::string& tagName = content[t].asString(); Orthanc::DicomTag tag(0, 0); OrthancPlugins::ParseTag(tag, tagName); - Orthanc::DicomMap::AddMainDicomTag(tag, level); + if (defaultTags.find(tag) == defaultTags.end()) // don't generate an error if the ExtraMainDicomTags is now a default one + { + Orthanc::DicomMap::AddMainDicomTag(tag, level); + } } } } @@ -682,6 +687,10 @@ return GetBooleanValue("EnableMetadataCache", true); } + bool IsReadOnly() + { + return globalConfiguration_->GetBooleanValue("ReadOnly", false); + } MetadataMode GetMetadataMode(Orthanc::ResourceType level) { diff -r a5241defb36f -r 280980f6dda3 Plugin/Configuration.h --- a/Plugin/Configuration.h Wed Oct 09 16:04:41 2024 +0200 +++ b/Plugin/Configuration.h Mon Dec 16 16:31:37 2024 +0100 @@ -142,5 +142,7 @@ unsigned int GetMetadataWorkerThreadsCount(); bool IsMetadataCacheEnabled(); + + bool IsReadOnly(); } } diff -r a5241defb36f -r 280980f6dda3 Plugin/Plugin.cpp --- a/Plugin/Plugin.cpp Wed Oct 09 16:04:41 2024 +0200 +++ b/Plugin/Plugin.cpp Mon Dec 16 16:31:37 2024 +0100 @@ -42,7 +42,10 @@ #define ORTHANC_CORE_MINIMAL_REVISION 0 static const char* const HAS_DELETE = "HasDelete"; - +static const char* const SYSTEM_CAPABILITIES = "Capabilities"; +static const char* const SYSTEM_CAPABILITIES_HAS_EXTENDED_FIND = "HasExtendedFind"; +static const char* const READ_ONLY = "ReadOnly"; +bool isReadOnly_ = false; bool RequestHasKey(const OrthancPluginHttpRequest* request, const char* key) @@ -471,11 +474,41 @@ switch (changeType) { case OrthancPluginChangeType_OrthancStarted: + { OrthancPlugins::Configuration::LoadDicomWebServers(); - break; + + Json::Value system; + if (OrthancPlugins::RestApiGet(system, "/system", false)) + { + bool hasExtendedFind = system.isMember(SYSTEM_CAPABILITIES) + && system[SYSTEM_CAPABILITIES].isMember(SYSTEM_CAPABILITIES_HAS_EXTENDED_FIND) + && system[SYSTEM_CAPABILITIES][SYSTEM_CAPABILITIES_HAS_EXTENDED_FIND].asBool(); + if (hasExtendedFind) + { + LOG(INFO) << "Orthanc supports ExtendedFind."; + SetPluginCanUseExtendedFile(true); + } + else + { + LOG(WARNING) << "Orthanc does not support ExtendedFind. The DICOMWeb plugin will not benefit from some optimizations."; + } + + bool isReadOnly = system.isMember(READ_ONLY) && system[READ_ONLY].asBool(); + + if (isReadOnly) + { + LOG(INFO) << "Orthanc is ReadOnly."; + SetSystemIsReadOnly(true); + } + } + + }; break; case OrthancPluginChangeType_StableSeries: - CacheSeriesMetadata(resourceId); + if (!OrthancPlugins::Configuration::IsReadOnly()) + { + CacheSeriesMetadata(resourceId); + } break; default: @@ -568,13 +601,27 @@ LOG(WARNING) << "URI to the DICOMweb REST API: " << root; - OrthancPlugins::ChunkedRestRegistration< - SearchForStudies /* TODO => Rename as QIDO-RS */, - OrthancPlugins::StowServer::PostCallback>::Apply(root + "studies"); + + if (!OrthancPlugins::Configuration::IsReadOnly()) + { + OrthancPlugins::ChunkedRestRegistration< + SearchForStudies /* TODO => Rename as QIDO-RS */, + OrthancPlugins::StowServer::PostCallback>::Apply(root + "studies"); - OrthancPlugins::ChunkedRestRegistration< - RetrieveDicomStudy /* TODO => Rename as WADO-RS */, - OrthancPlugins::StowServer::PostCallback>::Apply(root + "studies/([^/]*)"); + OrthancPlugins::ChunkedRestRegistration< + RetrieveDicomStudy /* TODO => Rename as WADO-RS */, + OrthancPlugins::StowServer::PostCallback>::Apply(root + "studies/([^/]*)"); + } + else + { + LOG(WARNING) << "READ-ONLY SYSTEM: deactivating STOW-RS routes"; + + OrthancPlugins::ChunkedRestRegistration< + SearchForStudies /* TODO => Rename as QIDO-RS */>::Apply(root + "studies"); + + OrthancPlugins::ChunkedRestRegistration< + RetrieveDicomStudy /* TODO => Rename as WADO-RS */>::Apply(root + "studies/([^/]*)"); + } OrthancPlugins::RegisterRestCallback(root + "instances", true); OrthancPlugins::RegisterRestCallback(root + "series", true); @@ -592,10 +639,19 @@ OrthancPlugins::RegisterRestCallback(root + "servers", true); OrthancPlugins::RegisterRestCallback(root + "servers/([^/]*)", true); - OrthancPlugins::RegisterRestCallback(root + "servers/([^/]*)/stow", true); + + if (!OrthancPlugins::Configuration::IsReadOnly()) + { + OrthancPlugins::RegisterRestCallback(root + "servers/([^/]*)/retrieve", true); + } + else + { + LOG(WARNING) << "READ-ONLY SYSTEM: deactivating 'servers/../retrieve' route"; + } + OrthancPlugins::RegisterRestCallback(root + "servers/([^/]*)/wado", true); + OrthancPlugins::RegisterRestCallback(root + "servers/([^/]*)/stow", true); OrthancPlugins::RegisterRestCallback(root + "servers/([^/]*)/get", true); - OrthancPlugins::RegisterRestCallback(root + "servers/([^/]*)/retrieve", true); OrthancPlugins::RegisterRestCallback(root + "servers/([^/]*)/qido", true); OrthancPlugins::RegisterRestCallback(root + "servers/([^/]*)/delete", true); @@ -610,7 +666,10 @@ OrthancPlugins::RegisterRestCallback(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/rendered", true); OrthancPlugins::RegisterRestCallback(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/frames/([^/]*)/rendered", true); - OrthancPlugins::RegisterRestCallback("/studies/([^/]*)/update-dicomweb-cache", true); + if (!OrthancPlugins::Configuration::IsReadOnly()) + { + OrthancPlugins::RegisterRestCallback("/studies/([^/]*)/update-dicomweb-cache", true); + } OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback); diff -r a5241defb36f -r 280980f6dda3 Plugin/WadoRs.cpp --- a/Plugin/WadoRs.cpp Wed Oct 09 16:04:41 2024 +0200 +++ b/Plugin/WadoRs.cpp Mon Dec 16 16:31:37 2024 +0100 @@ -50,6 +50,28 @@ static std::string instancesMainDicomTagsList; static boost::mutex mainDicomTagsListMutex; +static bool pluginCanUseExtendedFind_ = false; +static bool isSystemReadOnly_ = false; + +void SetPluginCanUseExtendedFile(bool enable) +{ + pluginCanUseExtendedFind_ = enable; +} + +bool CanUseExtendedFile() +{ + return pluginCanUseExtendedFind_; +} + +void SetSystemIsReadOnly(bool isReadOnly) +{ + isSystemReadOnly_ = isReadOnly; +} + +bool IsSystemReadOnly() +{ + return isSystemReadOnly_; +} static std::string GetResourceUri(Orthanc::ResourceType level, const std::string& publicId) @@ -1348,15 +1370,13 @@ const std::string& wadoBase) { const unsigned int workersCount = OrthancPlugins::Configuration::GetMetadataWorkerThreadsCount(); - const bool oneLargeQuery = false; // we keep this code here for future use once we'll have optimized Orthanc API /series/.../instances?full to minimize the SQL queries - // right now, it is faster to call /instances/..?full in each worker but, later, it should be more efficient with a large SQL query in Orthanc if (workersCount > 1 && mode == OrthancPlugins::MetadataMode_Full) { ChildrenMainDicomMaps instancesDicomMaps; std::string seriesDicomUid; - if (oneLargeQuery) + if (CanUseExtendedFile()) // in this case, /series/.../instances?full has been optimized to minimize the SQL queries { GetChildrenMainDicomTags(instancesDicomMaps, seriesDicomUid, Orthanc::ResourceType_Series, seriesOrthancId); for (ChildrenMainDicomMaps::const_iterator it = instancesDicomMaps.begin(); it != instancesDicomMaps.end(); ++it) @@ -1382,7 +1402,7 @@ instancesWorkers.push_back(boost::shared_ptr(new boost::thread(InstanceWorkerThread, threadData))); } - if (oneLargeQuery) + if (CanUseExtendedFile()) // we must correct the bulkRoot { for (ChildrenMainDicomMaps::const_iterator i = instancesDicomMaps.begin(); i != instancesDicomMaps.end(); ++i) { @@ -1448,20 +1468,22 @@ RetrieveSeriesMetadataInternal(instancesIds, writer, cache, OrthancPlugins::MetadataMode_Full, false /* isXml */, seriesOrthancId, studyInstanceUid, seriesInstanceUid, WADO_BASE_PLACEHOLDER); writer.CloseAndGetJsonOutput(serializedSeriesMetadata); - // save in attachments for future use - Orthanc::IBufferCompressor::Compress(compressedSeriesMetadata, compressor, serializedSeriesMetadata); - std::string instancesMd5; - Orthanc::Toolbox::ComputeMD5(instancesMd5, instancesIds); - - std::string cacheContent = "2;" + instancesMd5 + ";" + compressedSeriesMetadata; + if (!IsSystemReadOnly()) + { + // save in attachments for future use + Orthanc::IBufferCompressor::Compress(compressedSeriesMetadata, compressor, serializedSeriesMetadata); + std::string instancesMd5; + Orthanc::Toolbox::ComputeMD5(instancesMd5, instancesIds); - Json::Value putResult; - std::string attachmentUrl = "/series/" + seriesOrthancId + "/attachments/" + SERIES_METADATA_ATTACHMENT_ID; - if (!OrthancPlugins::RestApiPut(putResult, attachmentUrl, cacheContent, false)) - { - LOG(WARNING) << "DicomWEB: failed to write series metadata attachment"; + std::string cacheContent = "2;" + instancesMd5 + ";" + compressedSeriesMetadata; + + Json::Value putResult; + std::string attachmentUrl = "/series/" + seriesOrthancId + "/attachments/" + SERIES_METADATA_ATTACHMENT_ID; + if (!OrthancPlugins::RestApiPut(putResult, attachmentUrl, cacheContent, false)) + { + LOG(WARNING) << "DicomWEB: failed to write series metadata attachment"; + } } - } void CacheSeriesMetadata(const std::string& seriesOrthancId) diff -r a5241defb36f -r 280980f6dda3 Plugin/WadoRs.h --- a/Plugin/WadoRs.h Wed Oct 09 16:04:41 2024 +0200 +++ b/Plugin/WadoRs.h Mon Dec 16 16:31:37 2024 +0100 @@ -102,4 +102,8 @@ const char* url, const OrthancPluginHttpRequest* request); -void SetPluginCanDownloadTranscodedFile(bool enable); \ No newline at end of file +void SetPluginCanDownloadTranscodedFile(bool enable); + +void SetPluginCanUseExtendedFile(bool enable); + +void SetSystemIsReadOnly(bool isReadOnly); \ No newline at end of file diff -r a5241defb36f -r 280980f6dda3 Plugin/WadoRsRetrieveFrames.cpp --- a/Plugin/WadoRsRetrieveFrames.cpp Wed Oct 09 16:04:41 2024 +0200 +++ b/Plugin/WadoRsRetrieveFrames.cpp Mon Dec 16 16:31:37 2024 +0100 @@ -443,7 +443,7 @@ Orthanc::DicomTransferSyntax outputSyntax) { if (OrthancPluginStartMultipartAnswer( - OrthancPlugins::GetGlobalContext(), + OrthancPlugins::GetGlobalContext(), output, "related", GetMimeType(outputSyntax)) != OrthancPluginErrorCode_Success) { throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin, @@ -466,7 +466,7 @@ if (error != OrthancPluginErrorCode_Success) { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); } } @@ -552,7 +552,7 @@ sopInstanceUid, frames.front(), targetSyntax); return; } - else + else { if (!content.RestApiGet("/instances/" + orthancId + "/file", false)) { diff -r a5241defb36f -r 280980f6dda3 TODO --- a/TODO Wed Oct 09 16:04:41 2024 +0200 +++ b/TODO Mon Dec 16 16:31:37 2024 +0100 @@ -10,7 +10,7 @@ curl -H "Accept: multipart/related; type=application/octet-stream" http://localhost:8043/dicom-web/studies/1.2.156.112536.1.2143.25015081191207.14610300430.5/series/1.2.156.112536.1.2143.25015081191207.14610300430.6/instances/1.2.156.112536.1.2143.25015081191207.14610309990.44/frames/3 --output /tmp/out.bin check for these logs: DICOMweb RetrieveFrames: Transcoding instance a7aec17a-e296e51f-2abe8ad8-bc95d57b-4de269d0 to transfer syntax 1.2.840.10008.1.2.1 - Note: this has been partialy handled in 1.18: if no transcoding is needed, we avoid download the full instance from Orthanc + Note: This has been partially handled in 1.18: Tf no transcoding is needed, we avoid downloading the full instance from Orthanc We should very likely implement a cache in the DicomWEB plugin and make sure that, if 3 clients are requesting the same instance at the same time, we only request one transcoding.