# HG changeset patch # User Sebastien Jodogne # Date 1588842809 -7200 # Node ID 8f7ad4989fec9169d412cd85bbf92a819bf2c75f # Parent 7a5fa8f307e95bbeff6a9bc30ec00e0ab17cfed6 transcoding to uncompressed transfer syntaxes over DICOM protocol is implemented diff -r 7a5fa8f307e9 -r 8f7ad4989fec Core/DicomNetworking/DicomStoreUserConnection.cpp --- a/Core/DicomNetworking/DicomStoreUserConnection.cpp Wed May 06 12:48:28 2020 +0200 +++ b/Core/DicomNetworking/DicomStoreUserConnection.cpp Thu May 07 11:13:29 2020 +0200 @@ -321,6 +321,7 @@ void DicomStoreUserConnection::Store(std::string& sopClassUid, std::string& sopInstanceUid, DcmFileFormat& dicom, + bool hasMoveOriginator, const std::string& moveOriginatorAET, uint16_t moveOriginatorID) { @@ -347,8 +348,8 @@ request.DataSetType = DIMSE_DATASET_PRESENT; strncpy(request.AffectedSOPInstanceUID, sopInstanceUid.c_str(), DIC_UI_LEN); - if (!moveOriginatorAET.empty()) - { + if (hasMoveOriginator) + { strncpy(request.MoveOriginatorApplicationEntityTitle, moveOriginatorAET.c_str(), DIC_AE_LEN); request.opts = O_STORE_MOVEORIGINATORAETITLE; @@ -402,6 +403,7 @@ std::string& sopInstanceUid, const void* buffer, size_t size, + bool hasMoveOriginator, const std::string& moveOriginatorAET, uint16_t moveOriginatorID) { @@ -413,7 +415,7 @@ throw OrthancException(ErrorCode_InternalError); } - Store(sopClassUid, sopInstanceUid, *dicom, moveOriginatorAET, moveOriginatorID); + Store(sopClassUid, sopInstanceUid, *dicom, hasMoveOriginator, moveOriginatorAET, moveOriginatorID); } @@ -446,6 +448,7 @@ IDicomTranscoder& transcoder, const void* buffer, size_t size, + bool hasMoveOriginator, const std::string& moveOriginatorAET, uint16_t moveOriginatorID) { @@ -465,7 +468,8 @@ if (accepted.find(inputSyntax) != accepted.end()) { // No need for transcoding - Store(sopClassUid, sopInstanceUid, *dicom, moveOriginatorAET, moveOriginatorID); + Store(sopClassUid, sopInstanceUid, *dicom, + hasMoveOriginator, moveOriginatorAET, moveOriginatorID); } else { @@ -489,9 +493,11 @@ std::unique_ptr transcoded; + bool hasSopInstanceUidChanged; + if (transcoder.HasInplaceTranscode()) { - if (transcoder.InplaceTranscode(*dicom, uncompressedSyntaxes, false)) + if (transcoder.InplaceTranscode(hasSopInstanceUidChanged, *dicom, uncompressedSyntaxes, false)) { // In-place transcoding is supported and has succeeded transcoded.reset(dicom.release()); @@ -499,7 +505,13 @@ } else { - transcoded.reset(transcoder.TranscodeToParsed(buffer, size, uncompressedSyntaxes, false)); + transcoded.reset(transcoder.TranscodeToParsed(hasSopInstanceUidChanged, buffer, size, uncompressedSyntaxes, false)); + } + + if (hasSopInstanceUidChanged) + { + throw OrthancException(ErrorCode_Plugin, "The transcoder has changed the SOP " + "instance UID while transcoding to an uncompressed transfer syntax"); } // WARNING: The "dicom" variable must not be used below this @@ -527,7 +539,8 @@ } else { - Store(sopClassUid, sopInstanceUid, *transcoded, moveOriginatorAET, moveOriginatorID); + Store(sopClassUid, sopInstanceUid, *transcoded, + hasMoveOriginator, moveOriginatorAET, moveOriginatorID); } } } diff -r 7a5fa8f307e9 -r 8f7ad4989fec Core/DicomNetworking/DicomStoreUserConnection.h --- a/Core/DicomNetworking/DicomStoreUserConnection.h Wed May 06 12:48:28 2020 +0200 +++ b/Core/DicomNetworking/DicomStoreUserConnection.h Thu May 07 11:13:29 2020 +0200 @@ -150,6 +150,7 @@ void Store(std::string& sopClassUid, std::string& sopInstanceUid, DcmFileFormat& dicom, + bool hasMoveOriginator, const std::string& moveOriginatorAET, uint16_t moveOriginatorID); @@ -157,24 +158,10 @@ std::string& sopInstanceUid, const void* buffer, size_t size, + bool hasMoveOriginator, const std::string& moveOriginatorAET, uint16_t moveOriginatorID); - void Store(std::string& sopClassUid, - std::string& sopInstanceUid, - DcmFileFormat& dicom) - { - Store(sopClassUid, sopInstanceUid, dicom, "", 0); // Not a C-Move - } - - void Store(std::string& sopClassUid, - std::string& sopInstanceUid, - const void* buffer, - size_t size) - { - Store(sopClassUid, sopInstanceUid, buffer, size, "", 0); // Not a C-Move - } - void LookupParameters(std::string& sopClassUid, std::string& sopInstanceUid, DicomTransferSyntax& transferSyntax, @@ -185,17 +172,8 @@ IDicomTranscoder& transcoder, const void* buffer, size_t size, + bool hasMoveOriginator, const std::string& moveOriginatorAET, uint16_t moveOriginatorID); - - void Transcode(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - IDicomTranscoder& transcoder, - const void* buffer, - size_t size) - { - Transcode(sopClassUid, sopInstanceUid, transcoder, - buffer, size, "", 0); // Not a C-Move - } }; } diff -r 7a5fa8f307e9 -r 8f7ad4989fec Core/DicomNetworking/RemoteModalityParameters.cpp --- a/Core/DicomNetworking/RemoteModalityParameters.cpp Wed May 06 12:48:28 2020 +0200 +++ b/Core/DicomNetworking/RemoteModalityParameters.cpp Thu May 07 11:13:29 2020 +0200 @@ -51,6 +51,7 @@ static const char* KEY_ALLOW_N_ACTION = "AllowNAction"; static const char* KEY_ALLOW_N_EVENT_REPORT = "AllowEventReport"; static const char* KEY_ALLOW_STORAGE_COMMITMENT = "AllowStorageCommitment"; +static const char* KEY_ALLOW_TRANSCODING = "AllowTranscoding"; static const char* KEY_HOST = "Host"; static const char* KEY_MANUFACTURER = "Manufacturer"; static const char* KEY_PORT = "Port"; @@ -71,6 +72,7 @@ allowGet_ = true; allowNAction_ = true; // For storage commitment allowNEventReport_ = true; // For storage commitment + allowTranscoding_ = true; } @@ -233,6 +235,11 @@ allowNAction_ = allow; allowNEventReport_ = allow; } + + if (serialized.isMember(KEY_ALLOW_TRANSCODING)) + { + allowTranscoding_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_TRANSCODING); + } } @@ -314,7 +321,8 @@ !allowGet_ || !allowMove_ || !allowNAction_ || - !allowNEventReport_); + !allowNEventReport_ || + !allowTranscoding_); } @@ -336,6 +344,7 @@ target[KEY_ALLOW_MOVE] = allowMove_; target[KEY_ALLOW_N_ACTION] = allowNAction_; target[KEY_ALLOW_N_EVENT_REPORT] = allowNEventReport_; + target[KEY_ALLOW_TRANSCODING] = allowTranscoding_; } else { diff -r 7a5fa8f307e9 -r 8f7ad4989fec Core/DicomNetworking/RemoteModalityParameters.h --- a/Core/DicomNetworking/RemoteModalityParameters.h Wed May 06 12:48:28 2020 +0200 +++ b/Core/DicomNetworking/RemoteModalityParameters.h Thu May 07 11:13:29 2020 +0200 @@ -55,6 +55,7 @@ bool allowGet_; bool allowNAction_; bool allowNEventReport_; + bool allowTranscoding_; void Clear(); @@ -131,5 +132,15 @@ void Serialize(Json::Value& target, bool forceAdvancedFormat) const; + + bool IsTranscodingAllowed() const + { + return allowTranscoding_; + } + + void SetTranscodingAllowed(bool allowed) + { + allowTranscoding_ = allowed; + } }; } diff -r 7a5fa8f307e9 -r 8f7ad4989fec Core/DicomParsing/DcmtkTranscoder.cpp --- a/Core/DicomParsing/DcmtkTranscoder.cpp Wed May 06 12:48:28 2020 +0200 +++ b/Core/DicomParsing/DcmtkTranscoder.cpp Thu May 07 11:13:29 2020 +0200 @@ -129,7 +129,8 @@ } - DcmFileFormat* DcmtkTranscoder::TranscodeToParsed(const void* buffer, + DcmFileFormat* DcmtkTranscoder::TranscodeToParsed(bool& hasSopInstanceUidChanged /* out */, + const void* buffer, size_t size, const std::set& allowedSyntaxes, bool allowNewSopInstanceUid) @@ -141,7 +142,7 @@ throw OrthancException(ErrorCode_InternalError); } - if (InplaceTranscode(*dicom, allowedSyntaxes, allowNewSopInstanceUid)) + if (InplaceTranscode(hasSopInstanceUidChanged, *dicom, allowedSyntaxes, allowNewSopInstanceUid)) { return dicom.release(); } @@ -152,7 +153,8 @@ } - bool DcmtkTranscoder::InplaceTranscode(DcmFileFormat& dicom, + bool DcmtkTranscoder::InplaceTranscode(bool& hasSopInstanceUidChanged /* out */, + DcmFileFormat& dicom, const std::set& allowedSyntaxes, bool allowNewSopInstanceUid) { @@ -161,6 +163,8 @@ throw OrthancException(ErrorCode_InternalError); } + hasSopInstanceUidChanged = false; + DicomTransferSyntax syntax; if (!FromDcmtkBridge::LookupOrthancTransferSyntax(syntax, dicom)) { @@ -170,7 +174,7 @@ const uint16_t bitsStored = GetBitsStored(*dicom.getDataset()); std::string sourceSopInstanceUid = GetSopInstanceUid(*dicom.getDataset()); - + if (allowedSyntaxes.find(syntax) != allowedSyntaxes.end()) { // No transcoding is needed @@ -216,6 +220,7 @@ if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess1, ¶meters)) { CheckSopInstanceUid(dicom, sourceSopInstanceUid, false); + hasSopInstanceUidChanged = true; return true; } } @@ -231,6 +236,7 @@ if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess2_4, ¶meters)) { CheckSopInstanceUid(dicom, sourceSopInstanceUid, false); + hasSopInstanceUidChanged = true; return true; } } @@ -284,6 +290,7 @@ if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGLSLossy, ¶meters)) { CheckSopInstanceUid(dicom, sourceSopInstanceUid, false); + hasSopInstanceUidChanged = true; return true; } } @@ -294,13 +301,14 @@ bool DcmtkTranscoder::TranscodeToBuffer(std::string& target, + bool& hasSopInstanceUidChanged /* out */, const void* buffer, size_t size, const std::set& allowedSyntaxes, bool allowNewSopInstanceUid) { std::unique_ptr transcoded( - TranscodeToParsed(buffer, size, allowedSyntaxes, allowNewSopInstanceUid)); + TranscodeToParsed(hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid)); if (transcoded.get() == NULL) { diff -r 7a5fa8f307e9 -r 8f7ad4989fec Core/DicomParsing/DcmtkTranscoder.h --- a/Core/DicomParsing/DcmtkTranscoder.h Wed May 06 12:48:28 2020 +0200 +++ b/Core/DicomParsing/DcmtkTranscoder.h Thu May 07 11:13:29 2020 +0200 @@ -63,7 +63,8 @@ return lossyQuality_; } - virtual DcmFileFormat* TranscodeToParsed(const void* buffer, + virtual DcmFileFormat* TranscodeToParsed(bool& hasSopInstanceUidChanged /* out */, + const void* buffer, size_t size, const std::set& allowedSyntaxes, bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; @@ -73,11 +74,13 @@ return true; } - virtual bool InplaceTranscode(DcmFileFormat& dicom, + virtual bool InplaceTranscode(bool& hasSopInstanceUidChanged /* out */, + DcmFileFormat& dicom, const std::set& allowedSyntaxes, bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; virtual bool TranscodeToBuffer(std::string& target, + bool& hasSopInstanceUidChanged /* out */, const void* buffer, size_t size, const std::set& allowedSyntaxes, diff -r 7a5fa8f307e9 -r 8f7ad4989fec Core/DicomParsing/IDicomTranscoder.h --- a/Core/DicomParsing/IDicomTranscoder.h Wed May 06 12:48:28 2020 +0200 +++ b/Core/DicomParsing/IDicomTranscoder.h Thu May 07 11:13:29 2020 +0200 @@ -55,6 +55,7 @@ } virtual bool TranscodeToBuffer(std::string& target, + bool& hasSopInstanceUidChanged /* out */, const void* buffer, size_t size, const std::set& allowedSyntaxes, @@ -66,7 +67,8 @@ * possibility to do a single parsing for all the possible * transfer syntaxes. **/ - virtual DcmFileFormat* TranscodeToParsed(const void* buffer, + virtual DcmFileFormat* TranscodeToParsed(bool& hasSopInstanceUidChanged /* out */, + const void* buffer, size_t size, const std::set& allowedSyntaxes, bool allowNewSopInstanceUid) = 0; @@ -76,7 +78,8 @@ /** * In-place transcoding. This method is preferred for C-STORE. **/ - virtual bool InplaceTranscode(DcmFileFormat& dicom, + virtual bool InplaceTranscode(bool& hasSopInstanceUidChanged /* out */, + DcmFileFormat& dicom, const std::set& allowedSyntaxes, bool allowNewSopInstanceUid) = 0; }; diff -r 7a5fa8f307e9 -r 8f7ad4989fec Core/DicomParsing/MemoryBufferTranscoder.cpp --- a/Core/DicomParsing/MemoryBufferTranscoder.cpp Wed May 06 12:48:28 2020 +0200 +++ b/Core/DicomParsing/MemoryBufferTranscoder.cpp Thu May 07 11:13:29 2020 +0200 @@ -39,52 +39,66 @@ namespace Orthanc { - MemoryBufferTranscoder::MemoryBufferTranscoder(bool tryDcmtk) : - tryDcmtk_(tryDcmtk) + 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 (tryDcmtk) + if (useDcmtk) { throw OrthancException(ErrorCode_NotImplemented, "Orthanc was built without support for DMCTK transcoding"); } #endif + + useDcmtk_ = used; } + bool MemoryBufferTranscoder::TranscodeToBuffer(std::string& target, + bool& hasSopInstanceUidChanged, const void* buffer, size_t size, const std::set& allowedSyntaxes, bool allowNewSopInstanceUid) { #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 - if (tryDcmtk_) + if (useDcmtk_) { - return dcmtk_.TranscodeToBuffer(target, buffer, size, allowedSyntaxes, allowNewSopInstanceUid); + return dcmtk_.TranscodeToBuffer(target, hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid); } else #endif { - return Transcode(target, buffer, size, allowedSyntaxes, allowNewSopInstanceUid); + return Transcode(target, hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid); } } - DcmFileFormat* MemoryBufferTranscoder::TranscodeToParsed(const void* buffer, + DcmFileFormat* MemoryBufferTranscoder::TranscodeToParsed(bool& hasSopInstanceUidChanged, + const void* buffer, size_t size, const std::set& allowedSyntaxes, bool allowNewSopInstanceUid) { #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 - if (tryDcmtk_) + if (useDcmtk_) { - return dcmtk_.TranscodeToParsed(buffer, size, allowedSyntaxes, allowNewSopInstanceUid); + return dcmtk_.TranscodeToParsed(hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid); } else #endif { std::string transcoded; - if (Transcode(transcoded, buffer, size, allowedSyntaxes, allowNewSopInstanceUid)) + if (Transcode(transcoded, hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid)) { return FromDcmtkBridge::LoadFromMemoryBuffer( transcoded.empty() ? NULL : transcoded.c_str(), transcoded.size()); @@ -97,14 +111,15 @@ } - bool MemoryBufferTranscoder::InplaceTranscode(DcmFileFormat& dicom, + bool MemoryBufferTranscoder::InplaceTranscode(bool& hasSopInstanceUidChanged, + DcmFileFormat& dicom, const std::set& allowedSyntaxes, bool allowNewSopInstanceUid) { #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 - if (tryDcmtk_) + if (useDcmtk_) { - return dcmtk_.InplaceTranscode(dicom, allowedSyntaxes, allowNewSopInstanceUid); + return dcmtk_.InplaceTranscode(hasSopInstanceUidChanged, dicom, allowedSyntaxes, allowNewSopInstanceUid); } else #endif diff -r 7a5fa8f307e9 -r 8f7ad4989fec Core/DicomParsing/MemoryBufferTranscoder.h --- a/Core/DicomParsing/MemoryBufferTranscoder.h Wed May 06 12:48:28 2020 +0200 +++ b/Core/DicomParsing/MemoryBufferTranscoder.h Thu May 07 11:13:29 2020 +0200 @@ -47,7 +47,7 @@ class MemoryBufferTranscoder : public IDicomTranscoder { private: - bool tryDcmtk_; + bool useDcmtk_; #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 DcmtkTranscoder dcmtk_; @@ -55,6 +55,7 @@ protected: virtual bool Transcode(std::string& target, + bool& hasSopInstanceUidChanged /* out */, const void* buffer, size_t size, const std::set& allowedSyntaxes, @@ -62,28 +63,38 @@ public: /** - * If "tryDcmtk" is "true", the transcoder will first try and call + * If "useDcmtk" is "true", the transcoder will first try and call * DCMTK, before calling its own "Transcode()" implementation. **/ - MemoryBufferTranscoder(bool tryDcmtk); + MemoryBufferTranscoder(); + + void SetDcmtkUsed(bool used); + + bool IsDcmtkUsed() const + { + return useDcmtk_; + } virtual bool TranscodeToBuffer(std::string& target, + bool& hasSopInstanceUidChanged /* out */, const void* buffer, size_t size, const std::set& allowedSyntaxes, bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; - virtual DcmFileFormat* TranscodeToParsed(const void* buffer, + virtual DcmFileFormat* TranscodeToParsed(bool& hasSopInstanceUidChanged /* out */, + const void* buffer, size_t size, const std::set& allowedSyntaxes, bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; virtual bool HasInplaceTranscode() const ORTHANC_OVERRIDE { - return tryDcmtk_; + return useDcmtk_; } - virtual bool InplaceTranscode(DcmFileFormat& dicom, + virtual bool InplaceTranscode(bool& hasSopInstanceUidChanged /* out */, + DcmFileFormat& dicom, const std::set& allowedSyntaxes, bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; }; diff -r 7a5fa8f307e9 -r 8f7ad4989fec OrthancServer/LuaScripting.cpp --- a/OrthancServer/LuaScripting.cpp Wed May 06 12:48:28 2020 +0200 +++ b/OrthancServer/LuaScripting.cpp Thu May 07 11:13:29 2020 +0200 @@ -588,7 +588,7 @@ } // This is not a C-MOVE: No need to call "StoreScuCommand::SetMoveOriginator()" - return lock.AddStoreScuOperation(localAet, modality); + return lock.AddStoreScuOperation(context_, localAet, modality); } if (operation == "store-peer") diff -r 7a5fa8f307e9 -r 8f7ad4989fec OrthancServer/OrthancMoveRequestHandler.cpp --- a/OrthancServer/OrthancMoveRequestHandler.cpp Wed May 06 12:48:28 2020 +0200 +++ b/OrthancServer/OrthancMoveRequestHandler.cpp Thu May 07 11:13:29 2020 +0200 @@ -121,7 +121,7 @@ const void* data = dicom.empty() ? NULL : dicom.c_str(); connection_->Store(sopClassUid, sopInstanceUid, data, dicom.size(), - originatorAet_, originatorId_); + true, originatorAet_, originatorId_); return Status_Success; } diff -r 7a5fa8f307e9 -r 8f7ad4989fec OrthancServer/OrthancRestApi/OrthancRestModalities.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Wed May 06 12:48:28 2020 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Thu May 07 11:13:29 2020 +0200 @@ -113,23 +113,19 @@ static void DicomEcho(RestApiPostCall& call) { - try - { - DicomControlUserConnection connection(GetAssociationParameters(call)); + DicomControlUserConnection connection(GetAssociationParameters(call)); - if (connection.Echo()) - { - // Echo has succeeded - call.GetOutput().AnswerBuffer("{}", MimeType_Json); - return; - } + if (connection.Echo()) + { + // Echo has succeeded + call.GetOutput().AnswerBuffer("{}", MimeType_Json); + return; } - catch (OrthancException&) + else { + // Echo has failed + call.GetOutput().SignalError(HttpStatus_500_InternalServerError); } - - // Echo has failed - call.GetOutput().SignalError(HttpStatus_500_InternalServerError); } @@ -1008,8 +1004,8 @@ DicomStoreUserConnection connection(GetAssociationParameters(call, body)); std::string sopClassUid, sopInstanceUid; - connection.Store(sopClassUid, sopInstanceUid, - call.GetBodyData(), call.GetBodySize()); + connection.Store(sopClassUid, sopInstanceUid, call.GetBodyData(), + call.GetBodySize(), false /* Not a C-MOVE */, "", 0); Json::Value answer = Json::objectValue; answer[SOP_CLASS_UID] = sopClassUid; diff -r 7a5fa8f307e9 -r 8f7ad4989fec OrthancServer/ServerContext.cpp --- a/OrthancServer/ServerContext.cpp Wed May 06 12:48:28 2020 +0200 +++ b/OrthancServer/ServerContext.cpp Thu May 07 11:13:29 2020 +0200 @@ -35,6 +35,7 @@ #include "ServerContext.h" #include "../Core/Cache/SharedArchive.h" +#include "../Core/DicomParsing/DcmtkTranscoder.h" #include "../Core/DicomParsing/FromDcmtkBridge.h" #include "../Core/FileStorage/StorageAccessor.h" #include "../Core/HttpServer/FilesystemHttpSender.h" @@ -243,7 +244,8 @@ metricsRegistry_(new MetricsRegistry), isHttpServerSecure_(true), isExecuteLuaEnabled_(false), - overwriteInstances_(false) + overwriteInstances_(false), + dcmtkTranscoder_(new DcmtkTranscoder) { { OrthancConfiguration::ReaderLock lock; @@ -264,6 +266,9 @@ // New configuration option in Orthanc 1.6.0 storageCommitmentReports_.reset(new StorageCommitmentReports(lock.GetConfiguration().GetUnsignedIntegerParameter("StorageCommitmentReportsSize", 100))); + + // New option in Orthanc 1.7.0 + transcodingEnabled_ = lock.GetConfiguration().GetBooleanParameter("TranscodingEnabled", true); } jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200); @@ -1108,4 +1113,66 @@ return NULL; } + + + void ServerContext::StoreWithTranscoding(std::string& sopClassUid, + std::string& sopInstanceUid, + DicomStoreUserConnection& connection, + const std::string& dicom, + bool hasMoveOriginator, + const std::string& moveOriginatorAet, + uint16_t moveOriginatorId) + { + const void* data = dicom.empty() ? NULL : dicom.c_str(); + + if (!transcodingEnabled_ || + !connection.GetParameters().GetRemoteModality().IsTranscodingAllowed()) + { + connection.Store(sopClassUid, sopInstanceUid, data, dicom.size(), + hasMoveOriginator, moveOriginatorAet, moveOriginatorId); + } + else + { + IDicomTranscoder* transcoder = dcmtkTranscoder_.get(); + +#if ORTHANC_ENABLE_PLUGINS == 1 + if (HasPlugins()) + { + transcoder = &GetPlugins(); + } +#endif + + if (transcoder == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + connection.Transcode(sopClassUid, sopInstanceUid, *transcoder, data, dicom.size(), + hasMoveOriginator, moveOriginatorAet, moveOriginatorId); + } + } + } + + + bool ServerContext::TranscodeMemoryBuffer(std::string& target, + bool& hasSopInstanceUidChanged, + const std::string& source, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) + { + const char* data = source.empty() ? NULL : source.c_str(); + +#if ORTHANC_ENABLE_PLUGINS == 1 + if (HasPlugins()) + { + return GetPlugins().TranscodeToBuffer( + target, hasSopInstanceUidChanged, data, source.size(), allowedSyntaxes, allowNewSopInstanceUid); + } +#endif + + assert(dcmtkTranscoder_.get() != NULL); + return dcmtkTranscoder_->TranscodeToBuffer( + target, hasSopInstanceUidChanged, data, source.size(), allowedSyntaxes, allowNewSopInstanceUid); + } } diff -r 7a5fa8f307e9 -r 8f7ad4989fec OrthancServer/ServerContext.h --- a/OrthancServer/ServerContext.h Wed May 06 12:48:28 2020 +0200 +++ b/OrthancServer/ServerContext.h Thu May 07 11:13:29 2020 +0200 @@ -40,6 +40,7 @@ #include "ServerJobs/IStorageCommitmentFactory.h" #include "../Core/Cache/MemoryCache.h" +#include "../Core/DicomParsing/IDicomTranscoder.h" namespace Orthanc @@ -225,6 +226,9 @@ std::unique_ptr storageCommitmentReports_; + bool transcodingEnabled_; + std::unique_ptr dcmtkTranscoder_; + public: class DicomCacheLocker : public boost::noncopyable { @@ -450,5 +454,20 @@ { return *storageCommitmentReports_; } + + void StoreWithTranscoding(std::string& sopClassUid, + std::string& sopInstanceUid, + DicomStoreUserConnection& connection, + const std::string& dicom, + bool hasMoveOriginator, + const std::string& moveOriginatorAet, + uint16_t moveOriginatorId); + + // This method can be used even if "TranscodingEnabled" is set to "false" + bool TranscodeMemoryBuffer(std::string& target, + bool& hasSopInstanceUidChanged, + const std::string& source, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid); }; } diff -r 7a5fa8f307e9 -r 8f7ad4989fec OrthancServer/ServerJobs/DicomModalityStoreJob.cpp --- a/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp Wed May 06 12:48:28 2020 +0200 +++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp Thu May 07 11:13:29 2020 +0200 @@ -74,18 +74,8 @@ } std::string sopClassUid, sopInstanceUid; - - const void* data = dicom.empty() ? NULL : dicom.c_str(); - - if (HasMoveOriginator()) - { - connection_->Store(sopClassUid, sopInstanceUid, data, dicom.size(), - moveOriginatorAet_, moveOriginatorId_); - } - else - { - connection_->Store(sopClassUid, sopInstanceUid, data, dicom.size()); - } + context_.StoreWithTranscoding(sopClassUid, sopInstanceUid, *connection_, dicom, + HasMoveOriginator(), moveOriginatorAet_, moveOriginatorId_); if (storageCommitment_) { diff -r 7a5fa8f307e9 -r 8f7ad4989fec OrthancServer/ServerJobs/LuaJobManager.cpp --- a/OrthancServer/ServerJobs/LuaJobManager.cpp Wed May 06 12:48:28 2020 +0200 +++ b/OrthancServer/ServerJobs/LuaJobManager.cpp Thu May 07 11:13:29 2020 +0200 @@ -200,11 +200,13 @@ } - size_t LuaJobManager::Lock::AddStoreScuOperation(const std::string& localAet, + size_t LuaJobManager::Lock::AddStoreScuOperation(ServerContext& context, + const std::string& localAet, const RemoteModalityParameters& modality) { assert(jobLock_.get() != NULL); - return jobLock_->AddOperation(new StoreScuOperation(that_.connectionManager_, localAet, modality)); + return jobLock_->AddOperation(new StoreScuOperation( + context, that_.connectionManager_, localAet, modality)); } diff -r 7a5fa8f307e9 -r 8f7ad4989fec OrthancServer/ServerJobs/LuaJobManager.h --- a/OrthancServer/ServerJobs/LuaJobManager.h Wed May 06 12:48:28 2020 +0200 +++ b/OrthancServer/ServerJobs/LuaJobManager.h Thu May 07 11:13:29 2020 +0200 @@ -91,7 +91,8 @@ size_t AddDeleteResourceOperation(ServerContext& context); - size_t AddStoreScuOperation(const std::string& localAet, + size_t AddStoreScuOperation(ServerContext& context, + const std::string& localAet, const RemoteModalityParameters& modality); size_t AddStorePeerOperation(const WebServiceParameters& peer); diff -r 7a5fa8f307e9 -r 8f7ad4989fec OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp --- a/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp Wed May 06 12:48:28 2020 +0200 +++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp Thu May 07 11:13:29 2020 +0200 @@ -35,6 +35,7 @@ #include "StoreScuOperation.h" #include "DicomInstanceOperationValue.h" +#include "../../ServerContext.h" #include "../../../Core/Logging.h" #include "../../../Core/OrthancException.h" @@ -63,10 +64,9 @@ std::string dicom; instance.ReadDicom(dicom); - const void* data = dicom.empty() ? NULL : dicom.c_str(); - std::string sopClassUid, sopInstanceUid; // Unused - lock.GetConnection().Store(sopClassUid, sopInstanceUid, data, dicom.size()); + context_.StoreWithTranscoding(sopClassUid, sopInstanceUid, lock.GetConnection(), dicom, + false /* Not a C-MOVE */, "", 0); } catch (OrthancException& e) { @@ -87,8 +87,10 @@ } - StoreScuOperation::StoreScuOperation(TimeoutDicomConnectionManager& connectionManager, + StoreScuOperation::StoreScuOperation(ServerContext& context, + TimeoutDicomConnectionManager& connectionManager, const Json::Value& serialized) : + context_(context), connectionManager_(connectionManager) { if (SerializationToolbox::ReadString(serialized, "Type") != "StoreScu" || diff -r 7a5fa8f307e9 -r 8f7ad4989fec OrthancServer/ServerJobs/Operations/StoreScuOperation.h --- a/OrthancServer/ServerJobs/Operations/StoreScuOperation.h Wed May 06 12:48:28 2020 +0200 +++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.h Thu May 07 11:13:29 2020 +0200 @@ -38,24 +38,30 @@ namespace Orthanc { + class ServerContext; + class StoreScuOperation : public IJobOperation { private: + ServerContext& context_; TimeoutDicomConnectionManager& connectionManager_; std::string localAet_; RemoteModalityParameters modality_; public: - StoreScuOperation(TimeoutDicomConnectionManager& connectionManager, + StoreScuOperation(ServerContext& context, + TimeoutDicomConnectionManager& connectionManager, const std::string& localAet, const RemoteModalityParameters& modality) : + context_(context), connectionManager_(connectionManager), localAet_(localAet), modality_(modality) { } - StoreScuOperation(TimeoutDicomConnectionManager& connectionManager, + StoreScuOperation(ServerContext& context, + TimeoutDicomConnectionManager& connectionManager, const Json::Value& serialized); const std::string& GetLocalAet() const diff -r 7a5fa8f307e9 -r 8f7ad4989fec OrthancServer/ServerJobs/OrthancJobUnserializer.cpp --- a/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp Wed May 06 12:48:28 2020 +0200 +++ b/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp Thu May 07 11:13:29 2020 +0200 @@ -127,7 +127,7 @@ else if (type == "StoreScu") { return new StoreScuOperation( - context_.GetLuaScripting().GetDicomConnectionManager(), source); + context_, context_.GetLuaScripting().GetDicomConnectionManager(), source); } else if (type == "SystemCall") { diff -r 7a5fa8f307e9 -r 8f7ad4989fec Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Wed May 06 12:48:28 2020 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Thu May 07 11:13:29 2020 +0200 @@ -4791,4 +4791,16 @@ return NULL; } + + + bool OrthancPlugins::Transcode(std::string& target, + bool& hasSopInstanceUidChanged /* out */, + const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) + { + // TODO + return false; + } } diff -r 7a5fa8f307e9 -r 8f7ad4989fec Plugins/Engine/OrthancPlugins.h --- a/Plugins/Engine/OrthancPlugins.h Wed May 06 12:48:28 2020 +0200 +++ b/Plugins/Engine/OrthancPlugins.h Thu May 07 11:13:29 2020 +0200 @@ -56,6 +56,7 @@ #include "../../Core/DicomNetworking/IFindRequestHandlerFactory.h" #include "../../Core/DicomNetworking/IMoveRequestHandlerFactory.h" #include "../../Core/DicomNetworking/IWorklistRequestHandlerFactory.h" +#include "../../Core/DicomParsing/MemoryBufferTranscoder.h" #include "../../Core/FileStorage/IStorageArea.h" #include "../../Core/HttpServer/IHttpHandler.h" #include "../../Core/HttpServer/IIncomingHttpRequestFilter.h" @@ -82,7 +83,8 @@ public IIncomingHttpRequestFilter, public IFindRequestHandlerFactory, public IMoveRequestHandlerFactory, - public IStorageCommitmentFactory + public IStorageCommitmentFactory, + public MemoryBufferTranscoder { private: class PImpl; @@ -223,6 +225,15 @@ _OrthancPluginService service, const void* parameters); + protected: + // From "MemoryBufferTranscoder" + virtual bool Transcode(std::string& target, + bool& hasSopInstanceUidChanged /* out */, + const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + public: OrthancPlugins(); diff -r 7a5fa8f307e9 -r 8f7ad4989fec Resources/Configuration.json --- a/Resources/Configuration.json Wed May 06 12:48:28 2020 +0200 +++ b/Resources/Configuration.json Thu May 07 11:13:29 2020 +0200 @@ -209,9 +209,17 @@ * registered remote SCU modalities. Starting with Orthanc 1.5.0, * it is possible to specify which DICOM commands are allowed, * separately for each remote modality, using the syntax - * below. The "AllowEcho" (resp. "AllowStore") option only has an - * effect respectively if global option "DicomAlwaysAllowEcho" - * (resp. "DicomAlwaysAllowStore") is set to false. + * below. + * + * The "AllowEcho" (resp. "AllowStore") option only has an effect + * respectively if global option "DicomAlwaysAllowEcho" + * (resp. "DicomAlwaysAllowStore") is set to "false". + * + * Starting with Orthanc 1.7.0, "AllowTranscoding" can be used to + * disable the transcoding to uncompressed transfer syntaxes if + * the remote modality doesn't support compressed transfer + * syntaxes. This option only has an effect if global option + * "EnableTranscoding" is set to "true". **/ //"untrusted" : { // "AET" : "ORTHANC", @@ -222,7 +230,8 @@ // "AllowFind" : false, // "AllowMove" : false, // "AllowStore" : true, - // "AllowStorageCommitment" : false // new in 1.6.0 + // "AllowStorageCommitment" : false, // new in 1.6.0 + // "AllowTranscoding" : true // new in 1.7.0 //} }, @@ -532,5 +541,10 @@ // Maximum number of storage commitment reports (i.e. received from // remote modalities) to be kept in memory (new in Orthanc 1.6.0). - "StorageCommitmentReportsSize" : 100 + "StorageCommitmentReportsSize" : 100, + + // Whether Orthanc transcodes DICOM files to an uncompressed + // transfer syntax, if remote modalities do not support compressed + // transfer syntaxes (new in Orthanc 1.7.0). + "TranscodingEnabled" : true } diff -r 7a5fa8f307e9 -r 8f7ad4989fec UnitTestsSources/FromDcmtkTests.cpp --- a/UnitTestsSources/FromDcmtkTests.cpp Wed May 06 12:48:28 2020 +0200 +++ b/UnitTestsSources/FromDcmtkTests.cpp Thu May 07 11:13:29 2020 +0200 @@ -1955,7 +1955,7 @@ std::string c, i; try { - scu.Transcode(c, i, transcoder, source.c_str(), source.size()); + scu.Transcode(c, i, transcoder, source.c_str(), source.size(), false, "", 0); } catch (OrthancException& e) { @@ -1991,15 +1991,29 @@ s.insert(a); std::string t; - - if (!transcoder.TranscodeToBuffer(t, source.c_str(), source.size(), s, true)) + + bool hasSopInstanceUidChanged; + + if (!transcoder.TranscodeToBuffer(t, hasSopInstanceUidChanged, source.c_str(), source.size(), s, true)) { printf("**************** CANNOT: [%s] => [%s]\n", GetTransferSyntaxUid(sourceSyntax), GetTransferSyntaxUid(a)); } else { + bool lossy = (a == DicomTransferSyntax_JPEGProcess1 || + a == DicomTransferSyntax_JPEGProcess2_4 || + a == DicomTransferSyntax_JPEGLSLossy); + printf("SIZE: %lu\n", t.size()); + if (hasSopInstanceUidChanged) + { + ASSERT_TRUE(lossy); + } + else + { + ASSERT_FALSE(lossy); + } } } } diff -r 7a5fa8f307e9 -r 8f7ad4989fec UnitTestsSources/MultiThreadingTests.cpp --- a/UnitTestsSources/MultiThreadingTests.cpp Wed May 06 12:48:28 2020 +0200 +++ b/UnitTestsSources/MultiThreadingTests.cpp Thu May 07 11:13:29 2020 +0200 @@ -1415,7 +1415,7 @@ modality.SetPortNumber(1000); modality.SetManufacturer(ModalityManufacturer_StoreScp); - StoreScuOperation operation(luaManager, "TEST", modality); + StoreScuOperation operation(GetContext(), luaManager, "TEST", modality); ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); operation.Serialize(s); @@ -1903,6 +1903,7 @@ ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Move)); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction)); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + ASSERT_TRUE(modality.IsTranscodingAllowed()); } s = Json::nullValue; @@ -1933,6 +1934,7 @@ ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Move)); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction)); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + ASSERT_TRUE(modality.IsTranscodingAllowed()); } s["Port"] = "46"; @@ -1999,6 +2001,7 @@ ASSERT_EQ(104u, modality.GetPortNumber()); ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction)); ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + ASSERT_TRUE(modality.IsTranscodingAllowed()); } { @@ -2008,6 +2011,7 @@ s["AET"] = "AET"; s["Host"] = "host"; s["Port"] = "104"; + s["AllowTranscoding"] = false; RemoteModalityParameters modality(s); ASSERT_TRUE(modality.IsAdvancedFormatNeeded()); @@ -2016,6 +2020,7 @@ ASSERT_EQ(104u, modality.GetPortNumber()); ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction)); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + ASSERT_FALSE(modality.IsTranscodingAllowed()); } { @@ -2033,6 +2038,7 @@ ASSERT_EQ(104u, modality.GetPortNumber()); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction)); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + ASSERT_TRUE(modality.IsTranscodingAllowed()); } }