Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package orthanc-dicomweb for openSUSE:Factory checked in at 2023-09-06 18:56:59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/orthanc-dicomweb (Old) and /work/SRC/openSUSE:Factory/.orthanc-dicomweb.new.1766 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "orthanc-dicomweb" Wed Sep 6 18:56:59 2023 rev:13 rq:1108997 version:1.15 Changes: -------- --- /work/SRC/openSUSE:Factory/orthanc-dicomweb/orthanc-dicomweb.changes 2023-07-12 17:27:56.446747622 +0200 +++ /work/SRC/openSUSE:Factory/.orthanc-dicomweb.new.1766/orthanc-dicomweb.changes 2023-09-06 18:58:47.920853588 +0200 @@ -1,0 +2,16 @@ +Sun Sep 3 22:23:04 UTC 2023 - Axel Braun <axel.br...@gmx.de> + +- Version 1.15 + +* speed improvement: + - Now storing the output of /dicom-web/studies/../series/../metadata route in an attachment that can be used + by the "Full" mode. + The json file is gzipped and stored in attachment 4301 everytime a series is stable or the first time + its /dicom-web/studies/../series/../metadata route is called in "Full" mode if the attachment does not exist yet. + A new route /studies/{orthancId}/update-dicomweb-cache has also been added to allow e.g. the Housekeeper plugin + to generate these attachment for old studies. + This cache can be disabled by setting "EnableMetadataCache" to false. However, disabling the cache + won't delete the already cached data. You may call DELETE /series/../attachments/4031 to clear the cache. +* framework.diff added to compile against Orthanc framework + +------------------------------------------------------------------- Old: ---- OrthancDicomWeb-1.14.tar.gz New: ---- OrthancDicomWeb-1.15.tar.gz framework.diff ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ orthanc-dicomweb.spec ++++++ --- /var/tmp/diff_new_pack.WXP8PL/_old 2023-09-06 18:58:50.080930590 +0200 +++ /var/tmp/diff_new_pack.WXP8PL/_new 2023-09-06 18:58:50.084930733 +0200 @@ -21,7 +21,7 @@ Summary: WebViewer plugin for Orthanc License: AGPL-3.0-or-later Group: Productivity/Graphics/Viewers -Version: 1.14 +Version: 1.15 Release: 0 URL: https://orthanc-server.com Source0: https://www.orthanc-server.com/downloads/get.php?path=/plugin-dicom-web/OrthancDicomWeb-%{version}.tar.gz @@ -35,6 +35,7 @@ Source8: babel-polyfill-6.26.0.min.js.gz Source9: orthanc-dicomweb-readme.SUSE Source10: dicomweb.json +Patch0: framework.diff BuildRequires: cmake BuildRequires: e2fsprogs-devel @@ -64,6 +65,7 @@ %prep %setup -q -n OrthancDicomWeb-%{version} +%autopatch -p1 #OrthanPlugins may ask for additional files to be loaded #Putting them into this folder prevents download of sources from the web ++++++ OrthancDicomWeb-1.14.tar.gz -> OrthancDicomWeb-1.15.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OrthancDicomWeb-1.14/.hg_archival.txt new/OrthancDicomWeb-1.15/.hg_archival.txt --- old/OrthancDicomWeb-1.14/.hg_archival.txt 2023-07-05 14:13:54.000000000 +0200 +++ new/OrthancDicomWeb-1.15/.hg_archival.txt 2023-08-24 16:29:33.000000000 +0200 @@ -1,6 +1,6 @@ repo: d5f45924411123cfd02d035fd50b8e37536eadef -node: b208c07f0fcea822e44208ee4d9927ea0af803ee -branch: OrthancDicomWeb-1.14 +node: 8ccaf9f005a783c7ccd0a98aa438c58bd07a922a +branch: OrthancDicomWeb-1.15 latesttag: null -latesttagdistance: 540 -changessincelatesttag: 572 +latesttagdistance: 549 +changessincelatesttag: 581 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OrthancDicomWeb-1.14/CMakeLists.txt new/OrthancDicomWeb-1.15/CMakeLists.txt --- old/OrthancDicomWeb-1.14/CMakeLists.txt 2023-07-05 14:13:54.000000000 +0200 +++ new/OrthancDicomWeb-1.15/CMakeLists.txt 2023-08-24 16:29:33.000000000 +0200 @@ -22,13 +22,13 @@ project(OrthancDicomWeb) -set(ORTHANC_DICOM_WEB_VERSION "1.14") +set(ORTHANC_DICOM_WEB_VERSION "1.15") if (ORTHANC_DICOM_WEB_VERSION STREQUAL "mainline") set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") else() - set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.12.1") + set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "daf4807631c5") # TODO: upgrade to 1.12.2 when available set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") endif() @@ -85,7 +85,8 @@ set(ENABLE_PUGIXML ON) set(ENABLE_MODULE_JOBS OFF) set(USE_BOOST_ICONV ON) - + set(ENABLE_ZLIB ON) + include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake) include_directories(${ORTHANC_FRAMEWORK_ROOT}) endif() @@ -204,6 +205,8 @@ add_dependencies(OrthancDicomWeb AutogeneratedTarget) +DefineSourceBasenameForTarget(OrthancDicomWeb) + message("Setting the version of the library to ${ORTHANC_DICOM_WEB_VERSION}") add_definitions(-DORTHANC_DICOM_WEB_VERSION="${ORTHANC_DICOM_WEB_VERSION}") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OrthancDicomWeb-1.14/NEWS new/OrthancDicomWeb-1.15/NEWS --- old/OrthancDicomWeb-1.14/NEWS 2023-07-05 14:13:54.000000000 +0200 +++ new/OrthancDicomWeb-1.15/NEWS 2023-08-24 16:29:33.000000000 +0200 @@ -1,3 +1,16 @@ +Version 1.15 (2023-08-24) +========================= + +* speed improvement: + - Now storing the output of /dicom-web/studies/../series/../metadata route in an attachment that can be used + by the "Full" mode. + The json file is gzipped and stored in attachment 4301 everytime a series is stable or the first time + its /dicom-web/studies/../series/../metadata route is called in "Full" mode if the attachment does not exist yet. + A new route /studies/{orthancId}/update-dicomweb-cache has also been added to allow e.g. the Housekeeper plugin + to generate these attachment for old studies. + This cache can be disabled by setting "EnableMetadataCache" to false. However, disabling the cache + won't delete the already cached data. You may call DELETE /series/../attachments/4031 to clear the cache. + Version 1.14 (2023-07-05) ========================= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OrthancDicomWeb-1.14/Plugin/Configuration.cpp new/OrthancDicomWeb-1.15/Plugin/Configuration.cpp --- old/OrthancDicomWeb-1.14/Plugin/Configuration.cpp 2023-07-05 14:13:54.000000000 +0200 +++ new/OrthancDicomWeb-1.15/Plugin/Configuration.cpp 2023-08-24 16:29:33.000000000 +0200 @@ -676,6 +676,11 @@ return GetUnsignedIntegerValue("MetadataWorkerThreadsCount", 4); } + bool IsMetadataCacheEnabled() + { + return GetBooleanValue("EnableMetadataCache", true); + } + MetadataMode GetMetadataMode(Orthanc::ResourceType level) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OrthancDicomWeb-1.14/Plugin/Configuration.h new/OrthancDicomWeb-1.15/Plugin/Configuration.h --- old/OrthancDicomWeb-1.14/Plugin/Configuration.h 2023-07-05 14:13:54.000000000 +0200 +++ new/OrthancDicomWeb-1.15/Plugin/Configuration.h 2023-08-24 16:29:33.000000000 +0200 @@ -48,7 +48,7 @@ enum MetadataMode { - MetadataMode_Full, // Read all the DICOM instances from the storage area + MetadataMode_Full, // Read all the DICOM instances from the storage area and store them in an attachment on StableSeries event MetadataMode_MainDicomTags, // Only use the Orthanc database (main DICOM tags only) MetadataMode_Extrapolate // Extrapolate user-specified tags from a few DICOM instances }; @@ -139,5 +139,7 @@ void SaveDicomWebServers(); unsigned int GetMetadataWorkerThreadsCount(); + + bool IsMetadataCacheEnabled(); } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OrthancDicomWeb-1.14/Plugin/DicomWebFormatter.cpp new/OrthancDicomWeb-1.15/Plugin/DicomWebFormatter.cpp --- old/OrthancDicomWeb-1.14/Plugin/DicomWebFormatter.cpp 2023-07-05 14:13:54.000000000 +0200 +++ new/OrthancDicomWeb-1.15/Plugin/DicomWebFormatter.cpp 2023-08-24 16:29:33.000000000 +0200 @@ -165,7 +165,7 @@ first_(true) { if (context_ == NULL || - output_ == NULL) + (isXml_ && output_ == NULL)) // allow no output when working with Json output. { throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } @@ -289,8 +289,8 @@ } - void DicomWebFormatter::HttpWriter::AddDicomWebSerializedJson(const void* data, - size_t size) + void DicomWebFormatter::HttpWriter::AddDicomWebInstanceSerializedJson(const void* data, + size_t size) { if (isXml_) { @@ -318,6 +318,41 @@ jsonBuffer_.AddChunk(data, size); } + void DicomWebFormatter::HttpWriter::AddDicomWebSeriesSerializedJson(const void* data, + size_t size) + { + if (isXml_) + { + // This function can only be used in the JSON case + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + +#if !defined(NDEBUG) // In debug mode, check that the value is actually a JSON string + Json::Value json; + if (!OrthancPlugins::ReadJson(json, data, size)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } +#endif + + if (size <= 2 || + reinterpret_cast<const char*>(data)[0] != '[' || + reinterpret_cast<const char*>(data)[size-1] != ']') + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "The series metadata json does not contain an array."); + } + + if (first_) + { + first_ = false; + } + else + { + jsonBuffer_.AddChunk(","); + } + + jsonBuffer_.AddChunk(reinterpret_cast<const char*>(data) + 1, size - 2); // remove leading and trailing [] + } void DicomWebFormatter::HttpWriter::Send() { @@ -331,6 +366,15 @@ } } + void DicomWebFormatter::HttpWriter::CloseAndGetJsonOutput(std::string& target) + { + if (!isXml_) + { + jsonBuffer_.AddChunk("]"); + + jsonBuffer_.Flatten(target); + } + } void DicomWebFormatter::HttpWriter::AddInstance(const DicomInstance& instance, const std::string& bulkRoot) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OrthancDicomWeb-1.14/Plugin/DicomWebFormatter.h new/OrthancDicomWeb-1.15/Plugin/DicomWebFormatter.h --- old/OrthancDicomWeb-1.14/Plugin/DicomWebFormatter.h 2023-07-05 14:13:54.000000000 +0200 +++ new/OrthancDicomWeb-1.15/Plugin/DicomWebFormatter.h 2023-08-24 16:29:33.000000000 +0200 @@ -115,11 +115,16 @@ void AddOrthancJson(const Json::Value& value); - void AddDicomWebSerializedJson(const void* data, - size_t size); + void AddDicomWebInstanceSerializedJson(const void* data, + size_t size); + + void AddDicomWebSeriesSerializedJson(const void* data, + size_t size); void Send(); + void CloseAndGetJsonOutput(std::string& target); + void AddInstance(const DicomInstance& instance, const std::string& bulkRoot); }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OrthancDicomWeb-1.14/Plugin/Plugin.cpp new/OrthancDicomWeb-1.15/Plugin/Plugin.cpp --- old/OrthancDicomWeb-1.14/Plugin/Plugin.cpp 2023-07-05 14:13:54.000000000 +0200 +++ new/OrthancDicomWeb-1.15/Plugin/Plugin.cpp 2023-08-24 16:29:33.000000000 +0200 @@ -471,6 +471,10 @@ OrthancPlugins::Configuration::LoadDicomWebServers(); break; + case OrthancPluginChangeType_StableSeries: + CacheSeriesMetadata(resourceId); + break; + default: break; } @@ -599,6 +603,8 @@ OrthancPlugins::RegisterRestCallback<RetrieveInstanceRendered>(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/rendered", true); OrthancPlugins::RegisterRestCallback<RetrieveFrameRendered>(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/frames/([^/]*)/rendered", true); + OrthancPlugins::RegisterRestCallback<UpdateSeriesMetadataCache>("/studies/([^/]*)/update-dicomweb-cache", true); + OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OrthancDicomWeb-1.14/Plugin/WadoRs.cpp new/OrthancDicomWeb-1.15/Plugin/WadoRs.cpp --- old/OrthancDicomWeb-1.14/Plugin/WadoRs.cpp 2023-07-05 14:13:54.000000000 +0200 +++ new/OrthancDicomWeb-1.15/Plugin/WadoRs.cpp 2023-08-24 16:29:33.000000000 +0200 @@ -29,12 +29,18 @@ #include <HttpServer/HttpContentNegociation.h> #include <Logging.h> #include <Toolbox.h> +#include <SerializationToolbox.h> #include <MultiThreading/SharedMessageQueue.h> +#include <Compression/GzipCompressor.h> #include <memory> #include <boost/thread/mutex.hpp> #include <boost/thread.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/algorithm/string/predicate.hpp> +static const std::string SERIES_METADATA_ATTACHMENT_ID = "4301"; +static std::string WADO_BASE_PLACEHOLDER = "$WADO_BASE_PLACEHOLDER$"; static const char* const MAIN_DICOM_TAGS = "MainDicomTags"; static const char* const REQUESTED_TAGS = "RequestedTags"; static const char* const INSTANCES = "Instances"; @@ -878,7 +884,7 @@ { if (buffer.RestApiGet("/instances/" + orthancId + "/attachments/4444/data", false)) { - writer.AddDicomWebSerializedJson(buffer.GetData(), buffer.GetSize()); + writer.AddDicomWebInstanceSerializedJson(buffer.GetData(), buffer.GetSize()); } else if (buffer.RestApiGet("/instances/" + orthancId + "/file", false)) { @@ -894,7 +900,7 @@ } buffer.RestApiPut("/instances/" + orthancId + "/attachments/4444", dicomweb, false); - writer.AddDicomWebSerializedJson(dicomweb.c_str(), dicomweb.size()); + writer.AddDicomWebInstanceSerializedJson(dicomweb.c_str(), dicomweb.size()); } } #endif @@ -1074,7 +1080,7 @@ } -static void GetChildrenIdentifiers(std::list<std::string>& target, +static void GetChildrenIdentifiers(std::set<std::string>& target, std::string& resourceDicomUid, Orthanc::ResourceType level, const std::string& orthancId) @@ -1119,7 +1125,7 @@ for (Json::Value::ArrayIndex i = 0; i < children.size(); i++) { - target.push_back(children[i].asString()); + target.insert(children[i].asString()); } } } @@ -1250,7 +1256,7 @@ instanceToLoad->bulkRoot = (data->wadoBase + "studies/" + instanceToLoad->studyInstanceUid + "/series/" + instanceToLoad->seriesInstanceUid + - "/instances/" + instanceResource["MainDicomTags"]["SOPInstanceUID"].asString() + "/bulk"); + "/instances/" + instanceResource[MAIN_DICOM_TAGS]["SOPInstanceUID"].asString() + "/bulk"); } } @@ -1278,7 +1284,7 @@ } } -void RetrieveSeriesMetadataInternal(OrthancPluginRestOutput* output, +void RetrieveSeriesMetadataInternal(std::set<std::string>& instancesIds, OrthancPlugins::DicomWebFormatter::HttpWriter& writer, MainDicomTagsCache& cache, const OrthancPlugins::MetadataMode& mode, @@ -1289,7 +1295,6 @@ const std::string& wadoBase) { ChildrenMainDicomMaps instancesDicomMaps; - std::list<std::string> instancesIds; std::string seriesDicomUid; unsigned int workersCount = OrthancPlugins::Configuration::GetMetadataWorkerThreadsCount(); @@ -1299,6 +1304,10 @@ if (oneLargeQuery) { GetChildrenMainDicomTags(instancesDicomMaps, seriesDicomUid, Orthanc::ResourceType_Series, seriesOrthancId); + for (ChildrenMainDicomMaps::const_iterator it = instancesDicomMaps.begin(); it != instancesDicomMaps.end(); ++it) + { + instancesIds.insert(it->first); + } } else { @@ -1334,7 +1343,7 @@ } else { - for (std::list<std::string>::const_iterator i = instancesIds.begin(); i != instancesIds.end(); ++i) + for (std::set<std::string>::const_iterator i = instancesIds.begin(); i != instancesIds.end(); ++i) { instancesQueue.Enqueue(new InstanceToLoad(*i, "", writerMutex, writer, isXml, OrthancPluginDicomWebBinaryMode_BulkDataUri, studyInstanceUid, seriesInstanceUid)); } @@ -1358,18 +1367,190 @@ } else { // old single threaded code - std::list<std::string> instances; + std::set<std::string> instances; std::string seriesDicomUid; // not used GetChildrenIdentifiers(instances, seriesDicomUid, Orthanc::ResourceType_Series, seriesOrthancId); - for (std::list<std::string>::const_iterator i = instances.begin(); i != instances.end(); ++i) + for (std::set<std::string>::const_iterator i = instances.begin(); i != instances.end(); ++i) { WriteInstanceMetadata(writer, mode, cache, *i, studyInstanceUid, seriesInstanceUid, wadoBase); } } } +void CacheSeriesMetadataInternal(std::string& serializedSeriesMetadata, + OrthancPlugins::DicomWebFormatter::HttpWriter& writer, + MainDicomTagsCache& cache, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& seriesOrthancId) +{ + Orthanc::GzipCompressor compressor; + std::string compressedSeriesMetadata; + std::set<std::string> instancesIds; + + // compute the series metadata with a placeholder WADO base url because, the base url might change (e.g if there are 2 Orthanc connected to the same DB) + 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; + + 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) +{ + if (!OrthancPlugins::Configuration::IsMetadataCacheEnabled()) + { + return; + } + + LOG(INFO) << "DicomWEB: pre-computing the WADO-RS series metadata for series " << seriesOrthancId; + + std::string studyInstanceUid, seriesInstanceUid; + + Json::Value result; + if (OrthancPlugins::RestApiGet(result, "/series/" + seriesOrthancId, false)) + { + seriesInstanceUid = result[MAIN_DICOM_TAGS]["SeriesInstanceUID"].asString(); + if (OrthancPlugins::RestApiGet(result, "/studies/" + result["ParentStudy"].asString(), false)) + { + studyInstanceUid = result[MAIN_DICOM_TAGS]["StudyInstanceUID"].asString(); + + MainDicomTagsCache cache; + OrthancPlugins::DicomWebFormatter::HttpWriter writer(NULL /* output */, false /* isXml */); // we cache only the JSON format -> no need for an HttpOutput + + std::string serializedSeriesMetadataNotUsed; + CacheSeriesMetadataInternal(serializedSeriesMetadataNotUsed, writer, cache, studyInstanceUid, seriesInstanceUid, seriesOrthancId); + } + } +} + +void UpdateSeriesMetadataCache(OrthancPluginRestOutput* output, + const char* /*url*/, + const OrthancPluginHttpRequest* request) +{ + if (request->method != OrthancPluginHttpMethod_Post) + { + OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "POST"); + return; + } + + if (request->groupsCount != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest); + } + + if (!OrthancPlugins::Configuration::IsMetadataCacheEnabled()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "The metadata cache is disabled in the Orthanc configuration."); + } + + std::string studyId(request->groups[0]); + + LOG(INFO) << "DicomWEB: updating the series metadata cache for study " << studyId; + + Json::Value study; + + if (OrthancPlugins::RestApiGet(study, "/studies/" + studyId, false) && study.type() == Json::objectValue) + { + for (Json::ArrayIndex i = 0; i < study["Series"].size(); ++i) + { + CacheSeriesMetadata(study["Series"][i].asString()); + } + } + + std::string answer = "{}"; + OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, answer.c_str(), answer.size(), "application/json"); +} + + + +void RetrieveSeriesMetadataInternalWithCache(OrthancPlugins::DicomWebFormatter::HttpWriter& writer, + MainDicomTagsCache& cache, + const OrthancPlugins::MetadataMode& mode, + bool isXml, + const std::string& seriesOrthancId, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& wadoBase) +{ + if (OrthancPlugins::Configuration::IsMetadataCacheEnabled() && + mode == OrthancPlugins::MetadataMode_Full && + !isXml) + { + // check if we already have computed the series metadata and saved them in an attachment + std::string serializedSeriesMetadata; + std::string cacheContent; + bool hasBeenReadFromCache = false; + Orthanc::GzipCompressor compressor; + + std::string attachmentUrl = "/series/" + seriesOrthancId + "/attachments/" + SERIES_METADATA_ATTACHMENT_ID; + + if (OrthancPlugins::RestApiGetString(cacheContent, attachmentUrl + "/data", false)) + { + if (boost::starts_with(cacheContent, "2;")) // version 2, cacheContent is "2;sorted-instances-list-md5;compressedSeriesMetadata" + { + // check that the instances count have not changed since we have saved the data in cache + // StableSeries event will always overwrite it but this is usefull if retrieving the metadata while + // the instances are being received + // Note: we can not use Toolbox::SplitString because the compressed metadata contain a lot of ";" + const char* secondSemiColon = strchr(&cacheContent[2], ';'); + std::string instancesMd5InCache(&cacheContent[2], secondSemiColon - &cacheContent[2]); + std::string compressedSeriesMetadata(secondSemiColon + 1, cacheContent.size() - (secondSemiColon+1 - cacheContent.c_str())); + + Json::Value seriesInfo; + + if (!OrthancPlugins::RestApiGet(seriesInfo, "/series/" + seriesOrthancId, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + std::set<std::string> currentInstancesIds; + Orthanc::SerializationToolbox::ReadSetOfStrings(currentInstancesIds, seriesInfo, "Instances"); + std::string currentInstancesMd5; + Orthanc::Toolbox::ComputeMD5(currentInstancesMd5, currentInstancesIds); + + if (currentInstancesMd5 == instancesMd5InCache) + { + Orthanc::IBufferCompressor::Uncompress(serializedSeriesMetadata, compressor, compressedSeriesMetadata); + + hasBeenReadFromCache = true; + } + } + } + + if (!hasBeenReadFromCache) // regenerate and overwrite current cache + { + MainDicomTagsCache tmpCache; + OrthancPlugins::DicomWebFormatter::HttpWriter tmpWriter(NULL /* output */, false /* isXml */); // we cache only the JSON format -> no need for an HttpOutput + + CacheSeriesMetadataInternal(serializedSeriesMetadata, tmpWriter, tmpCache, studyInstanceUid, seriesInstanceUid, seriesOrthancId); + } + + boost::replace_all(serializedSeriesMetadata, WADO_BASE_PLACEHOLDER, wadoBase); + + writer.AddDicomWebSeriesSerializedJson(serializedSeriesMetadata.c_str(), serializedSeriesMetadata.size()); + } + else + { + std::set<std::string> instancesIdsNotUsed; + RetrieveSeriesMetadataInternal(instancesIdsNotUsed, writer, cache, mode, isXml, seriesOrthancId, studyInstanceUid, seriesInstanceUid, wadoBase); + } + +} + void RetrieveSeriesMetadata(OrthancPluginRestOutput* output, const char* /*url*/, @@ -1389,7 +1570,7 @@ if (LocateSeries(output, seriesOrthancId, studyInstanceUid, seriesInstanceUid, request)) { std::string wadoBase = OrthancPlugins::Configuration::GetBasePublicUrl(request); - RetrieveSeriesMetadataInternal(output, writer, cache, mode, isXml, seriesOrthancId, studyInstanceUid, seriesInstanceUid, wadoBase); + RetrieveSeriesMetadataInternalWithCache(writer, cache, mode, isXml, seriesOrthancId, studyInstanceUid, seriesInstanceUid, wadoBase); } writer.Send(); @@ -1415,17 +1596,17 @@ std::string wadoBase = OrthancPlugins::Configuration::GetBasePublicUrl(request); - std::list<std::string> series; + std::set<std::string> series; std::string studyDicomUid; GetChildrenIdentifiers(series, studyDicomUid, Orthanc::ResourceType_Study, studyOrthancId); - for (std::list<std::string>::const_iterator s = series.begin(); s != series.end(); ++s) + for (std::set<std::string>::const_iterator s = series.begin(); s != series.end(); ++s) { - std::list<std::string> instances; + std::set<std::string> instances; std::string seriesDicomUid; GetChildrenIdentifiers(instances, seriesDicomUid, Orthanc::ResourceType_Series, *s); - RetrieveSeriesMetadataInternal(output, writer, cache, mode, isXml, *s, studyDicomUid, seriesDicomUid, wadoBase); + RetrieveSeriesMetadataInternalWithCache(writer, cache, mode, isXml, *s, studyDicomUid, seriesDicomUid, wadoBase); } writer.Send(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OrthancDicomWeb-1.14/Plugin/WadoRs.h new/OrthancDicomWeb-1.15/Plugin/WadoRs.h --- old/OrthancDicomWeb-1.14/Plugin/WadoRs.h 2023-07-05 14:13:54.000000000 +0200 +++ new/OrthancDicomWeb-1.15/Plugin/WadoRs.h 2023-08-24 16:29:33.000000000 +0200 @@ -63,6 +63,12 @@ const char* url, const OrthancPluginHttpRequest* request); +void UpdateSeriesMetadataCache(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request); + +void CacheSeriesMetadata(const std::string& seriesOrthancId); + void RetrieveInstanceMetadata(OrthancPluginRestOutput* output, const char* url, const OrthancPluginHttpRequest* request); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OrthancDicomWeb-1.14/README new/OrthancDicomWeb-1.15/README --- old/OrthancDicomWeb-1.14/README 2023-07-05 14:13:54.000000000 +0200 +++ new/OrthancDicomWeb-1.15/README 2023-08-24 16:29:33.000000000 +0200 @@ -39,6 +39,14 @@ http://book.orthanc-server.com/plugins/dicomweb.html +Contributing +------------ + +Instructions for contributing to the Orthanc project are included in +the Orthanc Book: +https://book.orthanc-server.com/developers/repositories.html + + Licensing: AGPL --------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OrthancDicomWeb-1.14/Resources/CMake/JavaScriptLibraries.cmake new/OrthancDicomWeb-1.15/Resources/CMake/JavaScriptLibraries.cmake --- old/OrthancDicomWeb-1.14/Resources/CMake/JavaScriptLibraries.cmake 2023-07-05 14:13:54.000000000 +0200 +++ new/OrthancDicomWeb-1.15/Resources/CMake/JavaScriptLibraries.cmake 2023-08-24 16:29:33.000000000 +0200 @@ -18,7 +18,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. -set(BASE_URL "https://orthanc.uclouvain.be/third-party-downloads/dicom-web") +set(BASE_URL "http://orthanc.osimis.io/ThirdPartyDownloads/dicom-web") DownloadPackage( "da0189f7c33bf9f652ea65401e0a3dc9" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OrthancDicomWeb-1.14/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake new/OrthancDicomWeb-1.15/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake --- old/OrthancDicomWeb-1.14/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake 2023-07-05 14:13:54.000000000 +0200 +++ new/OrthancDicomWeb-1.15/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake 2023-08-24 16:29:33.000000000 +0200 @@ -153,11 +153,9 @@ elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.11.2") set(ORTHANC_FRAMEWORK_MD5 "ede3de356493a8868545f8cb4b8bc8b5") elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.11.3") - set(ORTHANC_FRAMEWORK_MD5 "f941c0f5771db7616e7b7961026a60e2") + set(ORTHANC_FRAMEWORK_MD5 "5c1b11009d782f248739919db6bf7f7a") elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.0") set(ORTHANC_FRAMEWORK_MD5 "d32a0cde03b6eb603d8dd2b33d38bf1b") - elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.1") - set(ORTHANC_FRAMEWORK_MD5 "8a435140efc8ff4a01d8242f092f21de") # Below this point are development snapshots that were used to # release some plugin, before an official release of the Orthanc @@ -181,6 +179,9 @@ elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "b2e08d83e21d") # WSI 1.1 (framework pre-1.10.0), to remove "-std=c++11" set(ORTHANC_FRAMEWORK_MD5 "2eaa073cbb4b44ffba199ad93393b2b1") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "daf4807631c5") + # DICOMweb 1.15 (framework pre-1.12.2) + set(ORTHANC_FRAMEWORK_MD5 "c644aff2817306b3207c98c92e43f35f") endif() endif() endif() ++++++ framework.diff ++++++ --- a/CMakeLists.txt Thu Aug 24 16:29:33 2023 +0200 +++ b/CMakeLists.txt Mon Sep 04 07:49:29 2023 +0200 @@ -54,6 +54,10 @@ set(BUILD_BABEL_POLYFILL OFF CACHE BOOL "Retrieve babel-polyfill from npm") +# Hotfix to compile against system-wide Orthanc framework +function(DefineSourceBasenameForTarget targetname) +endfunction() + # Download and setup the Orthanc framework include(${CMAKE_SOURCE_DIR}/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake) --- a/Plugin/WadoRs.cpp Thu Aug 24 16:29:33 2023 +0200 +++ b/Plugin/WadoRs.cpp Mon Sep 04 07:49:29 2023 +0200 @@ -70,6 +70,20 @@ } +// Hotfix: This function corresponds to a new signature introduced in Orthanc > 1.12.1 +static void ComputeMD5OfSet(std::string& result, + const std::set<std::string>& data) +{ + std::string s; + + for (std::set<std::string>::const_iterator it = data.begin(); it != data.end(); ++it) + { + s += *it; + } + + Orthanc::Toolbox::ComputeMD5(result, s); +} + namespace { @@ -1397,7 +1411,7 @@ // save in attachments for future use Orthanc::IBufferCompressor::Compress(compressedSeriesMetadata, compressor, serializedSeriesMetadata); std::string instancesMd5; - Orthanc::Toolbox::ComputeMD5(instancesMd5, instancesIds); + ComputeMD5OfSet(instancesMd5, instancesIds); std::string cacheContent = "2;" + instancesMd5 + ";" + compressedSeriesMetadata; @@ -1520,7 +1534,7 @@ std::set<std::string> currentInstancesIds; Orthanc::SerializationToolbox::ReadSetOfStrings(currentInstancesIds, seriesInfo, "Instances"); std::string currentInstancesMd5; - Orthanc::Toolbox::ComputeMD5(currentInstancesMd5, currentInstancesIds); + ComputeMD5OfSet(currentInstancesMd5, currentInstancesIds); if (currentInstancesMd5 == instancesMd5InCache) {