Mercurial > hg > orthanc
view OrthancServer/Sources/ServerTranscoder.cpp @ 7129:662d58ce7ddf Orthanc-1.13.0
Orthanc-1.13.0
| author | Sebastien Jodogne <s.jodogne@gmail.com> |
|---|---|
| date | Sat, 15 Aug 2026 13:53:33 +0200 |
| parents | dcf1dc9e455a |
| children | 4ba287b00973 |
line wrap: on
line source
/** * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * Copyright (C) 2017-2023 Osimis S.A., Belgium * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ #include "PrecompiledHeadersServer.h" #include "ServerTranscoder.h" #include "../../OrthancFramework/Sources/Cache/LeastRecentlyUsedIndex.h" #include "../../OrthancFramework/Sources/DataSource/DicomDataSource.h" #include "../../OrthancFramework/Sources/DataSource/StorageAreaDataSource.h" #include "../../OrthancFramework/Sources/DataSource/TranscoderDataSource.h" #include "../../OrthancFramework/Sources/DicomFormat/DicomImageInformation.h" #include "../../OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.h" #include "../../OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h" #include "../../OrthancFramework/Sources/Logging.h" #include "../../OrthancFramework/Sources/OrthancException.h" #include "../Plugins/Engine/OrthancPlugins.h" #include "DicomInstanceToStore.h" #include "OrthancConfiguration.h" #include <boost/thread.hpp> #include <dcmtk/dcmdata/dcfilefo.h> static const size_t MAX_CACHE_SIZE = 10000; namespace Orthanc { namespace { enum WorkingSource { WorkingSource_Unknown, WorkingSource_Builtin, WorkingSource_PluginsDecoder, WorkingSource_PluginsTranscoder }; } class ServerTranscoder::PImpl { private: boost::mutex mutex_; LeastRecentlyUsedIndex<std::string, WorkingSource> workingSources_; public: PImpl() { } WorkingSource LookupWorkingSource(const std::string& attachmentId) { boost::mutex::scoped_lock lock(mutex_); WorkingSource source; if (workingSources_.Contains(attachmentId, source)) { return source; } else { return WorkingSource_Unknown; } } void SetWorkingSource(const std::string& attachmentId, WorkingSource source) { if (source == WorkingSource_Unknown) { throw OrthancException(ErrorCode_ParameterOutOfRange); } else { boost::mutex::scoped_lock lock(mutex_); assert(MAX_CACHE_SIZE > 0); if (workingSources_.GetSize() >= MAX_CACHE_SIZE && !workingSources_.Contains(attachmentId)) { workingSources_.RemoveOldest(); } workingSources_.AddOrMakeMostRecent(attachmentId, source); } } }; ServerTranscoder::ServerTranscoder(unsigned int maxConcurrentDcmtkTranscoder) : #if ORTHANC_ENABLE_PLUGINS == 1 plugins_(NULL), #endif pimpl_(new PImpl), dcmtkTranscoder_(new DcmtkTranscoder(maxConcurrentDcmtkTranscoder)), builtinDecoderTranscoderOrder_(BuiltinDecoderTranscoderOrder_After) { OrthancConfiguration::ReaderLock lock; // New option in Orthanc 1.7.0 builtinDecoderTranscoderOrder_ = StringToBuiltinDecoderTranscoderOrder(lock.GetConfiguration().GetStringParameter("BuiltinDecoderTranscoderOrder")); // New option in Orthanc 1.12.6 dynamic_cast<DcmtkTranscoder&>(*dcmtkTranscoder_).SetDefaultLossyQuality(lock.GetConfiguration().GetDicomLossyTranscodingQuality()); } #if ORTHANC_ENABLE_PLUGINS == 1 void ServerTranscoder::SetPlugins(OrthancPlugins& plugins) { if (plugins_ == NULL) { plugins_ = &plugins; } else { throw OrthancException(ErrorCode_BadSequenceOfCalls); } } #endif bool ServerTranscoder::HasPluginsDecoder() const { #if ORTHANC_ENABLE_PLUGINS == 1 return (plugins_ != NULL && plugins_->HasCustomImageDecoder()); #else return false; #endif } bool ServerTranscoder::HasPluginsTranscoder() const { #if ORTHANC_ENABLE_PLUGINS == 1 return (plugins_ != NULL && plugins_->HasCustomTranscoder()); #else return false; #endif } ImageAccessor* ServerTranscoder::DecodeFrameBuiltin(const ParsedDicomFile& dicom, unsigned int frameIndex) { // Use Orthanc's built-in decoder try { return dicom.DecodeFrame(frameIndex); } catch (const OrthancException&) // NOLINT(bugprone-empty-catch) { } return NULL; } ImageAccessor* ServerTranscoder::DecodeFrameUsingPluginsDecoder(const void* buffer, size_t size, unsigned int frameIndex) { #if ORTHANC_ENABLE_PLUGINS == 1 if (HasPluginsDecoder()) { try { return plugins_->Decode(buffer, size, frameIndex); } catch (const OrthancException&) // NOLINT(bugprone-empty-catch) { } } #endif return NULL; } ImageAccessor* ServerTranscoder::DecodeFrameUsingPluginsTranscoder(const void* buffer, size_t size, unsigned int frameIndex) { /** * Contrarily to "ServerTranscoder::Transcode()", this method * doesn't use "dcmtkTranscoder_", as "DecodeFrameBuiltin()" would * have succeeded in this case. **/ #if ORTHANC_ENABLE_PLUGINS == 1 if (HasPluginsTranscoder()) { DicomImage transcoded; DicomImage source; std::set<DicomTransferSyntax> allowedSyntaxes; source.SetExternalBuffer(buffer, size); allowedSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit); if (plugins_->Transcode(transcoded, source, allowedSyntaxes, TranscodingSopInstanceUidMode_AllowNew)) { try { std::unique_ptr<ParsedDicomFile> dicom(transcoded.ReleaseAsParsedDicomFile()); return DecodeFrameBuiltin(*dicom, frameIndex); } catch (const OrthancException&) // NOLINT(bugprone-empty-catch) { } } } #endif return NULL; } ImageAccessor* ServerTranscoder::DecodeFrameBuiltin(const boost::shared_ptr<DataSourceReader>& dicomReader, const FileInfo& attachment, unsigned int frameIndex) { std::unique_ptr<DicomDataSource::Dicom> dicom( DicomDataSource::Execute(*dicomReader, DicomDataSource::CreateWholeRequest(attachment))); DicomDataSource::Dicom::Lock lock(*dicom); return DecodeFrameBuiltin(lock.GetContent(), frameIndex); } ImageAccessor* ServerTranscoder::DecodeFrameUsingPluginsDecoder(const boost::shared_ptr<DataSourceReader>& storageAreaReader, const FileInfo& attachment, unsigned int frameIndex) { if (!HasPluginsDecoder()) { throw OrthancException(ErrorCode_BadSequenceOfCalls); } else { std::unique_ptr<StorageAreaDataSource::Range> range( StorageAreaDataSource::Execute( *storageAreaReader, StorageAreaDataSource::CreateAttachmentRequest(attachment, true /* uncompress */))); return DecodeFrameUsingPluginsDecoder(range->GetData(), range->GetSize(), frameIndex); } } ImageAccessor* ServerTranscoder::DecodeFrameUsingPluginsTranscoder(const boost::shared_ptr<DataSourceReader>& transcoderReader, const FileInfo& attachment, unsigned int frameIndex) { if (!HasPluginsDecoder()) { throw OrthancException(ErrorCode_BadSequenceOfCalls); } else { std::unique_ptr<TranscoderDataSource::Transcoded> transcoded( TranscoderDataSource::Execute(*transcoderReader, TranscoderDataSource::CreateRequest( attachment, DicomTransferSyntax_LittleEndianExplicit, TranscodingSopInstanceUidMode_AllowNew, false /* no lossy quality specified */, 0))); TranscoderDataSource::Transcoded::LockAsParsed lock(*transcoded); return DecodeFrameBuiltin(lock.GetContent(), frameIndex); } } ImageAccessor* ServerTranscoder::DecodeFrame(const DicomInstanceToStore& image, unsigned int frameIndex) { { // check that the target image has a valid/reasonable size before decoding to avoid possible crash or OOB during transcoding DicomMap summary; image.GetSummary(summary); DicomImageInformation imageInfo(summary); imageInfo.ThrowIfInvalidFrameSize(); } std::unique_ptr<ImageAccessor> decoded; if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_Before) { decoded.reset(DecodeFrameBuiltin(image.GetParsedDicomFile(), frameIndex)); if (decoded.get() != NULL) { return decoded.release(); } } decoded.reset(DecodeFrameUsingPluginsDecoder(image.GetBufferData(), image.GetBufferSize(), frameIndex)); if (decoded.get() != NULL) { return decoded.release(); } if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After) { decoded.reset(DecodeFrameBuiltin(image.GetParsedDicomFile(), frameIndex)); if (decoded.get() != NULL) { return decoded.release(); } } decoded.reset(DecodeFrameUsingPluginsTranscoder(image.GetBufferData(), image.GetBufferSize(), frameIndex)); if (decoded.get() != NULL) { return decoded.release(); } return NULL; } ImageAccessor* ServerTranscoder::DecodeFrame(const boost::shared_ptr<DataSourceReader>& dicomReader, // For built-in decoding const boost::shared_ptr<DataSourceReader>& storageAreaReader, // For plugin-based decoding const boost::shared_ptr<DataSourceReader>& transcoderReader, // For transcoding-based decoding const FileInfo& attachment, unsigned int frameIndex) { // Step 1: Try with the last decoder that successfully handled this DICOM attachment const WorkingSource source = pimpl_->LookupWorkingSource(attachment.GetUuid()); std::unique_ptr<ImageAccessor> decoded; switch (source) { case WorkingSource_Unknown: break; case WorkingSource_Builtin: decoded.reset(DecodeFrameBuiltin(dicomReader, attachment, frameIndex)); break; case WorkingSource_PluginsDecoder: decoded.reset(DecodeFrameUsingPluginsDecoder(storageAreaReader, attachment, frameIndex)); break; case WorkingSource_PluginsTranscoder: decoded.reset(DecodeFrameUsingPluginsTranscoder(transcoderReader, attachment, frameIndex)); break; default: throw OrthancException(ErrorCode_ParameterOutOfRange); } if (decoded.get() != NULL) { return decoded.release(); } // Step 2: If this attachment is unknown, try the available decoders if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_Before) { decoded.reset(DecodeFrameBuiltin(dicomReader, attachment, frameIndex)); if (decoded.get() != NULL) { pimpl_->SetWorkingSource(attachment.GetUuid(), WorkingSource_Builtin); return decoded.release(); } } if (HasPluginsDecoder()) { decoded.reset(DecodeFrameUsingPluginsDecoder(storageAreaReader, attachment, frameIndex)); if (decoded.get() != NULL) { pimpl_->SetWorkingSource(attachment.GetUuid(), WorkingSource_PluginsDecoder); return decoded.release(); } } if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After) { decoded.reset(DecodeFrameBuiltin(dicomReader, attachment, frameIndex)); if (decoded.get() != NULL) { pimpl_->SetWorkingSource(attachment.GetUuid(), WorkingSource_Builtin); return decoded.release(); } } if (HasPluginsTranscoder()) { decoded.reset(DecodeFrameUsingPluginsTranscoder(transcoderReader, attachment, frameIndex)); if (decoded.get() != NULL) { pimpl_->SetWorkingSource(attachment.GetUuid(), WorkingSource_PluginsTranscoder); return decoded.release(); } } return NULL; } bool ServerTranscoder::Transcode(DicomImage& target, DicomImage& source, const std::set<DicomTransferSyntax>& allowedSyntaxes, TranscodingSopInstanceUidMode mode) { unsigned int lossyQuality; { OrthancConfiguration::ReaderLock lock; lossyQuality = lock.GetConfiguration().GetDicomLossyTranscodingQuality(); } return Transcode(target, source, allowedSyntaxes, mode, lossyQuality); } bool ServerTranscoder::Transcode(DicomImage& target, DicomImage& source, const std::set<DicomTransferSyntax>& allowedSyntaxes, TranscodingSopInstanceUidMode mode, unsigned int lossyQuality) { if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_Before) { if (dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, mode, lossyQuality)) { return true; } } #if ORTHANC_ENABLE_PLUGINS == 1 if (plugins_ != NULL && plugins_->HasCustomTranscoder()) { if (plugins_->Transcode(target, source, allowedSyntaxes, mode)) // TODO: pass lossyQuality to plugins -> needs a new plugin interface { return true; } else if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After) { LOG(INFO) << "The installed transcoding plugins cannot handle an image, " << "fallback to the built-in DCMTK transcoder"; } } #endif if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After) { return dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, mode, lossyQuality); } else { return false; } } }
