Mercurial > hg > orthanc
changeset 3930:b99acc213937 transcoding
transcoder plugins and GDCM transcoding are working
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 14 May 2020 19:20:40 +0200 |
parents | 7dc5e7e0045d |
children | e6606d3ec892 |
files | Core/DicomParsing/MemoryBufferTranscoder.cpp Core/DicomParsing/MemoryBufferTranscoder.h OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp OrthancServer/ServerContext.cpp OrthancServer/ServerContext.h OrthancServer/ServerJobs/ArchiveJob.cpp Plugins/Engine/OrthancPlugins.cpp Plugins/Engine/OrthancPlugins.h Plugins/Include/orthanc/OrthancCPlugin.h Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Plugins/Samples/Common/OrthancPluginCppWrapper.h Plugins/Samples/GdcmDecoder/Plugin.cpp |
diffstat | 12 files changed, 425 insertions(+), 128 deletions(-) [+] |
line wrap: on
line diff
--- a/Core/DicomParsing/MemoryBufferTranscoder.cpp Thu May 14 14:40:13 2020 +0200 +++ b/Core/DicomParsing/MemoryBufferTranscoder.cpp Thu May 14 19:20:40 2020 +0200 @@ -43,30 +43,6 @@ namespace Orthanc { - MemoryBufferTranscoder::MemoryBufferTranscoder() - { -#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 - useDcmtk_ = true; -#else - useDcmtk_ = false; -#endif - } - - - void MemoryBufferTranscoder::SetDcmtkUsed(bool used) - { -#if ORTHANC_ENABLE_DCMTK_TRANSCODING != 1 - if (useDcmtk) - { - throw OrthancException(ErrorCode_NotImplemented, - "Orthanc was built without support for DMCTK transcoding"); - } -#endif - - useDcmtk_ = used; - } - - static void CheckTargetSyntax(const std::string& transcoded, const std::set<DicomTransferSyntax>& allowedSyntaxes) { @@ -110,21 +86,16 @@ std::set<DicomTransferSyntax> allowedSyntaxes; allowedSyntaxes.insert(targetSyntax); - bool success = Transcode(target, hasSopInstanceUidChanged, - data, source.size(), allowedSyntaxes, allowNewSopInstanceUid); - -#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 - if (!success && - useDcmtk_ && - dcmtk_.TranscodeParsedToBuffer( - target, hasSopInstanceUidChanged, dicom, targetSyntax, allowNewSopInstanceUid)) + if (Transcode(target, hasSopInstanceUidChanged, + data, source.size(), allowedSyntaxes, allowNewSopInstanceUid)) { - success = true; + CheckTargetSyntax(target, allowedSyntaxes); + return true; } -#endif - - CheckTargetSyntax(target, allowedSyntaxes); - return success; + else + { + return false; + } } @@ -147,12 +118,6 @@ return IDicomTranscoder::TranscodedDicom::CreateFromInternal( FromDcmtkBridge::LoadFromMemoryBuffer(data, target.size()), hasSopInstanceUidChanged); } -#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 - else if (useDcmtk_) - { - return dcmtk_.TranscodeToParsed(dicom, buffer, size, allowedSyntaxes, allowNewSopInstanceUid); - } -#endif else { return NULL;
--- a/Core/DicomParsing/MemoryBufferTranscoder.h Thu May 14 14:40:13 2020 +0200 +++ b/Core/DicomParsing/MemoryBufferTranscoder.h Thu May 14 19:20:40 2020 +0200 @@ -33,26 +33,13 @@ #pragma once -#if !defined(ORTHANC_ENABLE_DCMTK_TRANSCODING) -# error Macro ORTHANC_ENABLE_DCMTK_TRANSCODING must be defined to use this file -#endif - -#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 -# include "DcmtkTranscoder.h" -#endif +#include "IDicomTranscoder.h" namespace Orthanc { // This is the basis class for transcoding plugins class MemoryBufferTranscoder : public IDicomTranscoder { - private: - bool useDcmtk_; - -#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 - DcmtkTranscoder dcmtk_; -#endif - protected: virtual bool Transcode(std::string& target, bool& hasSopInstanceUidChanged /* out */, @@ -62,19 +49,6 @@ bool allowNewSopInstanceUid) = 0; public: - /** - * If "useDcmtk" is "true", the transcoder will first try and call - * DCMTK, before calling its own "Transcode()" implementation. - **/ - MemoryBufferTranscoder(); - - void SetDcmtkUsed(bool used); - - bool IsDcmtkUsed() const - { - return useDcmtk_; - } - virtual bool TranscodeParsedToBuffer(std::string& target /* out */, bool& hasSopInstanceUidChanged /* out */, DcmFileFormat& dicom /* in, possibly modified */,
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Thu May 14 14:40:13 2020 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Thu May 14 19:20:40 2020 +0200 @@ -133,9 +133,8 @@ std::string transcoded; bool hasSopInstanceUidChanged; - if (context.GetTranscoder().TranscodeParsedToBuffer( - transcoded, hasSopInstanceUidChanged, - modified->GetDcmtkObject(), targetSyntax, true)) + if (context.TranscodeParsedToBuffer(transcoded, hasSopInstanceUidChanged, + modified->GetDcmtkObject(), targetSyntax, true)) { call.GetOutput().AnswerBuffer(transcoded, MimeType_Dicom); }
--- a/OrthancServer/ServerContext.cpp Thu May 14 14:40:13 2020 +0200 +++ b/OrthancServer/ServerContext.cpp Thu May 14 19:20:40 2020 +0200 @@ -532,10 +532,10 @@ syntaxes.insert(option); std::unique_ptr<IDicomTranscoder::TranscodedDicom> transcoded( - GetTranscoder().TranscodeToParsed(dicom.GetParsedDicomFile().GetDcmtkObject(), - dicom.GetBufferData(), dicom.GetBufferSize(), - syntaxes, true /* allow new SOP instance UID */)); - + TranscodeToParsed(dicom.GetParsedDicomFile().GetDcmtkObject(), + dicom.GetBufferData(), dicom.GetBufferSize(), + syntaxes, true /* allow new SOP instance UID */)); + if (transcoded.get() == NULL) { // Cannot transcode => store the original file @@ -1172,28 +1172,6 @@ } - IDicomTranscoder& ServerContext::GetTranscoder() - { - IDicomTranscoder* transcoder = dcmtkTranscoder_.get(); - -#if ORTHANC_ENABLE_PLUGINS == 1 - if (HasPlugins()) - { - transcoder = &GetPlugins(); - } -#endif - - if (transcoder == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - else - { - return *transcoder; - } - } - - ImageAccessor* ServerContext::DecodeDicomFrame(const std::string& publicId, unsigned int frameIndex) { @@ -1216,7 +1194,7 @@ else { LOG(INFO) << "The installed image decoding plugins cannot handle an image, " - << "fallback to the built-in decoder"; + << "fallback to the built-in DCMTK decoder"; } } #endif @@ -1248,7 +1226,7 @@ else { LOG(INFO) << "The installed image decoding plugins cannot handle an image, " - << "fallback to the built-in decoder"; + << "fallback to the built-in DCMTK decoder"; } } #endif @@ -1275,8 +1253,65 @@ } else { - connection.Transcode(sopClassUid, sopInstanceUid, GetTranscoder(), data, dicom.size(), + connection.Transcode(sopClassUid, sopInstanceUid, *this, data, dicom.size(), hasMoveOriginator, moveOriginatorAet, moveOriginatorId); } } + + + bool ServerContext::TranscodeParsedToBuffer(std::string& target /* out */, + bool& hasSopInstanceUidChanged /* out */, + DcmFileFormat& dicom /* in, possibly modified */, + DicomTransferSyntax targetSyntax, + bool allowNewSopInstanceUid) + { +#if ORTHANC_ENABLE_PLUGINS == 1 + if (HasPlugins()) + { + if (GetPlugins().TranscodeParsedToBuffer(target, hasSopInstanceUidChanged, dicom, + targetSyntax, allowNewSopInstanceUid)) + { + return true; + } + else + { + LOG(INFO) << "The installed transcoding plugins cannot handle an image, " + << "fallback to the built-in DCMTK transcoder"; + } + } +#endif + + return dcmtkTranscoder_->TranscodeParsedToBuffer(target, hasSopInstanceUidChanged, dicom, + targetSyntax, allowNewSopInstanceUid); + } + + + IDicomTranscoder::TranscodedDicom* + ServerContext::TranscodeToParsed(DcmFileFormat& dicom, + const void* buffer, + size_t size, + const std::set<DicomTransferSyntax>& allowedSyntaxes, + bool allowNewSopInstanceUid) + { +#if ORTHANC_ENABLE_PLUGINS == 1 + if (HasPlugins()) + { + std::unique_ptr<IDicomTranscoder::TranscodedDicom> transcoded( + GetPlugins().TranscodeToParsed(dicom, buffer, size, allowedSyntaxes, allowNewSopInstanceUid)); + + if (transcoded.get() != NULL) + { + return transcoded.release(); + } + else + { + LOG(INFO) << "The installed transcoding plugins cannot handle an image, " + << "fallback to the built-in DCMTK transcoder"; + } + } +#endif + + return dcmtkTranscoder_->TranscodeToParsed( + dicom, buffer, size, allowedSyntaxes, allowNewSopInstanceUid); + } }
--- a/OrthancServer/ServerContext.h Thu May 14 14:40:13 2020 +0200 +++ b/OrthancServer/ServerContext.h Thu May 14 19:20:40 2020 +0200 @@ -65,6 +65,7 @@ **/ class ServerContext : public IStorageCommitmentFactory, + public IDicomTranscoder, private JobsRegistry::IObserver { public: @@ -473,8 +474,21 @@ const std::string& moveOriginatorAet, uint16_t moveOriginatorId); - // This accessor can be used even if the global option + // This method can be used even if the global option // "TranscodeDicomProtocol" is set to "false" - IDicomTranscoder& GetTranscoder(); + virtual bool TranscodeParsedToBuffer(std::string& target /* out */, + bool& hasSopInstanceUidChanged /* out */, + DcmFileFormat& dicom /* in, possibly modified */, + DicomTransferSyntax targetSyntax, + bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + + // This method can be used even if the global option + // "TranscodeDicomProtocol" is set to "false" + virtual IDicomTranscoder::TranscodedDicom* TranscodeToParsed( + DcmFileFormat& dicom /* in, possibly modified */, + const void* buffer /* in, same DICOM file as "dicom" */, + size_t size, + const std::set<DicomTransferSyntax>& allowedSyntaxes, + bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; }; }
--- a/OrthancServer/ServerJobs/ArchiveJob.cpp Thu May 14 14:40:13 2020 +0200 +++ b/OrthancServer/ServerJobs/ArchiveJob.cpp Thu May 14 19:20:40 2020 +0200 @@ -447,9 +447,8 @@ const char* data = content.empty() ? NULL : content.c_str(); std::unique_ptr<IDicomTranscoder::TranscodedDicom> transcodedDicom( - context.GetTranscoder().TranscodeToParsed( - parsed->GetDcmtkObject(), data, content.size(), - syntaxes, true /* allow new SOP instance UID */)); + context.TranscodeToParsed(parsed->GetDcmtkObject(), data, content.size(), + syntaxes, true /* allow new SOP instance UID */)); if (transcodedDicom.get() != NULL && transcodedDicom->GetDicom().getDataset() != NULL)
--- a/Plugins/Engine/OrthancPlugins.cpp Thu May 14 14:40:13 2020 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Thu May 14 19:20:40 2020 +0200 @@ -890,6 +890,7 @@ typedef std::list<OrthancPluginIncomingHttpRequestFilter2> IncomingHttpRequestFilters2; typedef std::list<OrthancPluginIncomingDicomInstanceFilter> IncomingDicomInstanceFilters; typedef std::list<OrthancPluginDecodeImageCallback> DecodeImageCallbacks; + typedef std::list<OrthancPluginTranscoderCallback> TranscoderCallbacks; typedef std::list<OrthancPluginJobsUnserializer> JobsUnserializers; typedef std::list<OrthancPluginRefreshMetricsCallback> RefreshMetricsCallbacks; typedef std::list<StorageCommitmentScp*> StorageCommitmentScpCallbacks; @@ -904,6 +905,7 @@ OrthancPluginFindCallback findCallback_; OrthancPluginWorklistCallback worklistCallback_; DecodeImageCallbacks decodeImageCallbacks_; + TranscoderCallbacks transcoderCallbacks_; JobsUnserializers jobsUnserializers_; _OrthancPluginMoveCallback moveCallbacks_; IncomingHttpRequestFilters incomingHttpRequestFilters_; @@ -918,7 +920,7 @@ boost::recursive_mutex changeCallbackMutex_; boost::mutex findCallbackMutex_; boost::mutex worklistCallbackMutex_; - boost::shared_mutex decodeImageCallbackMutex_; // Changed from "boost::mutex" in Orthanc 1.7.0 + boost::shared_mutex decoderTranscoderMutex_; // Changed from "boost::mutex" in Orthanc 1.7.0 boost::mutex jobsUnserializersMutex_; boost::mutex refreshMetricsMutex_; boost::mutex storageCommitmentScpMutex_; @@ -2109,7 +2111,7 @@ const _OrthancPluginDecodeImageCallback& p = *reinterpret_cast<const _OrthancPluginDecodeImageCallback*>(parameters); - boost::unique_lock<boost::shared_mutex> lock(pimpl_->decodeImageCallbackMutex_); + boost::unique_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_); pimpl_->decodeImageCallbacks_.push_back(p.callback); LOG(INFO) << "Plugin has registered a callback to decode DICOM images (" @@ -2117,6 +2119,19 @@ } + void OrthancPlugins::RegisterTranscoderCallback(const void* parameters) + { + const _OrthancPluginTranscoderCallback& p = + *reinterpret_cast<const _OrthancPluginTranscoderCallback*>(parameters); + + boost::unique_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_); + + pimpl_->transcoderCallbacks_.push_back(p.callback); + LOG(INFO) << "Plugin has registered a callback to transcode DICOM images (" + << pimpl_->transcoderCallbacks_.size() << " transcoder(s) now active)"; + } + + void OrthancPlugins::RegisterJobsUnserializer(const void* parameters) { const _OrthancPluginJobsUnserializer& p = @@ -4363,7 +4378,7 @@ { PImpl::ServerContextLock lock(*pimpl_); - transcoded.reset(lock.GetContext().GetTranscoder().TranscodeToParsed( + transcoded.reset(lock.GetContext().TranscodeToParsed( dicom.GetDcmtkObject(), p.buffer, p.size, syntaxes, true /* allow new sop */)); } @@ -4380,6 +4395,25 @@ } } } + + case _OrthancPluginService_CreateMemoryBuffer: + { + const _OrthancPluginCreateMemoryBuffer& p = + *reinterpret_cast<const _OrthancPluginCreateMemoryBuffer*>(parameters); + + p.target->size = p.size; + + if (p.size == 0) + { + p.target->data = NULL; + } + else + { + p.target->data = malloc(p.size); + } + + return true; + } default: return false; @@ -4434,6 +4468,10 @@ RegisterDecodeImageCallback(parameters); return true; + case _OrthancPluginService_RegisterTranscoderCallback: + RegisterTranscoderCallback(parameters); + return true; + case _OrthancPluginService_RegisterJobsUnserializer: RegisterJobsUnserializer(parameters); return true; @@ -4818,7 +4856,7 @@ bool OrthancPlugins::HasCustomImageDecoder() { - boost::shared_lock<boost::shared_mutex> lock(pimpl_->decodeImageCallbackMutex_); + boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_); return !pimpl_->decodeImageCallbacks_.empty(); } @@ -4827,7 +4865,7 @@ size_t size, unsigned int frame) { - boost::shared_lock<boost::shared_mutex> lock(pimpl_->decodeImageCallbackMutex_); + boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_); for (PImpl::DecodeImageCallbacks::const_iterator decoder = pimpl_->decodeImageCallbacks_.begin(); @@ -5130,6 +5168,43 @@ } + class MemoryBufferRaii : public boost::noncopyable + { + private: + OrthancPluginMemoryBuffer buffer_; + + public: + MemoryBufferRaii() + { + buffer_.size = 0; + buffer_.data = NULL; + } + + ~MemoryBufferRaii() + { + if (buffer_.size != 0) + { + free(buffer_.data); + } + } + + OrthancPluginMemoryBuffer* GetObject() + { + return &buffer_; + } + + void ToString(std::string& target) const + { + target.resize(buffer_.size); + + if (buffer_.size != 0) + { + memcpy(&target[0], buffer_.data, buffer_.size); + } + } + }; + + bool OrthancPlugins::Transcode(std::string& target, bool& hasSopInstanceUidChanged /* out */, const void* buffer, @@ -5137,7 +5212,37 @@ const std::set<DicomTransferSyntax>& allowedSyntaxes, bool allowNewSopInstanceUid) { - // TODO + boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_); + + if (pimpl_->transcoderCallbacks_.empty()) + { + return NULL; + } + + std::vector<const char*> uids; + uids.reserve(allowedSyntaxes.size()); + for (std::set<DicomTransferSyntax>::const_iterator + it = allowedSyntaxes.begin(); it != allowedSyntaxes.end(); ++it) + { + uids.push_back(GetTransferSyntaxUid(*it)); + } + + for (PImpl::TranscoderCallbacks::const_iterator + transcoder = pimpl_->transcoderCallbacks_.begin(); + transcoder != pimpl_->transcoderCallbacks_.end(); ++transcoder) + { + MemoryBufferRaii a; + uint8_t b; + + if ((*transcoder) (a.GetObject(), &b, buffer, size, uids.empty() ? NULL : &uids[0], + static_cast<uint32_t>(uids.size()), allowNewSopInstanceUid) == + OrthancPluginErrorCode_Success) + { + a.ToString(target); + return true; + } + } + return false; } }
--- a/Plugins/Engine/OrthancPlugins.h Thu May 14 14:40:13 2020 +0200 +++ b/Plugins/Engine/OrthancPlugins.h Thu May 14 19:20:40 2020 +0200 @@ -124,6 +124,8 @@ void RegisterDecodeImageCallback(const void* parameters); + void RegisterTranscoderCallback(const void* parameters); + void RegisterJobsUnserializer(const void* parameters); void RegisterIncomingHttpRequestFilter(const void* parameters);
--- a/Plugins/Include/orthanc/OrthancCPlugin.h Thu May 14 14:40:13 2020 +0200 +++ b/Plugins/Include/orthanc/OrthancCPlugin.h Thu May 14 19:20:40 2020 +0200 @@ -28,6 +28,7 @@ * - Possibly register a callback to answer chunked HTTP transfers using ::OrthancPluginRegisterChunkedRestCallback(). * - Possibly register a callback for Storage Commitment SCP using ::OrthancPluginRegisterStorageCommitmentScpCallback(). * - Possibly register a callback to filter incoming DICOM instance using OrthancPluginRegisterIncomingDicomInstanceFilter(). + * - Possibly register a custom transcoder for DICOM images using OrthancPluginRegisterTranscoderCallback(). * -# <tt>void OrthancPluginFinalize()</tt>: * This function is invoked by Orthanc during its shutdown. The plugin * must free all its memory. @@ -441,6 +442,7 @@ _OrthancPluginService_GetTagName = 35, /* New in Orthanc 1.5.7 */ _OrthancPluginService_EncodeDicomWebJson2 = 36, /* New in Orthanc 1.7.0 */ _OrthancPluginService_EncodeDicomWebXml2 = 37, /* New in Orthanc 1.7.0 */ + _OrthancPluginService_CreateMemoryBuffer = 38, /* New in Orthanc 1.7.0 */ /* Registration of callbacks */ _OrthancPluginService_RegisterRestCallback = 1000, @@ -458,7 +460,8 @@ _OrthancPluginService_RegisterChunkedRestCallback = 1012, /* New in Orthanc 1.5.7 */ _OrthancPluginService_RegisterStorageCommitmentScpCallback = 1013, _OrthancPluginService_RegisterIncomingDicomInstanceFilter = 1014, - + _OrthancPluginService_RegisterTranscoderCallback = 1015, /* New in Orthanc 1.7.0 */ + /* Sending answers to REST calls */ _OrthancPluginService_AnswerBuffer = 2000, _OrthancPluginService_CompressAndAnswerPngImage = 2001, /* Unused as of Orthanc 0.9.4 */ @@ -7951,6 +7954,54 @@ return target; } } + + + + + typedef OrthancPluginErrorCode (*OrthancPluginTranscoderCallback) ( + OrthancPluginMemoryBuffer* transcoded /* out */, + uint8_t* hasSopInstanceUidChanged /* out */, + const void* buffer, + uint64_t size, + const char* const* allowedSyntaxes, + uint32_t countSyntaxes, + uint8_t allowNewSopInstanceUid); + + + typedef struct + { + OrthancPluginTranscoderCallback callback; + } _OrthancPluginTranscoderCallback; + + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterTranscoderCallback( + OrthancPluginContext* context, + OrthancPluginTranscoderCallback callback) + { + _OrthancPluginTranscoderCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterTranscoderCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + uint32_t size; + } _OrthancPluginCreateMemoryBuffer; + + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateMemoryBuffer( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + uint32_t size) + { + _OrthancPluginCreateMemoryBuffer params; + params.target = target; + params.size = size; + + return context->InvokeService(context, _OrthancPluginService_CreateMemoryBuffer, ¶ms); + } #ifdef __cplusplus
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Thu May 14 14:40:13 2020 +0200 +++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Thu May 14 19:20:40 2020 +0200 @@ -130,6 +130,26 @@ } + MemoryBuffer::MemoryBuffer(const void* buffer, + size_t size) + { + uint32_t s = static_cast<uint32_t>(size); + if (static_cast<size_t>(s) != size) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); + } + else if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) != + OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); + } + else + { + memcpy(buffer_.data, buffer, size); + } + } + + void MemoryBuffer::Clear() { if (buffer_.data != NULL)
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.h Thu May 14 14:40:13 2020 +0200 +++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.h Thu May 14 19:20:40 2020 +0200 @@ -146,6 +146,9 @@ public: MemoryBuffer(); + MemoryBuffer(const void* buffer, + size_t size); + ~MemoryBuffer() { Clear();
--- a/Plugins/Samples/GdcmDecoder/Plugin.cpp Thu May 14 14:40:13 2020 +0200 +++ b/Plugins/Samples/GdcmDecoder/Plugin.cpp Thu May 14 19:20:40 2020 +0200 @@ -24,6 +24,13 @@ #include "../../../Core/Toolbox.h" #include "GdcmDecoderCache.h" +#include <gdcmImageChangeTransferSyntax.h> +#include <gdcmImageReader.h> +#include <gdcmImageWriter.h> +#include <gdcmUIDGenerator.h> +#include <gdcmAttribute.h> + + static OrthancPlugins::GdcmDecoderCache cache_; static bool restrictTransferSyntaxes_ = false; static std::set<std::string> enabledTransferSyntaxes_; @@ -148,6 +155,124 @@ } +OrthancPluginErrorCode TranscoderCallback( + OrthancPluginMemoryBuffer* transcoded /* out */, + uint8_t* hasSopInstanceUidChanged /* out */, + const void* buffer, + uint64_t size, + const char* const* allowedSyntaxes, + uint32_t countSyntaxes, + uint8_t allowNewSopInstanceUid) +{ + try + { + std::string dicom(reinterpret_cast<const char*>(buffer), size); + std::stringstream stream(dicom); + + gdcm::ImageReader reader; + reader.SetStream(stream); + if (!reader.Read()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "GDCM cannot decode the image"); + } + + // First check that transcoding is mandatory + for (uint32_t i = 0; i < countSyntaxes; i++) + { + gdcm::TransferSyntax syntax(gdcm::TransferSyntax::GetTSType(allowedSyntaxes[i])); + if (syntax.IsValid() && + reader.GetImage().GetTransferSyntax() == syntax) + { + // Same transfer syntax as in the source, return a copy of the + // source buffer + OrthancPlugins::MemoryBuffer orthancBuffer(buffer, size); + *transcoded = orthancBuffer.Release(); + *hasSopInstanceUidChanged = false; + return OrthancPluginErrorCode_Success; + } + } + + for (uint32_t i = 0; i < countSyntaxes; i++) + { + gdcm::TransferSyntax syntax(gdcm::TransferSyntax::GetTSType(allowedSyntaxes[i])); + if (syntax.IsValid()) + { + gdcm::ImageChangeTransferSyntax change; + change.SetTransferSyntax(syntax); + change.SetInput(reader.GetImage()); + + if (change.Change()) + { + if (syntax == gdcm::TransferSyntax::JPEGBaselineProcess1 || + syntax == gdcm::TransferSyntax::JPEGExtendedProcess2_4 || + syntax == gdcm::TransferSyntax::JPEGLSNearLossless || + syntax == gdcm::TransferSyntax::JPEG2000 || + syntax == gdcm::TransferSyntax::JPEG2000Part2) + { + // In the case of a lossy compression, generate new SOP instance UID + gdcm::UIDGenerator generator; + std::string uid = generator.Generate(); + if (uid.size() == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "GDCM cannot generate a UID"); + } + + gdcm::Attribute<0x0008,0x0018> sopInstanceUid; + sopInstanceUid.SetValue(uid); + reader.GetFile().GetDataSet().Replace(sopInstanceUid.GetAsDataElement()); + *hasSopInstanceUidChanged = 1; + } + else + { + *hasSopInstanceUidChanged = 0; + } + + // GDCM was able to change the transfer syntax, serialize it + // to the output buffer + gdcm::ImageWriter writer; + writer.SetImage(change.GetOutput()); + writer.SetFile(reader.GetFile()); + + std::stringstream ss; + writer.SetStream(ss); + if (writer.Write()) + { + std::string s = ss.str(); + OrthancPlugins::MemoryBuffer orthancBuffer(s.empty() ? NULL : s.c_str(), s.size()); + *transcoded = orthancBuffer.Release(); + + return OrthancPluginErrorCode_Success; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "GDCM cannot serialize the image"); + } + } + } + } + + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + catch (Orthanc::OrthancException& e) + { + LOG(INFO) << "Cannot transcode image using GDCM: " << e.What(); + return OrthancPluginErrorCode_Plugin; + } + catch (std::runtime_error& e) + { + LOG(INFO) << "Cannot transcode image using GDCM: " << e.what(); + return OrthancPluginErrorCode_Plugin; + } + catch (...) + { + LOG(INFO) << "Native exception while decoding image using GDCM"; + return OrthancPluginErrorCode_Plugin; + } +} + /** * We force the redefinition of the "ORTHANC_PLUGINS_API" macro, that @@ -176,23 +301,18 @@ static const char* const KEY_RESTRICT_TRANSFER_SYNTAXES = "RestrictTransferSyntaxes"; OrthancPlugins::SetGlobalContext(context); - LOG(INFO) << "Initializing the advanced decoder of medical images using GDCM"; - + Orthanc::Logging::Initialize(context); + LOG(INFO) << "Initializing the decoder/transcoder of medical images using GDCM"; /* Check the version of the Orthanc core */ - if (OrthancPluginCheckVersion(context) == 0) + if (!OrthancPlugins::CheckMinimalOrthancVersion(0, 9, 5)) { - char info[1024]; - sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", - context->orthancVersion, - ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, - ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, - ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); - OrthancPluginLogError(context, info); + LOG(ERROR) << "Your version of Orthanc (" << std::string(context->orthancVersion) + << ") must be above 0.9.5 to run this plugin"; return -1; } - OrthancPluginSetDescription(context, "Advanced decoder of medical images using GDCM."); + OrthancPluginSetDescription(context, "Decoder/transcoder of medical images using GDCM."); OrthancPlugins::OrthancConfiguration global; @@ -220,10 +340,20 @@ if (enabled) { OrthancPluginRegisterDecodeImageCallback(context, DecodeImageCallback); + + if (OrthancPlugins::CheckMinimalOrthancVersion(1, 7, 0)) + { + OrthancPluginRegisterTranscoderCallback(context, TranscoderCallback); + } + else + { + LOG(ERROR) << "Your version of Orthanc (" << std::string(context->orthancVersion) + << ") must be above 1.7.0 to benefit from transcoding"; + } } else { - LOG(WARNING) << "The advanced decoder of medical images using GDCM is disabled"; + LOG(WARNING) << "The decoder/transcoder of medical images using GDCM is disabled"; } return 0; @@ -232,13 +362,13 @@ ORTHANC_PLUGINS_API void OrthancPluginFinalize() { - LOG(INFO) << "Finalizing the advanced decoder of medical images using GDCM"; + LOG(INFO) << "Finalizing the decoder/transcoder of medical images using GDCM"; } ORTHANC_PLUGINS_API const char* OrthancPluginGetName() { - return "gdcm-decoder"; + return "gdcm"; }