# HG changeset patch # User Alain Mazy # Date 1700147344 -3600 # Node ID c65e036d649b4dbe7ac22d6d739199ddd500c0ee # Parent e2c9f9d9700e0471c41621b44321c331d3925fa5 StorageCache is now storing transcoded instances + added ?transcode=... option to the /file route. diff -r e2c9f9d9700e -r c65e036d649b NEWS --- a/NEWS Wed Nov 15 08:58:45 2023 +0100 +++ b/NEWS Thu Nov 16 16:09:04 2023 +0100 @@ -10,8 +10,10 @@ This can bring some significant improvements particularly in viewers. - Optimized the StorageCache to prevent loading the same file multiple times if multiple users request the same file at the same time. + - The StorageCache is now also storing transcoded instances that have been requested by /file?transcode=... + that is now used by the DICOMweb plugin. This speeds up retrieval of transcoded frames through WADO-RS. * Housekeeper plugin: - - Update to rebuild the cache of the DicomWeb plugin when updating to DicomWeb 1.15. + - Update to rebuild the cache of the DICOMweb plugin when updating to DICOMweb 1.15. - New trigger configuration: "DicomWebCacheChange" - Fixed reading the triggers configuration. * HTTP Compression: @@ -37,6 +39,8 @@ * API version upgraded to 22 * Added a route to delete completed jobs from history: DELETE /jobs/{id} +* added a "transcode" option to the /file route: + e.g: /instances/../file?transcode=1.2.840.10008.1.2.4.80 * All 'expand' GET arguments now accepts expand=true and expand=false values. The /studies/../instances and sibling routes are the only whose expand is true if not specified. These routes now accepts expand=false to simply list the child resources ids. diff -r e2c9f9d9700e -r c65e036d649b OrthancFramework/Sources/Cache/MemoryStringCache.cpp --- a/OrthancFramework/Sources/Cache/MemoryStringCache.cpp Wed Nov 15 08:58:45 2023 +0100 +++ b/OrthancFramework/Sources/Cache/MemoryStringCache.cpp Thu Nov 16 16:09:04 2023 +0100 @@ -206,6 +206,24 @@ } + void MemoryStringCache::InvalidateByPrefix(const std::string& keyPrefix) + { + std::vector allKeys; + + { + boost::mutex::scoped_lock cacheLock(cacheMutex_); + content_.GetAllKeys(allKeys); + } + + for (std::vector::const_iterator it = allKeys.begin(); it != allKeys.end(); ++it) + { + if (it->find(keyPrefix) == 0) + { + Invalidate(*it); + } + } + } + bool MemoryStringCache::Fetch(std::string& value, const std::string& key) { diff -r e2c9f9d9700e -r c65e036d649b OrthancFramework/Sources/Cache/MemoryStringCache.h --- a/OrthancFramework/Sources/Cache/MemoryStringCache.h Wed Nov 15 08:58:45 2023 +0100 +++ b/OrthancFramework/Sources/Cache/MemoryStringCache.h Thu Nov 16 16:09:04 2023 +0100 @@ -86,6 +86,7 @@ void Invalidate(const std::string& key); + void InvalidateByPrefix(const std::string& keyPrefix); private: void Add(const std::string& key, diff -r e2c9f9d9700e -r c65e036d649b OrthancFramework/Sources/Enumerations.cpp --- a/OrthancFramework/Sources/Enumerations.cpp Wed Nov 15 08:58:45 2023 +0100 +++ b/OrthancFramework/Sources/Enumerations.cpp Thu Nov 16 16:09:04 2023 +0100 @@ -2468,6 +2468,21 @@ throw OrthancException(ErrorCode_InternalError); } } + + DicomTransferSyntax GetTransferSyntax(const std::string& value) + { + DicomTransferSyntax syntax; + if (LookupTransferSyntax(syntax, value)) + { + return syntax; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Unknown transfer syntax: " + value); + } + } + } diff -r e2c9f9d9700e -r c65e036d649b OrthancFramework/Sources/Enumerations.h --- a/OrthancFramework/Sources/Enumerations.h Wed Nov 15 08:58:45 2023 +0100 +++ b/OrthancFramework/Sources/Enumerations.h Thu Nov 16 16:09:04 2023 +0100 @@ -935,6 +935,9 @@ const std::string& uid); ORTHANC_PUBLIC + DicomTransferSyntax GetTransferSyntax(const std::string& uid); + + ORTHANC_PUBLIC const char* GetResourceTypeText(ResourceType type, bool isPlural, bool isUpperCase); diff -r e2c9f9d9700e -r c65e036d649b OrthancFramework/Sources/FileStorage/StorageCache.cpp --- a/OrthancFramework/Sources/FileStorage/StorageCache.cpp Wed Nov 15 08:58:45 2023 +0100 +++ b/OrthancFramework/Sources/FileStorage/StorageCache.cpp Thu Nov 16 16:09:04 2023 +0100 @@ -38,13 +38,22 @@ { return uuid + ":" + boost::lexical_cast(contentType) + ":1"; } - + + static std::string GetCacheKeyStartRange(const std::string& uuid, FileContentType contentType) { return uuid + ":" + boost::lexical_cast(contentType) + ":0"; } + + static std::string GetCacheKeyTranscodedInstance(const std::string& uuid, + DicomTransferSyntax transferSyntax) + { + return uuid + ":" + GetTransferSyntaxUid(transferSyntax) + ":1"; + } + + void StorageCache::SetMaximumSize(size_t size) { cache_.SetMaximumSize(size); @@ -54,12 +63,8 @@ void StorageCache::Invalidate(const std::string& uuid, FileContentType contentType) { - // invalidate both full file + start range file - const std::string keyFullFile = GetCacheKeyFullFile(uuid, contentType); - cache_.Invalidate(keyFullFile); - - const std::string keyPartialFile = GetCacheKeyStartRange(uuid, contentType); - cache_.Invalidate(keyPartialFile); + // invalidate full file, start range file and possible transcoded instances + cache_.InvalidateByPrefix(uuid); } @@ -111,6 +116,32 @@ } } + bool StorageCache::Accessor::FetchTranscodedInstance(std::string& value, + const std::string& uuid, + DicomTransferSyntax targetSyntax) + { + const std::string key = GetCacheKeyTranscodedInstance(uuid, targetSyntax); + if (MemoryStringCache::Accessor::Fetch(value, key)) + { + LOG(INFO) << "Read instance \"" << uuid << "\" transcoded to " + << GetTransferSyntaxUid(targetSyntax) << " from cache"; + return true; + } + else + { + return false; + } + } + + void StorageCache::Accessor::AddTranscodedInstance(const std::string& uuid, + DicomTransferSyntax targetSyntax, + const void* buffer, + size_t size) + { + const std::string key = GetCacheKeyTranscodedInstance(uuid, targetSyntax); + MemoryStringCache::Accessor::Add(key, reinterpret_cast(buffer), size); + } + bool StorageCache::Accessor::FetchStartRange(std::string& value, const std::string& uuid, FileContentType contentType, diff -r e2c9f9d9700e -r c65e036d649b OrthancFramework/Sources/FileStorage/StorageCache.h --- a/OrthancFramework/Sources/FileStorage/StorageCache.h Wed Nov 15 08:58:45 2023 +0100 +++ b/OrthancFramework/Sources/FileStorage/StorageCache.h Thu Nov 16 16:09:04 2023 +0100 @@ -50,26 +50,35 @@ Accessor(StorageCache& cache); void Add(const std::string& uuid, - FileContentType contentType, - const std::string& value); + FileContentType contentType, + const std::string& value); void AddStartRange(const std::string& uuid, - FileContentType contentType, - const std::string& value); + FileContentType contentType, + const std::string& value); void Add(const std::string& uuid, - FileContentType contentType, - const void* buffer, - size_t size); + FileContentType contentType, + const void* buffer, + size_t size); bool Fetch(std::string& value, - const std::string& uuid, - FileContentType contentType); + const std::string& uuid, + FileContentType contentType); bool FetchStartRange(std::string& value, - const std::string& uuid, - FileContentType contentType, - uint64_t end /* exclusive */); + const std::string& uuid, + FileContentType contentType, + uint64_t end /* exclusive */); + + bool FetchTranscodedInstance(std::string& value, + const std::string& uuid, + DicomTransferSyntax targetSyntax); + + void AddTranscodedInstance(const std::string& uuid, + DicomTransferSyntax targetSyntax, + const void* buffer, + size_t size); }; private: diff -r e2c9f9d9700e -r c65e036d649b OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp Wed Nov 15 08:58:45 2023 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp Thu Nov 16 16:09:04 2023 +0100 @@ -93,21 +93,6 @@ } } - - static DicomTransferSyntax GetTransferSyntax(const std::string& value) - { - DicomTransferSyntax syntax; - if (LookupTransferSyntax(syntax, value)) - { - return syntax; - } - else - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Unknown transfer syntax: " + value); - } - } - static void GetJobParameters(bool& synchronous, /* out */ bool& extended, /* out */ @@ -137,7 +122,7 @@ body.isMember(KEY_TRANSCODE)) { transcode = true; - syntax = GetTransferSyntax(SerializationToolbox::ReadString(body, KEY_TRANSCODE)); + syntax = Orthanc::GetTransferSyntax(SerializationToolbox::ReadString(body, KEY_TRANSCODE)); } else { diff -r e2c9f9d9700e -r c65e036d649b OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Wed Nov 15 08:58:45 2023 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Thu Nov 16 16:09:04 2023 +0100 @@ -361,6 +361,8 @@ static void GetInstanceFile(RestApiGetCall& call) { + static const char* const TRANSCODE = "transcode"; + if (call.IsDocumentation()) { call.GetDocumentation() @@ -369,6 +371,9 @@ .SetDescription("Download one DICOM instance") .SetUriArgument("id", "Orthanc identifier of the DICOM instance of interest") .SetHttpHeader("Accept", "This HTTP header can be set to retrieve the DICOM instance in DICOMweb format") + .SetHttpGetArgument(TRANSCODE, RestApiCallDocumentation::Type_String, + "If present, the DICOM file will be transcoded to the provided " + "transfer syntax: https://book.orthanc-server.com/faq/transcoding.html", false) .AddAnswerType(MimeType_Dicom, "The DICOM instance") .AddAnswerType(MimeType_DicomWebJson, "The DICOM instance, in DICOMweb JSON format") .AddAnswerType(MimeType_DicomWebXml, "The DICOM instance, in DICOMweb XML format"); @@ -417,7 +422,22 @@ } } - context.AnswerAttachment(call.GetOutput(), publicId, FileContentType_Dicom); + if (call.HasArgument(TRANSCODE)) + { + std::string source; + std::string transcoded; + context.ReadDicom(source, publicId); + + if (context.TranscodeWithCache(transcoded, source, publicId, GetTransferSyntax(call.GetArgument(TRANSCODE, "")))) + { + call.GetOutput().AnswerBuffer(transcoded, MimeType_Dicom); + } + } + else + { + // return the attachment without any transcoding + context.AnswerAttachment(call.GetOutput(), publicId, FileContentType_Dicom); + } } diff -r e2c9f9d9700e -r c65e036d649b OrthancServer/Sources/ServerContext.cpp --- a/OrthancServer/Sources/ServerContext.cpp Wed Nov 15 08:58:45 2023 +0100 +++ b/OrthancServer/Sources/ServerContext.cpp Thu Nov 16 16:09:04 2023 +0100 @@ -1948,6 +1948,36 @@ } + bool ServerContext::TranscodeWithCache(std::string& target, + const std::string& source, + const std::string& sourceInstanceId, + DicomTransferSyntax targetSyntax) + { + StorageCache::Accessor cacheAccessor(storageCache_); + + if (!cacheAccessor.FetchTranscodedInstance(target, sourceInstanceId, targetSyntax)) + { + IDicomTranscoder::DicomImage sourceDicom; + sourceDicom.SetExternalBuffer(source); + + IDicomTranscoder::DicomImage targetDicom; + std::set syntaxes; + syntaxes.insert(targetSyntax); + + if (Transcode(targetDicom, sourceDicom, syntaxes, true)) + { + cacheAccessor.AddTranscodedInstance(sourceInstanceId, targetSyntax, reinterpret_cast(targetDicom.GetBufferData()), targetDicom.GetBufferSize()); + target = std::string(reinterpret_cast(targetDicom.GetBufferData()), targetDicom.GetBufferSize()); + return true; + } + + return false; + } + + return true; + } + + bool ServerContext::Transcode(DicomImage& target, DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set& allowedSyntaxes, diff -r e2c9f9d9700e -r c65e036d649b OrthancServer/Sources/ServerContext.h --- a/OrthancServer/Sources/ServerContext.h Wed Nov 15 08:58:45 2023 +0100 +++ b/OrthancServer/Sources/ServerContext.h Thu Nov 16 16:09:04 2023 +0100 @@ -556,6 +556,11 @@ const std::set& allowedSyntaxes, bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + virtual bool TranscodeWithCache(std::string& target, + const std::string& source, + const std::string& sourceInstanceId, + DicomTransferSyntax targetSyntax); + bool IsTranscodeDicomProtocol() const { return transcodeDicomProtocol_;