Mercurial > hg > orthanc-dicomweb
changeset 593:0c3bc2ea0756
store series/metadata in an attachment
author | Alain Mazy <am@osimis.io> |
---|---|
date | Mon, 21 Aug 2023 15:16:23 +0200 |
parents | afb98ea1458d |
children | 68aa83ffa290 |
files | CMakeLists.txt NEWS Plugin/Configuration.cpp Plugin/Configuration.h Plugin/DicomWebFormatter.cpp Plugin/DicomWebFormatter.h Plugin/Plugin.cpp Plugin/WadoRs.cpp Plugin/WadoRs.h |
diffstat | 9 files changed, 139 insertions(+), 7 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Mon Aug 14 10:17:17 2023 +0200 +++ b/CMakeLists.txt Mon Aug 21 15:16:23 2023 +0200 @@ -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}")
--- a/NEWS Mon Aug 14 10:17:17 2023 +0200 +++ b/NEWS Mon Aug 21 15:16:23 2023 +0200 @@ -1,3 +1,11 @@ +Pending changes in the mainline +=============================== + +* speed improvement: + - Now storing the output of studies/../series/../metadata route in an attachment when in "Full" mode. + The json file is gzipped and stored in attachment 4301 everytime a series is stable or the first time + its studies/../series/../metadata route is called if the attachment does not exist yet. + Version 1.14 (2023-07-05) =========================
--- a/Plugin/Configuration.cpp Mon Aug 14 10:17:17 2023 +0200 +++ b/Plugin/Configuration.cpp Mon Aug 21 15:16:23 2023 +0200 @@ -680,6 +680,7 @@ MetadataMode GetMetadataMode(Orthanc::ResourceType level) { static const std::string FULL = "Full"; + static const std::string FULL_NO_CACHE = "FullNoCache"; static const std::string MAIN_DICOM_TAGS = "MainDicomTags"; static const std::string EXTRAPOLATE = "Extrapolate"; @@ -702,6 +703,10 @@ if (value == FULL) { + return MetadataMode_FullWithCache; + } + else if (value == FULL_NO_CACHE) + { return MetadataMode_Full; } else if (value == MAIN_DICOM_TAGS)
--- a/Plugin/Configuration.h Mon Aug 14 10:17:17 2023 +0200 +++ b/Plugin/Configuration.h Mon Aug 21 15:16:23 2023 +0200 @@ -49,6 +49,7 @@ enum MetadataMode { MetadataMode_Full, // Read all the DICOM instances from the storage area + MetadataMode_FullWithCache, // 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 };
--- a/Plugin/DicomWebFormatter.cpp Mon Aug 14 10:17:17 2023 +0200 +++ b/Plugin/DicomWebFormatter.cpp Mon Aug 21 15:16:23 2023 +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); } @@ -331,6 +331,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)
--- a/Plugin/DicomWebFormatter.h Mon Aug 14 10:17:17 2023 +0200 +++ b/Plugin/DicomWebFormatter.h Mon Aug 21 15:16:23 2023 +0200 @@ -120,6 +120,8 @@ void Send(); + void CloseAndGetJsonOutput(std::string& target); + void AddInstance(const DicomInstance& instance, const std::string& bulkRoot); };
--- a/Plugin/Plugin.cpp Mon Aug 14 10:17:17 2023 +0200 +++ b/Plugin/Plugin.cpp Mon Aug 21 15:16:23 2023 +0200 @@ -471,6 +471,10 @@ OrthancPlugins::Configuration::LoadDicomWebServers(); break; + case OrthancPluginChangeType_StableSeries: + CacheSeriesMetadata(resourceId); + break; + default: break; }
--- a/Plugin/WadoRs.cpp Mon Aug 14 10:17:17 2023 +0200 +++ b/Plugin/WadoRs.cpp Mon Aug 21 15:16:23 2023 +0200 @@ -30,11 +30,14 @@ #include <Logging.h> #include <Toolbox.h> #include <MultiThreading/SharedMessageQueue.h> +#include <Compression/GzipCompressor.h> #include <memory> #include <boost/thread/mutex.hpp> #include <boost/thread.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"; @@ -814,6 +817,7 @@ } case OrthancPlugins::MetadataMode_Full: + case OrthancPlugins::MetadataMode_FullWithCache: { const std::string bulkRoot = (wadoBase + "studies/" + studyInstanceUid + @@ -1250,7 +1254,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,8 +1282,7 @@ } } -void RetrieveSeriesMetadataInternal(OrthancPluginRestOutput* output, - OrthancPlugins::DicomWebFormatter::HttpWriter& writer, +void RetrieveSeriesMetadataInternal(OrthancPlugins::DicomWebFormatter::HttpWriter& writer, MainDicomTagsCache& cache, const OrthancPlugins::MetadataMode& mode, bool isXml, @@ -1370,6 +1373,101 @@ } } +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; + + // 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(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); + + Json::Value putResult; + std::string attachmentUrl = "/series/" + seriesOrthancId + "/attachments/" + SERIES_METADATA_ATTACHMENT_ID; + if (!OrthancPlugins::RestApiPut(putResult, attachmentUrl, compressedSeriesMetadata, false)) + { + LOG(WARNING) << "DicomWEB: failed to write series metadata attachment"; + } + +} + +void CacheSeriesMetadata(const std::string& seriesOrthancId) +{ + const OrthancPlugins::MetadataMode mode = + OrthancPlugins::Configuration::GetMetadataMode(Orthanc::ResourceType_Series); + + if (mode == OrthancPlugins::MetadataMode_FullWithCache) + { + LOG(INFO) << "DicomWEB: pre-computing the WADO-RS series metadata"; + + 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 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 (mode == OrthancPlugins::MetadataMode_FullWithCache && !isXml) + { + // check if we already have computed the series metadata and saved them in an attachment + std::string serializedSeriesMetadata; + std::string compressedSeriesMetadata; + Orthanc::GzipCompressor compressor; + + std::string attachmentUrl = "/series/" + seriesOrthancId + "/attachments/" + SERIES_METADATA_ATTACHMENT_ID; + + if (!OrthancPlugins::RestApiGetString(compressedSeriesMetadata, attachmentUrl + "/data", false)) + { + CacheSeriesMetadataInternal(serializedSeriesMetadata, writer, cache, studyInstanceUid, seriesInstanceUid, seriesOrthancId); + } + else + { + Orthanc::IBufferCompressor::Uncompress(serializedSeriesMetadata, compressor, compressedSeriesMetadata); + } + + boost::replace_all(serializedSeriesMetadata, WADO_BASE_PLACEHOLDER, wadoBase); + + writer.AddDicomWebSerializedJson(serializedSeriesMetadata.c_str(), serializedSeriesMetadata.size()); + } + else + { + RetrieveSeriesMetadataInternal(writer, cache, mode, isXml, seriesOrthancId, studyInstanceUid, seriesInstanceUid, wadoBase); + writer.Send(); + } + +} + void RetrieveSeriesMetadata(OrthancPluginRestOutput* output, const char* /*url*/, @@ -1389,7 +1487,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(); @@ -1425,7 +1523,7 @@ 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();
--- a/Plugin/WadoRs.h Mon Aug 14 10:17:17 2023 +0200 +++ b/Plugin/WadoRs.h Mon Aug 21 15:16:23 2023 +0200 @@ -63,6 +63,8 @@ const char* url, const OrthancPluginHttpRequest* request); +void CacheSeriesMetadata(const std::string& seriesOrthancId); + void RetrieveInstanceMetadata(OrthancPluginRestOutput* output, const char* url, const OrthancPluginHttpRequest* request);