# HG changeset patch # User Alain Mazy # Date 1588843935 -7200 # Node ID 210af28c4087449b2f902faa1833027cd43ed28d # Parent 37cf1889667a62d9d60f8fa41c5dcb8d867c1c4c# Parent 8f7ad4989fec9169d412cd85bbf92a819bf2c75f merge diff -r 37cf1889667a -r 210af28c4087 CMakeLists.txt --- a/CMakeLists.txt Thu May 07 11:31:58 2020 +0200 +++ b/CMakeLists.txt Thu May 07 11:32:15 2020 +0200 @@ -13,6 +13,7 @@ set(ENABLE_CRYPTO_OPTIONS ON) set(ENABLE_DCMTK ON) set(ENABLE_DCMTK_NETWORKING ON) +set(ENABLE_DCMTK_TRANSCODING ON) set(ENABLE_GOOGLE_TEST ON) set(ENABLE_JPEG ON) set(ENABLE_LOCALE ON) @@ -25,9 +26,6 @@ set(ENABLE_WEB_SERVER ON) set(ENABLE_ZLIB ON) -# To test transcoding -set(ENABLE_DCMTK_TRANSCODING ON) - set(HAS_EMBEDDED_RESOURCES ON) diff -r 37cf1889667a -r 210af28c4087 Core/DicomNetworking/DicomAssociation.cpp --- a/Core/DicomNetworking/DicomAssociation.cpp Thu May 07 11:31:58 2020 +0200 +++ b/Core/DicomNetworking/DicomAssociation.cpp Thu May 07 11:32:15 2020 +0200 @@ -259,10 +259,10 @@ LOG(INFO) << "Opening a DICOM SCU connection from AET \"" << parameters.GetLocalApplicationEntityTitle() - << "\" to AET \"" << parameters.GetRemoteApplicationEntityTitle() - << "\" on host " << parameters.GetRemoteHost() - << ":" << parameters.GetRemotePort() - << " (manufacturer: " << EnumerationToString(parameters.GetRemoteManufacturer()) << ")"; + << "\" to AET \"" << parameters.GetRemoteModality().GetApplicationEntityTitle() + << "\" on host " << parameters.GetRemoteModality().GetHost() + << ":" << parameters.GetRemoteModality().GetPortNumber() + << " (manufacturer: " << EnumerationToString(parameters.GetRemoteModality().GetManufacturer()) << ")"; CheckConnecting(parameters, ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ acseTimeout, &net_)); CheckConnecting(parameters, ASC_createAssociationParameters(¶ms_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU)); @@ -270,7 +270,7 @@ // Set this application's title and the called application's title in the params CheckConnecting(parameters, ASC_setAPTitles( params_, parameters.GetLocalApplicationEntityTitle().c_str(), - parameters.GetRemoteApplicationEntityTitle().c_str(), NULL)); + parameters.GetRemoteModality().GetApplicationEntityTitle().c_str(), NULL)); // Set the network addresses of the local and remote entities char localHost[HOST_NAME_MAX]; @@ -284,7 +284,8 @@ snprintf #endif (remoteHostAndPort, HOST_NAME_MAX - 1, "%s:%d", - parameters.GetRemoteHost().c_str(), parameters.GetRemotePort()); + parameters.GetRemoteModality().GetHost().c_str(), + parameters.GetRemoteModality().GetPortNumber()); CheckConnecting(parameters, ASC_setPresentationAddresses(params_, localHost, remoteHostAndPort)); @@ -339,7 +340,7 @@ else { LOG(WARNING) << "Unknown transfer syntax received from AET \"" - << parameters.GetRemoteApplicationEntityTitle() + << parameters.GetRemoteModality().GetApplicationEntityTitle() << "\": " << pc->acceptedTransferSyntax; } } @@ -352,7 +353,7 @@ { throw OrthancException(ErrorCode_NoPresentationContext, "Unable to negotiate a presentation context with AET \"" + - parameters.GetRemoteApplicationEntityTitle() + "\""); + parameters.GetRemoteModality().GetApplicationEntityTitle() + "\""); } } @@ -517,7 +518,7 @@ throw OrthancException(ErrorCode_NetworkProtocol, "DicomAssociation - " + command + " to AET \"" + - parameters.GetRemoteApplicationEntityTitle() + + parameters.GetRemoteModality().GetApplicationEntityTitle() + "\": " + info); } } @@ -604,7 +605,7 @@ **/ LOG(INFO) << "Reporting modality \"" - << parameters.GetRemoteApplicationEntityTitle() + << parameters.GetRemoteModality().GetApplicationEntityTitle() << "\" about storage commitment transaction: " << transactionUid << " (" << successSopClassUids.size() << " successes, " << failedSopClassUids.size() << " failures)"; @@ -654,7 +655,7 @@ { throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " "Unable to send N-EVENT-REPORT request to AET: " + - parameters.GetRemoteApplicationEntityTitle()); + parameters.GetRemoteModality().GetApplicationEntityTitle()); } if (!DIMSE_sendMessageUsingMemoryData( @@ -682,7 +683,7 @@ { throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " "Unable to read N-EVENT-REPORT response from AET: " + - parameters.GetRemoteApplicationEntityTitle()); + parameters.GetRemoteModality().GetApplicationEntityTitle()); } const T_DIMSE_N_EventReportRSP& content = message.msg.NEventReportRSP; @@ -696,14 +697,14 @@ { throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " "Badly formatted N-EVENT-REPORT response from AET: " + - parameters.GetRemoteApplicationEntityTitle()); + parameters.GetRemoteModality().GetApplicationEntityTitle()); } if (content.DimseStatus != 0 /* success */) { throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " "The request cannot be handled by remote AET: " + - parameters.GetRemoteApplicationEntityTitle()); + parameters.GetRemoteModality().GetApplicationEntityTitle()); } } @@ -767,7 +768,7 @@ **/ LOG(INFO) << "Request to modality \"" - << parameters.GetRemoteApplicationEntityTitle() + << parameters.GetRemoteModality().GetApplicationEntityTitle() << "\" about storage commitment for " << sopClassUids.size() << " instances, with transaction UID: " << transactionUid; const DIC_US messageId = association.GetDcmtkAssociation().nextMsgID++; @@ -801,7 +802,7 @@ { throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " "Unable to send N-ACTION request to AET: " + - parameters.GetRemoteApplicationEntityTitle()); + parameters.GetRemoteModality().GetApplicationEntityTitle()); } if (!DIMSE_sendMessageUsingMemoryData( @@ -829,7 +830,7 @@ { throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " "Unable to read N-ACTION response from AET: " + - parameters.GetRemoteApplicationEntityTitle()); + parameters.GetRemoteModality().GetApplicationEntityTitle()); } const T_DIMSE_N_ActionRSP& content = message.msg.NActionRSP; @@ -843,14 +844,14 @@ { throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " "Badly formatted N-ACTION response from AET: " + - parameters.GetRemoteApplicationEntityTitle()); + parameters.GetRemoteModality().GetApplicationEntityTitle()); } if (content.DimseStatus != 0 /* success */) { throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " "The request cannot be handled by remote AET: " + - parameters.GetRemoteApplicationEntityTitle()); + parameters.GetRemoteModality().GetApplicationEntityTitle()); } } diff -r 37cf1889667a -r 210af28c4087 Core/DicomNetworking/DicomAssociationParameters.cpp --- a/Core/DicomNetworking/DicomAssociationParameters.cpp Thu May 07 11:31:58 2020 +0200 +++ b/Core/DicomNetworking/DicomAssociationParameters.cpp Thu May 07 11:32:15 2020 +0200 @@ -37,6 +37,7 @@ #include "../Compatibility.h" #include "../Logging.h" #include "../OrthancException.h" +#include "../SerializationToolbox.h" #include "NetworkingCompatibility.h" #include @@ -48,68 +49,104 @@ namespace Orthanc { - void DicomAssociationParameters::ReadDefaultTimeout() + void DicomAssociationParameters::CheckHost(const std::string& host) + { + if (host.size() > HOST_NAME_MAX - 10) + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Invalid host name (too long): " + host); + } + } + + + uint32_t DicomAssociationParameters::GetDefaultTimeout() { boost::mutex::scoped_lock lock(defaultTimeoutMutex_); - timeout_ = defaultTimeout_; + return defaultTimeout_; } DicomAssociationParameters::DicomAssociationParameters() : - localAet_("STORESCU"), - remoteAet_("ANY-SCP"), - remoteHost_("127.0.0.1"), - remotePort_(104), - manufacturer_(ModalityManufacturer_Generic) + localAet_("ORTHANC"), + timeout_(GetDefaultTimeout()) { - ReadDefaultTimeout(); + remote_.SetApplicationEntityTitle("ANY-SCP"); } DicomAssociationParameters::DicomAssociationParameters(const std::string& localAet, const RemoteModalityParameters& remote) : localAet_(localAet), - remoteAet_(remote.GetApplicationEntityTitle()), - remoteHost_(remote.GetHost()), - remotePort_(remote.GetPortNumber()), - manufacturer_(remote.GetManufacturer()), - timeout_(defaultTimeout_) + timeout_(GetDefaultTimeout()) { - ReadDefaultTimeout(); + SetRemoteModality(remote); } - void DicomAssociationParameters::SetRemoteHost(const std::string& host) + void DicomAssociationParameters::SetRemoteModality(const RemoteModalityParameters& remote) { - if (host.size() > HOST_NAME_MAX - 10) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Invalid host name (too long): " + host); - } - - remoteHost_ = host; + CheckHost(remote.GetHost()); + remote_ = remote; } - void DicomAssociationParameters::SetRemoteModality(const RemoteModalityParameters& parameters) + void DicomAssociationParameters::SetRemoteHost(const std::string& host) { - SetRemoteApplicationEntityTitle(parameters.GetApplicationEntityTitle()); - SetRemoteHost(parameters.GetHost()); - SetRemotePort(parameters.GetPortNumber()); - SetRemoteManufacturer(parameters.GetManufacturer()); + CheckHost(host); + remote_.SetHost(host); } bool DicomAssociationParameters::IsEqual(const DicomAssociationParameters& other) const { return (localAet_ == other.localAet_ && - remoteAet_ == other.remoteAet_ && - remoteHost_ == other.remoteHost_ && - remotePort_ == other.remotePort_ && - manufacturer_ == other.manufacturer_); + remote_.GetApplicationEntityTitle() == other.remote_.GetApplicationEntityTitle() && + remote_.GetHost() == other.remote_.GetHost() && + remote_.GetPortNumber() == other.remote_.GetPortNumber() && + remote_.GetManufacturer() == other.remote_.GetManufacturer() && + timeout_ == other.timeout_); } + + static const char* const LOCAL_AET = "LocalAet"; + static const char* const REMOTE = "Remote"; + static const char* const TIMEOUT = "Timeout"; // New in Orthanc in 1.7.0 + + + void DicomAssociationParameters::SerializeJob(Json::Value& target) const + { + if (target.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + target[LOCAL_AET] = localAet_; + remote_.Serialize(target[REMOTE], true /* force advanced format */); + target[TIMEOUT] = timeout_; + } + } + + + DicomAssociationParameters DicomAssociationParameters::UnserializeJob(const Json::Value& serialized) + { + if (serialized.type() == Json::objectValue) + { + DicomAssociationParameters result; + result.remote_ = RemoteModalityParameters(serialized[REMOTE]); + result.localAet_ = SerializationToolbox::ReadString(serialized, LOCAL_AET); + result.timeout_ = SerializationToolbox::ReadInteger(serialized, TIMEOUT, GetDefaultTimeout()); + + return result; + } + else + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + void DicomAssociationParameters::SetDefaultTimeout(uint32_t seconds) { LOG(INFO) << "Default timeout for DICOM connections if Orthanc acts as SCU (client): " diff -r 37cf1889667a -r 210af28c4087 Core/DicomNetworking/DicomAssociationParameters.h --- a/Core/DicomNetworking/DicomAssociationParameters.h Thu May 07 11:31:58 2020 +0200 +++ b/Core/DicomNetworking/DicomAssociationParameters.h Thu May 07 11:32:15 2020 +0200 @@ -35,6 +35,8 @@ #include "RemoteModalityParameters.h" +#include + class OFCondition; // From DCMTK namespace Orthanc @@ -42,14 +44,11 @@ class DicomAssociationParameters { private: - std::string localAet_; - std::string remoteAet_; - std::string remoteHost_; - uint16_t remotePort_; - ModalityManufacturer manufacturer_; - uint32_t timeout_; + std::string localAet_; + RemoteModalityParameters remote_; + uint32_t timeout_; - void ReadDefaultTimeout(); + static void CheckHost(const std::string& host); public: DicomAssociationParameters(); @@ -62,52 +61,38 @@ return localAet_; } - const std::string& GetRemoteApplicationEntityTitle() const - { - return remoteAet_; - } - - const std::string& GetRemoteHost() const - { - return remoteHost_; - } - - uint16_t GetRemotePort() const - { - return remotePort_; - } - - ModalityManufacturer GetRemoteManufacturer() const - { - return manufacturer_; - } - void SetLocalApplicationEntityTitle(const std::string& aet) { localAet_ = aet; } + const RemoteModalityParameters& GetRemoteModality() const + { + return remote_; + } + + void SetRemoteModality(const RemoteModalityParameters& parameters); + void SetRemoteApplicationEntityTitle(const std::string& aet) { - remoteAet_ = aet; + remote_.SetApplicationEntityTitle(aet); } void SetRemoteHost(const std::string& host); void SetRemotePort(uint16_t port) { - remotePort_ = port; + remote_.SetPortNumber(port); } void SetRemoteManufacturer(ModalityManufacturer manufacturer) { - manufacturer_ = manufacturer; + remote_.SetManufacturer(manufacturer); } - void SetRemoteModality(const RemoteModalityParameters& parameters); - bool IsEqual(const DicomAssociationParameters& other) const; + // Setting it to "0" disables the timeout (infinite wait) void SetTimeout(uint32_t seconds) { timeout_ = seconds; @@ -122,7 +107,13 @@ { return timeout_ != 0; } + + void SerializeJob(Json::Value& target) const; + + static DicomAssociationParameters UnserializeJob(const Json::Value& serialized); static void SetDefaultTimeout(uint32_t seconds); + + static uint32_t GetDefaultTimeout(); }; } diff -r 37cf1889667a -r 210af28c4087 Core/DicomNetworking/DicomControlUserConnection.cpp --- a/Core/DicomNetworking/DicomControlUserConnection.cpp Thu May 07 11:31:58 2020 +0200 +++ b/Core/DicomNetworking/DicomControlUserConnection.cpp Thu May 07 11:32:15 2020 +0200 @@ -257,7 +257,7 @@ if (presID == 0) { throw OrthancException(ErrorCode_DicomFindUnavailable, - "Remote AET is " + parameters_.GetRemoteApplicationEntityTitle()); + "Remote AET is " + parameters_.GetRemoteModality().GetApplicationEntityTitle()); } T_DIMSE_C_FindRQ request; @@ -309,14 +309,14 @@ throw OrthancException(ErrorCode_NetworkProtocol, HttpStatus_422_UnprocessableEntity, "C-FIND SCU to AET \"" + - parameters_.GetRemoteApplicationEntityTitle() + + parameters_.GetRemoteModality().GetApplicationEntityTitle() + "\" has failed with DIMSE status 0x" + buf + " (unable to process - invalid query ?)"); } else { throw OrthancException(ErrorCode_NetworkProtocol, "C-FIND SCU to AET \"" + - parameters_.GetRemoteApplicationEntityTitle() + + parameters_.GetRemoteModality().GetApplicationEntityTitle() + "\" has failed with DIMSE status 0x" + buf); } } @@ -331,7 +331,7 @@ association_->Open(parameters_); std::unique_ptr query( - ConvertQueryFields(fields, parameters_.GetRemoteManufacturer())); + ConvertQueryFields(fields, parameters_.GetRemoteModality().GetManufacturer())); DcmDataset* dataset = query->GetDcmtkObject().getDataset(); const char* sopClass = UID_MOVEStudyRootQueryRetrieveInformationModel; @@ -362,7 +362,7 @@ if (presID == 0) { throw OrthancException(ErrorCode_DicomMoveUnavailable, - "Remote AET is " + parameters_.GetRemoteApplicationEntityTitle()); + "Remote AET is " + parameters_.GetRemoteModality().GetApplicationEntityTitle()); } T_DIMSE_C_MoveRQ request; @@ -412,14 +412,14 @@ throw OrthancException(ErrorCode_NetworkProtocol, HttpStatus_422_UnprocessableEntity, "C-MOVE SCU to AET \"" + - parameters_.GetRemoteApplicationEntityTitle() + + parameters_.GetRemoteModality().GetApplicationEntityTitle() + "\" has failed with DIMSE status 0x" + buf + " (unable to process - resource not found ?)"); } else { throw OrthancException(ErrorCode_NetworkProtocol, "C-MOVE SCU to AET \"" + - parameters_.GetRemoteApplicationEntityTitle() + + parameters_.GetRemoteModality().GetApplicationEntityTitle() + "\" has failed with DIMSE status 0x" + buf); } } @@ -434,15 +434,6 @@ } - DicomControlUserConnection::DicomControlUserConnection(const std::string& localAet, - const RemoteModalityParameters& remote) : - parameters_(localAet, remote), - association_(new DicomAssociation) - { - SetupPresentationContexts(); - } - - void DicomControlUserConnection::Close() { assert(association_.get() != NULL); @@ -479,7 +470,7 @@ { DicomMap fields; NormalizeFindQuery(fields, level, originalFields); - query.reset(ConvertQueryFields(fields, parameters_.GetRemoteManufacturer())); + query.reset(ConvertQueryFields(fields, parameters_.GetRemoteModality().GetManufacturer())); } else { @@ -525,7 +516,7 @@ const char* universal; - if (parameters_.GetRemoteManufacturer() == ModalityManufacturer_GE) + if (parameters_.GetRemoteModality().GetManufacturer() == ModalityManufacturer_GE) { universal = "*"; } @@ -669,10 +660,6 @@ MoveInternal(targetAet, ResourceType_Instance, query); } - void DicomControlUserConnection::SetTimeout(uint32_t seconds) - { - parameters_.SetTimeout(seconds); - } void DicomControlUserConnection::FindWorklist(DicomFindAnswers& result, ParsedDicomFile& query) diff -r 37cf1889667a -r 210af28c4087 Core/DicomNetworking/DicomControlUserConnection.h --- a/Core/DicomNetworking/DicomControlUserConnection.h Thu May 07 11:31:58 2020 +0200 +++ b/Core/DicomNetworking/DicomControlUserConnection.h Thu May 07 11:32:15 2020 +0200 @@ -65,9 +65,6 @@ const DicomMap& fields); public: - DicomControlUserConnection(const std::string& localAet, - const RemoteModalityParameters& remote); - DicomControlUserConnection(const DicomAssociationParameters& params); const DicomAssociationParameters& GetParameters() const @@ -108,7 +105,5 @@ void FindWorklist(DicomFindAnswers& result, ParsedDicomFile& query); - - void SetTimeout(uint32_t seconds); // 0 = no timeout }; } diff -r 37cf1889667a -r 210af28c4087 Core/DicomNetworking/DicomStoreUserConnection.cpp --- a/Core/DicomNetworking/DicomStoreUserConnection.cpp Thu May 07 11:31:58 2020 +0200 +++ b/Core/DicomNetworking/DicomStoreUserConnection.cpp Thu May 07 11:32:15 2020 +0200 @@ -84,13 +84,13 @@ it = syntaxes.begin(); it != syntaxes.end(); ++it) { association_->ProposePresentationContext(sopClassUid, *it); + proposedOriginalClasses_.insert(std::make_pair(sopClassUid, *it)); } if (addLittleEndianImplicit) { - std::set uncompressed; - uncompressed.insert(DicomTransferSyntax_LittleEndianImplicit); - association_->ProposePresentationContext(sopClassUid, uncompressed); + association_->ProposePresentationContext(sopClassUid, DicomTransferSyntax_LittleEndianImplicit); + proposedOriginalClasses_.insert(std::make_pair(sopClassUid, DicomTransferSyntax_LittleEndianImplicit)); } if (addLittleEndianExplicit || @@ -109,6 +109,14 @@ } association_->ProposePresentationContext(sopClassUid, uncompressed); + + assert(!uncompressed.empty()); + if (addLittleEndianExplicit ^ addBigEndianExplicit) + { + // Only one transfer syntax was proposed for this presentation context + assert(uncompressed.size() == 1); + proposedOriginalClasses_.insert(std::make_pair(sopClassUid, *uncompressed.begin())); + } } return true; @@ -139,42 +147,27 @@ } - void DicomStoreUserConnection::Setup() - { - association_.reset(new DicomAssociation); - proposeCommonClasses_ = true; - proposeUncompressedSyntaxes_ = true; - proposeRetiredBigEndian_ = false; - } - - - DicomStoreUserConnection::DicomStoreUserConnection( - const std::string& localAet, - const RemoteModalityParameters& remote) : - parameters_(localAet, remote) - { - Setup(); - } - - DicomStoreUserConnection::DicomStoreUserConnection( const DicomAssociationParameters& params) : - parameters_(params) + parameters_(params), + association_(new DicomAssociation), + proposeCommonClasses_(true), + proposeUncompressedSyntaxes_(true), + proposeRetiredBigEndian_(false) { - Setup(); } void DicomStoreUserConnection::RegisterStorageClass(const std::string& sopClassUid, DicomTransferSyntax syntax) { - StorageClasses::iterator found = storageClasses_.find(sopClassUid); + RegisteredClasses::iterator found = registeredClasses_.find(sopClassUid); - if (found == storageClasses_.end()) + if (found == registeredClasses_.end()) { std::set ts; ts.insert(syntax); - storageClasses_[sopClassUid] = ts; + registeredClasses_[sopClassUid] = ts; } else { @@ -186,22 +179,26 @@ void DicomStoreUserConnection::LookupParameters(std::string& sopClassUid, std::string& sopInstanceUid, DicomTransferSyntax& transferSyntax, - DcmDataset& dataset) + DcmFileFormat& dicom) { + if (dicom.getDataset() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + OFString a, b; - if (!dataset.findAndGetOFString(DCM_SOPClassUID, a).good() || - !dataset.findAndGetOFString(DCM_SOPInstanceUID, b).good()) + if (!dicom.getDataset()->findAndGetOFString(DCM_SOPClassUID, a).good() || + !dicom.getDataset()->findAndGetOFString(DCM_SOPInstanceUID, b).good()) { throw OrthancException(ErrorCode_NoSopClassOrInstance, "Unable to determine the SOP class/instance for C-STORE with AET " + - parameters_.GetRemoteApplicationEntityTitle()); + parameters_.GetRemoteModality().GetApplicationEntityTitle()); } sopClassUid.assign(a.c_str()); sopInstanceUid.assign(b.c_str()); - if (!FromDcmtkBridge::LookupOrthancTransferSyntax( - transferSyntax, dataset.getOriginalXfer())) + if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transferSyntax, dicom)) { throw OrthancException(ErrorCode_InternalError, "Unknown transfer syntax from DCMTK"); @@ -216,7 +213,7 @@ { /** * Step 1: Check whether this presentation context is already - * available in the previously negociated assocation. + * available in the previously negotiated assocation. **/ if (LookupPresentationContext(presentationContextId, sopClassUid, transferSyntax)) @@ -227,24 +224,35 @@ // The association must be re-negotiated if (association_->IsOpen()) { - LOG(INFO) << "Re-negociating DICOM association with " - << parameters_.GetRemoteApplicationEntityTitle(); + LOG(INFO) << "Re-negotiating DICOM association with " + << parameters_.GetRemoteModality().GetApplicationEntityTitle(); + + if (proposedOriginalClasses_.find(std::make_pair(sopClassUid, transferSyntax)) != + proposedOriginalClasses_.end()) + { + LOG(INFO) << "The remote modality has already rejected SOP class UID \"" + << sopClassUid << "\" with transfer syntax \"" + << GetTransferSyntaxUid(transferSyntax) << "\", don't renegotiate"; + return false; + } } + + association_->ClearPresentationContexts(); + proposedOriginalClasses_.clear(); + RegisterStorageClass(sopClassUid, transferSyntax); // (*) + - association_->ClearPresentationContexts(); - RegisterStorageClass(sopClassUid, transferSyntax); - - /** * Step 2: Propose at least the mandatory SOP class. **/ { - StorageClasses::const_iterator mandatory = storageClasses_.find(sopClassUid); + RegisteredClasses::const_iterator mandatory = registeredClasses_.find(sopClassUid); - if (mandatory == storageClasses_.end() || + if (mandatory == registeredClasses_.end() || mandatory->second.find(transferSyntax) == mandatory->second.end()) { + // Should never fail because of (*) throw OrthancException(ErrorCode_InternalError); } @@ -263,8 +271,8 @@ * registered through the "RegisterStorageClass()" method. **/ - for (StorageClasses::const_iterator it = storageClasses_.begin(); - it != storageClasses_.end(); ++it) + for (RegisteredClasses::const_iterator it = registeredClasses_.begin(); + it != registeredClasses_.end(); ++it) { if (it->first != sopClassUid) { @@ -292,7 +300,7 @@ std::string c(dcmShortSCUStorageSOPClassUIDs[i]); if (c != sopClassUid && - storageClasses_.find(c) == storageClasses_.end()) + registeredClasses_.find(c) == registeredClasses_.end()) { ProposeStorageClass(c, ts); } @@ -312,13 +320,14 @@ void DicomStoreUserConnection::Store(std::string& sopClassUid, std::string& sopInstanceUid, - DcmDataset& dataset, + DcmFileFormat& dicom, + bool hasMoveOriginator, const std::string& moveOriginatorAET, uint16_t moveOriginatorID) { DicomTransferSyntax transferSyntax; - LookupParameters(sopClassUid, sopInstanceUid, transferSyntax, dataset); - + LookupParameters(sopClassUid, sopInstanceUid, transferSyntax, dicom); + uint8_t presID; if (!NegotiatePresentationContext(presID, sopClassUid, transferSyntax)) { @@ -327,7 +336,7 @@ "SOP class UID [" + sopClassUid + "] and transfer " "syntax [" + GetTransferSyntaxUid(transferSyntax) + "] " "while sending to modality [" + - parameters_.GetRemoteApplicationEntityTitle() + "]"); + parameters_.GetRemoteModality().GetApplicationEntityTitle() + "]"); } // Prepare the transmission of data @@ -339,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; @@ -349,12 +358,17 @@ request.opts |= O_STORE_MOVEORIGINATORID; } + if (dicom.getDataset() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + // Finally conduct transmission of data T_DIMSE_C_StoreRSP response; DcmDataset* statusDetail = NULL; DicomAssociation::CheckCondition( DIMSE_storeUser(&association_->GetDcmtkAssociation(), presID, &request, - NULL, &dataset, /*progressCallback*/ NULL, NULL, + NULL, dicom.getDataset(), /*progressCallback*/ NULL, NULL, /*opt_blockMode*/ (GetParameters().HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), /*opt_dimse_timeout*/ GetParameters().GetTimeout(), &response, &statusDetail, NULL), @@ -379,7 +393,7 @@ sprintf(buf, "%04X", response.DimseStatus); throw OrthancException(ErrorCode_NetworkProtocol, "C-STORE SCU to AET \"" + - GetParameters().GetRemoteApplicationEntityTitle() + + GetParameters().GetRemoteModality().GetApplicationEntityTitle() + "\" has failed with DIMSE status 0x" + buf); } } @@ -387,37 +401,148 @@ void DicomStoreUserConnection::Store(std::string& sopClassUid, std::string& sopInstanceUid, - ParsedDicomFile& parsed, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID) - { - if (parsed.GetDcmtkObject().getDataset() == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - Store(sopClassUid, sopInstanceUid, *parsed.GetDcmtkObject().getDataset(), - moveOriginatorAET, moveOriginatorID); - } - - - void DicomStoreUserConnection::Store(std::string& sopClassUid, - std::string& sopInstanceUid, const void* buffer, size_t size, + bool hasMoveOriginator, const std::string& moveOriginatorAET, uint16_t moveOriginatorID) { std::unique_ptr dicom( FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size)); - if (dicom.get() == NULL || - dicom->getDataset() == NULL) + if (dicom.get() == NULL) { throw OrthancException(ErrorCode_InternalError); } - Store(sopClassUid, sopInstanceUid, *dicom->getDataset(), - moveOriginatorAET, moveOriginatorID); + Store(sopClassUid, sopInstanceUid, *dicom, hasMoveOriginator, moveOriginatorAET, moveOriginatorID); + } + + + void DicomStoreUserConnection::LookupTranscoding(std::set& acceptedSyntaxes, + const std::string& sopClassUid, + DicomTransferSyntax sourceSyntax) + { + acceptedSyntaxes.clear(); + + // Make sure a negotiation has already occurred for this transfer + // syntax. We don't use the return code: Transcoding is possible + // even if the "sourceSyntax" is not supported. + uint8_t presID; + NegotiatePresentationContext(presID, sopClassUid, sourceSyntax); + + std::map contexts; + if (association_->LookupAcceptedPresentationContext(contexts, sopClassUid)) + { + for (std::map::const_iterator + it = contexts.begin(); it != contexts.end(); ++it) + { + acceptedSyntaxes.insert(it->first); + } + } + } + + + void DicomStoreUserConnection::Transcode(std::string& sopClassUid /* out */, + std::string& sopInstanceUid /* out */, + IDicomTranscoder& transcoder, + const void* buffer, + size_t size, + bool hasMoveOriginator, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID) + { + std::unique_ptr dicom(FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size)); + if (dicom.get() == NULL || + dicom->getDataset() == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + DicomTransferSyntax inputSyntax; + LookupParameters(sopClassUid, sopInstanceUid, inputSyntax, *dicom); + + std::set accepted; + LookupTranscoding(accepted, sopClassUid, inputSyntax); + + if (accepted.find(inputSyntax) != accepted.end()) + { + // No need for transcoding + Store(sopClassUid, sopInstanceUid, *dicom, + hasMoveOriginator, moveOriginatorAET, moveOriginatorID); + } + else + { + // Transcoding is needed + std::set uncompressedSyntaxes; + + if (accepted.find(DicomTransferSyntax_LittleEndianImplicit) != accepted.end()) + { + uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit); + } + + if (accepted.find(DicomTransferSyntax_LittleEndianExplicit) != accepted.end()) + { + uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit); + } + + if (accepted.find(DicomTransferSyntax_BigEndianExplicit) != accepted.end()) + { + uncompressedSyntaxes.insert(DicomTransferSyntax_BigEndianExplicit); + } + + std::unique_ptr transcoded; + + bool hasSopInstanceUidChanged; + + if (transcoder.HasInplaceTranscode()) + { + if (transcoder.InplaceTranscode(hasSopInstanceUidChanged, *dicom, uncompressedSyntaxes, false)) + { + // In-place transcoding is supported and has succeeded + transcoded.reset(dicom.release()); + } + } + else + { + 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 + // point. The "sopInstanceUid" might also have changed (if + // using lossy compression). + + if (transcoded == NULL || + transcoded->getDataset() == NULL) + { + throw OrthancException( + ErrorCode_NotImplemented, + "Cannot transcode from \"" + std::string(GetTransferSyntaxUid(inputSyntax)) + + "\" to an uncompressed syntax for modality: " + + GetParameters().GetRemoteModality().GetApplicationEntityTitle()); + } + else + { + DicomTransferSyntax transcodedSyntax; + + // Sanity check + if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transcodedSyntax, *transcoded) || + accepted.find(transcodedSyntax) == accepted.end()) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + Store(sopClassUid, sopInstanceUid, *transcoded, + hasMoveOriginator, moveOriginatorAET, moveOriginatorID); + } + } + } } } diff -r 37cf1889667a -r 210af28c4087 Core/DicomNetworking/DicomStoreUserConnection.h --- a/Core/DicomNetworking/DicomStoreUserConnection.h Thu May 07 11:31:58 2020 +0200 +++ b/Core/DicomNetworking/DicomStoreUserConnection.h Thu May 07 11:32:15 2020 +0200 @@ -33,15 +33,23 @@ #pragma once +#if !defined(ORTHANC_ENABLE_DCMTK_TRANSCODING) +# error Macro ORTHANC_ENABLE_DCMTK_TRANSCODING must be defined to use this file +#endif + #include "DicomAssociationParameters.h" +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 +# include "../DicomParsing/IDicomTranscoder.h" +#endif + #include #include #include #include // For uint8_t -class DcmDataset; +class DcmFileFormat; namespace Orthanc { @@ -64,30 +72,41 @@ **/ class DicomAssociation; // Forward declaration for PImpl design pattern - class ParsedDicomFile; class DicomStoreUserConnection : public boost::noncopyable { private: - typedef std::map > StorageClasses; + typedef std::map > RegisteredClasses; + + // "ProposedOriginalClasses" keeps track of the storage classes + // that were proposed with a single transfer syntax + typedef std::set< std::pair > ProposedOriginalClasses; DicomAssociationParameters parameters_; boost::shared_ptr association_; // "shared_ptr" is for PImpl - StorageClasses storageClasses_; + RegisteredClasses registeredClasses_; + ProposedOriginalClasses proposedOriginalClasses_; bool proposeCommonClasses_; bool proposeUncompressedSyntaxes_; bool proposeRetiredBigEndian_; - void Setup(); - // Return "false" if there is not enough room remaining in the association bool ProposeStorageClass(const std::string& sopClassUid, const std::set& syntaxes); + bool LookupPresentationContext(uint8_t& presentationContextId, + const std::string& sopClassUid, + DicomTransferSyntax transferSyntax); + + bool NegotiatePresentationContext(uint8_t& presentationContextId, + const std::string& sopClassUid, + DicomTransferSyntax transferSyntax); + + void LookupTranscoding(std::set& acceptedSyntaxes, + const std::string& sopClassUid, + DicomTransferSyntax sourceSyntax); + public: - DicomStoreUserConnection(const std::string& localAet, - const RemoteModalityParameters& remote); - DicomStoreUserConnection(const DicomAssociationParameters& params); const DicomAssociationParameters& GetParameters() const @@ -95,11 +114,6 @@ return parameters_; } - void SetTimeout(int timeout) - { - parameters_.SetTimeout(timeout); - } - void SetCommonClassesProposed(bool proposed) { proposeCommonClasses_ = proposed; @@ -133,50 +147,33 @@ void RegisterStorageClass(const std::string& sopClassUid, DicomTransferSyntax syntax); - // Should only be used if transcoding - // TODO => to private - bool LookupPresentationContext(uint8_t& presentationContextId, - const std::string& sopClassUid, - DicomTransferSyntax transferSyntax); - - // TODO => to private - bool NegotiatePresentationContext(uint8_t& presentationContextId, - const std::string& sopClassUid, - DicomTransferSyntax transferSyntax); - - // TODO => to private - void LookupParameters(std::string& sopClassUid, - std::string& sopInstanceUid, - DicomTransferSyntax& transferSyntax, - DcmDataset& dataset); - - private: void Store(std::string& sopClassUid, std::string& sopInstanceUid, - DcmDataset& dataset, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID); - - void Store(std::string& sopClassUid, - std::string& sopInstanceUid, - ParsedDicomFile& parsed, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID); - - public: - void Store(std::string& sopClassUid, - std::string& sopInstanceUid, - const void* buffer, - size_t size, + DcmFileFormat& dicom, + bool hasMoveOriginator, const std::string& moveOriginatorAET, uint16_t moveOriginatorID); void Store(std::string& sopClassUid, std::string& sopInstanceUid, const void* buffer, - size_t size) - { - Store(sopClassUid, sopInstanceUid, buffer, size, "", 0); // Not a C-Move - } + size_t size, + bool hasMoveOriginator, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID); + + void LookupParameters(std::string& sopClassUid, + std::string& sopInstanceUid, + DicomTransferSyntax& transferSyntax, + DcmFileFormat& dicom); + + void Transcode(std::string& sopClassUid /* out */, + std::string& sopInstanceUid /* out */, + IDicomTranscoder& transcoder, + const void* buffer, + size_t size, + bool hasMoveOriginator, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID); }; } diff -r 37cf1889667a -r 210af28c4087 Core/DicomNetworking/RemoteModalityParameters.cpp --- a/Core/DicomNetworking/RemoteModalityParameters.cpp Thu May 07 11:31:58 2020 +0200 +++ b/Core/DicomNetworking/RemoteModalityParameters.cpp Thu May 07 11:32:15 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 37cf1889667a -r 210af28c4087 Core/DicomNetworking/RemoteModalityParameters.h --- a/Core/DicomNetworking/RemoteModalityParameters.h Thu May 07 11:31:58 2020 +0200 +++ b/Core/DicomNetworking/RemoteModalityParameters.h Thu May 07 11:32:15 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 37cf1889667a -r 210af28c4087 Core/DicomNetworking/TimeoutDicomConnectionManager.cpp --- a/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp Thu May 07 11:31:58 2020 +0200 +++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp Thu May 07 11:32:15 2020 +0200 @@ -103,7 +103,7 @@ if (connection_.get() != NULL) { LOG(INFO) << "Closing inactive DICOM association with modality: " - << connection_->GetParameters().GetRemoteApplicationEntityTitle(); + << connection_->GetParameters().GetRemoteModality().GetApplicationEntityTitle(); connection_.reset(NULL); } diff -r 37cf1889667a -r 210af28c4087 Core/DicomParsing/DcmtkTranscoder.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/DcmtkTranscoder.cpp Thu May 07 11:32:15 2020 +0200 @@ -0,0 +1,328 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 . + **/ + + +#include "../PrecompiledHeaders.h" +#include "DcmtkTranscoder.h" + + +#if !defined(ORTHANC_ENABLE_DCMTK_JPEG) +# error Macro ORTHANC_ENABLE_DCMTK_JPEG must be defined +#endif + +#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS) +# error Macro ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS must be defined +#endif + + +#include "FromDcmtkBridge.h" +#include "../OrthancException.h" + +#include +#include // for DJ_RPLossy +#include // for DJ_RPLossless +#include // for DJLSRepresentationParameter + + +namespace Orthanc +{ + static uint16_t GetBitsStored(DcmDataset& dataset) + { + uint16_t bitsStored; + if (dataset.findAndGetUint16(DCM_BitsStored, bitsStored).good()) + { + return bitsStored; + } + else + { + throw OrthancException(ErrorCode_BadFileFormat, + "Missing \"Bits Stored\" tag in DICOM instance"); + } + } + + + static std::string GetSopInstanceUid(DcmDataset& dataset) + { + const char* v = NULL; + + if (dataset.findAndGetString(DCM_SOPInstanceUID, v).good() && + v != NULL) + { + return std::string(v); + } + else + { + throw OrthancException(ErrorCode_BadFileFormat, "File without SOP instance UID"); + } + } + + + static void CheckSopInstanceUid(DcmFileFormat& dicom, + const std::string& sopInstanceUid, + bool mustEqual) + { + if (dicom.getDataset() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + bool ok; + + if (mustEqual) + { + ok = (GetSopInstanceUid(*dicom.getDataset()) == sopInstanceUid); + } + else + { + ok = (GetSopInstanceUid(*dicom.getDataset()) != sopInstanceUid); + } + + if (!ok) + { + throw OrthancException(ErrorCode_InternalError, + mustEqual ? "The SOP instance UID has changed unexpectedly during transcoding" : + "The SOP instance UID has not changed as expected during transcoding"); + } + } + + + void DcmtkTranscoder::SetLossyQuality(unsigned int quality) + { + if (quality <= 0 || + quality > 100) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + lossyQuality_ = quality; + } + } + + + DcmFileFormat* DcmtkTranscoder::TranscodeToParsed(bool& hasSopInstanceUidChanged /* out */, + const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) + { + std::unique_ptr dicom(FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size)); + + if (dicom.get() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + if (InplaceTranscode(hasSopInstanceUidChanged, *dicom, allowedSyntaxes, allowNewSopInstanceUid)) + { + return dicom.release(); + } + else + { + return NULL; + } + } + + + bool DcmtkTranscoder::InplaceTranscode(bool& hasSopInstanceUidChanged /* out */, + DcmFileFormat& dicom, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) + { + if (dicom.getDataset() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + hasSopInstanceUidChanged = false; + + DicomTransferSyntax syntax; + if (!FromDcmtkBridge::LookupOrthancTransferSyntax(syntax, dicom)) + { + throw OrthancException(ErrorCode_BadFileFormat, + "Cannot determine the transfer syntax"); + } + + const uint16_t bitsStored = GetBitsStored(*dicom.getDataset()); + std::string sourceSopInstanceUid = GetSopInstanceUid(*dicom.getDataset()); + + if (allowedSyntaxes.find(syntax) != allowedSyntaxes.end()) + { + // No transcoding is needed + return true; + } + + if (allowedSyntaxes.find(DicomTransferSyntax_LittleEndianImplicit) != allowedSyntaxes.end() && + FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_LittleEndianImplicit, NULL)) + { + CheckSopInstanceUid(dicom, sourceSopInstanceUid, true); + return true; + } + + if (allowedSyntaxes.find(DicomTransferSyntax_LittleEndianExplicit) != allowedSyntaxes.end() && + FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_LittleEndianExplicit, NULL)) + { + CheckSopInstanceUid(dicom, sourceSopInstanceUid, true); + return true; + } + + if (allowedSyntaxes.find(DicomTransferSyntax_BigEndianExplicit) != allowedSyntaxes.end() && + FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_BigEndianExplicit, NULL)) + { + CheckSopInstanceUid(dicom, sourceSopInstanceUid, true); + return true; + } + + if (allowedSyntaxes.find(DicomTransferSyntax_DeflatedLittleEndianExplicit) != allowedSyntaxes.end() && + FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_DeflatedLittleEndianExplicit, NULL)) + { + CheckSopInstanceUid(dicom, sourceSopInstanceUid, true); + return true; + } + +#if ORTHANC_ENABLE_DCMTK_JPEG == 1 + if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess1) != allowedSyntaxes.end() && + allowNewSopInstanceUid && + bitsStored == 8) + { + // Check out "dcmjpeg/apps/dcmcjpeg.cc" + DJ_RPLossy parameters(lossyQuality_); + + if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess1, ¶meters)) + { + CheckSopInstanceUid(dicom, sourceSopInstanceUid, false); + hasSopInstanceUidChanged = true; + return true; + } + } +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG == 1 + if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess2_4) != allowedSyntaxes.end() && + allowNewSopInstanceUid && + bitsStored <= 12) + { + // Check out "dcmjpeg/apps/dcmcjpeg.cc" + DJ_RPLossy parameters(lossyQuality_); + if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess2_4, ¶meters)) + { + CheckSopInstanceUid(dicom, sourceSopInstanceUid, false); + hasSopInstanceUidChanged = true; + return true; + } + } +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG == 1 + if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess14SV1) != allowedSyntaxes.end()) + { + // Check out "dcmjpeg/apps/dcmcjpeg.cc" + DJ_RPLossless parameters(6 /* opt_selection_value */, + 0 /* opt_point_transform */); + if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess14SV1, ¶meters)) + { + CheckSopInstanceUid(dicom, sourceSopInstanceUid, true); + return true; + } + } +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 + if (allowedSyntaxes.find(DicomTransferSyntax_JPEGLSLossless) != allowedSyntaxes.end()) + { + // Check out "dcmjpls/apps/dcmcjpls.cc" + DJLSRepresentationParameter parameters(2 /* opt_nearlossless_deviation */, + OFTrue /* opt_useLosslessProcess */); + + /** + * WARNING: This call results in a segmentation fault if using + * the DCMTK package 3.6.2 from Ubuntu 18.04. + **/ + if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGLSLossless, ¶meters)) + { + CheckSopInstanceUid(dicom, sourceSopInstanceUid, true); + return true; + } + } +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 + if (allowNewSopInstanceUid && + allowedSyntaxes.find(DicomTransferSyntax_JPEGLSLossy) != allowedSyntaxes.end()) + { + // Check out "dcmjpls/apps/dcmcjpls.cc" + DJLSRepresentationParameter parameters(2 /* opt_nearlossless_deviation */, + OFFalse /* opt_useLosslessProcess */); + + /** + * WARNING: This call results in a segmentation fault if using + * the DCMTK package 3.6.2 from Ubuntu 18.04. + **/ + if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGLSLossy, ¶meters)) + { + CheckSopInstanceUid(dicom, sourceSopInstanceUid, false); + hasSopInstanceUidChanged = true; + return true; + } + } +#endif + + return false; + } + + + 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(hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid)); + + if (transcoded.get() == NULL) + { + return false; + } + else + { + if (transcoded->getDataset() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + FromDcmtkBridge::SaveToMemoryBuffer(target, *transcoded->getDataset()); + return true; + } + } +} diff -r 37cf1889667a -r 210af28c4087 Core/DicomParsing/DcmtkTranscoder.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/DcmtkTranscoder.h Thu May 07 11:32:15 2020 +0200 @@ -0,0 +1,89 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 . + **/ + + +#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 +# error Transcoding is disabled, cannot compile this file +#endif + +#include "IDicomTranscoder.h" + +namespace Orthanc +{ + class DcmtkTranscoder : public IDicomTranscoder + { + private: + unsigned int lossyQuality_; + + public: + DcmtkTranscoder() : + lossyQuality_(90) + { + } + + void SetLossyQuality(unsigned int quality); + + unsigned int GetLossyQuality() const + { + return lossyQuality_; + } + + virtual DcmFileFormat* TranscodeToParsed(bool& hasSopInstanceUidChanged /* out */, + const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + + virtual bool HasInplaceTranscode() const + { + return true; + } + + 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, + bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + }; +} diff -r 37cf1889667a -r 210af28c4087 Core/DicomParsing/FromDcmtkBridge.cpp --- a/Core/DicomParsing/FromDcmtkBridge.cpp Thu May 07 11:31:58 2020 +0200 +++ b/Core/DicomParsing/FromDcmtkBridge.cpp Thu May 07 11:32:15 2020 +0200 @@ -1206,7 +1206,7 @@ } - + static bool SaveToMemoryBufferInternal(std::string& buffer, DcmFileFormat& dicom, E_TransferSyntax xfer) @@ -1269,7 +1269,7 @@ * dataset into memory. We now keep the original transfer syntax * (if available). **/ - E_TransferSyntax xfer = dataSet.getOriginalXfer(); + E_TransferSyntax xfer = dataSet.getCurrentXfer(); if (xfer == EXS_Unknown) { // No information about the original transfer syntax: This is @@ -1286,29 +1286,7 @@ } - bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer, - DcmFileFormat& dicom) - { - E_TransferSyntax xfer = dicom.getDataset()->getOriginalXfer(); - if (xfer == EXS_Unknown) - { - throw OrthancException(ErrorCode_InternalError, - "Cannot write a DICOM instance with unknown transfer syntax"); - } - else if (!dicom.validateMetaInfo(xfer).good()) - { - throw OrthancException(ErrorCode_InternalError, - "Cannot setup the transfer syntax to write a DICOM instance"); - } - else - { - return SaveToMemoryBufferInternal(buffer, dicom, xfer); - } - } - - - bool FromDcmtkBridge::Transcode(std::string& buffer, - DcmFileFormat& dicom, + bool FromDcmtkBridge::Transcode(DcmFileFormat& dicom, DicomTransferSyntax syntax, const DcmRepresentationParameter* representation) { @@ -1319,16 +1297,33 @@ } else { + DicomTransferSyntax sourceSyntax; + bool known = LookupOrthancTransferSyntax(sourceSyntax, dicom); + if (!dicom.getDataset()->chooseRepresentation(xfer, representation).good() || !dicom.getDataset()->canWriteXfer(xfer) || !dicom.validateMetaInfo(xfer, EWM_updateMeta).good()) { return false; } - - dicom.removeInvalidGroups(); - - return SaveToMemoryBufferInternal(buffer, dicom, xfer); + else + { + dicom.removeInvalidGroups(); + + if (known) + { + LOG(INFO) << "Transcoded an image from transfer syntax " + << GetTransferSyntaxUid(sourceSyntax) << " to " + << GetTransferSyntaxUid(syntax); + } + else + { + LOG(INFO) << "Transcoded an image from unknown transfer syntax to " + << GetTransferSyntaxUid(syntax); + } + + return true; + } } } @@ -1787,7 +1782,7 @@ DcmPixelData& pixelData = dynamic_cast(*element); DcmPixelSequence* pixelSequence = NULL; if (!pixelData.getEncapsulatedRepresentation - (dataset.getOriginalXfer(), NULL, pixelSequence).good()) + (dataset.getCurrentXfer(), NULL, pixelSequence).good()) { return NULL; } @@ -2040,25 +2035,6 @@ } - bool FromDcmtkBridge::LookupTransferSyntax(std::string& result, - DcmFileFormat& dicom) - { - const char* value = NULL; - - if (dicom.getMetaInfo() != NULL && - dicom.getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() && - value != NULL) - { - result.assign(value); - return true; - } - else - { - return false; - } - } - - #if ORTHANC_ENABLE_LUA == 1 void FromDcmtkBridge::ExecuteToDicom(DicomMap& target, LuaFunctionCall& call) @@ -2655,6 +2631,33 @@ Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding); ApplyVisitorToDataset(dataset, visitor, parentTags, parentIndexes, encoding, hasCodeExtensions); } + + + + bool FromDcmtkBridge::LookupOrthancTransferSyntax(DicomTransferSyntax& target, + DcmFileFormat& dicom) + { + if (dicom.getDataset() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + DcmDataset& dataset = *dicom.getDataset(); + + E_TransferSyntax xfer = dataset.getCurrentXfer(); + if (xfer == EXS_Unknown) + { + dataset.updateOriginalXfer(); + xfer = dataset.getOriginalXfer(); + if (xfer == EXS_Unknown) + { + throw OrthancException(ErrorCode_BadFileFormat, + "Cannot determine the transfer syntax of the DICOM instance"); + } + } + + return FromDcmtkBridge::LookupOrthancTransferSyntax(target, xfer); + } } diff -r 37cf1889667a -r 210af28c4087 Core/DicomParsing/FromDcmtkBridge.h --- a/Core/DicomParsing/FromDcmtkBridge.h Thu May 07 11:31:58 2020 +0200 +++ b/Core/DicomParsing/FromDcmtkBridge.h Thu May 07 11:32:15 2020 +0200 @@ -205,11 +205,7 @@ static bool SaveToMemoryBuffer(std::string& buffer, DcmDataset& dataSet); - static bool SaveToMemoryBuffer(std::string& buffer, - DcmFileFormat& dicom); - - static bool Transcode(std::string& buffer, - DcmFileFormat& dicom, + static bool Transcode(DcmFileFormat& dicom, DicomTransferSyntax syntax, const DcmRepresentationParameter* representation); @@ -248,9 +244,6 @@ static void FromJson(DicomMap& values, const Json::Value& result); - static bool LookupTransferSyntax(std::string& result, - DcmFileFormat& dicom); - #if ORTHANC_ENABLE_LUA == 1 static void ExecuteToDicom(DicomMap& target, LuaFunctionCall& call); @@ -284,5 +277,8 @@ static bool LookupOrthancTransferSyntax(DicomTransferSyntax& target, E_TransferSyntax source); + + static bool LookupOrthancTransferSyntax(DicomTransferSyntax& target, + DcmFileFormat& dicom); }; } diff -r 37cf1889667a -r 210af28c4087 Core/DicomParsing/IDicomTranscoder.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/IDicomTranscoder.h Thu May 07 11:32:15 2020 +0200 @@ -0,0 +1,86 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 . + **/ + + +#pragma once + +#include "../Enumerations.h" + +#include +#include + +class DcmFileFormat; + +namespace Orthanc +{ + /** + * WARNING: This class might be called from several threads at + * once. Make sure to implement proper locking. + **/ + + class IDicomTranscoder : public boost::noncopyable + { + public: + virtual ~IDicomTranscoder() + { + } + + virtual bool TranscodeToBuffer(std::string& target, + bool& hasSopInstanceUidChanged /* out */, + const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) = 0; + + /** + * Transcoding flavor that creates a new parsed DICOM file. A + * "std::set<>" is used to give the possible plugin the + * possibility to do a single parsing for all the possible + * transfer syntaxes. + **/ + virtual DcmFileFormat* TranscodeToParsed(bool& hasSopInstanceUidChanged /* out */, + const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) = 0; + + virtual bool HasInplaceTranscode() const = 0; + + /** + * In-place transcoding. This method is preferred for C-STORE. + **/ + virtual bool InplaceTranscode(bool& hasSopInstanceUidChanged /* out */, + DcmFileFormat& dicom, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) = 0; + }; +} diff -r 37cf1889667a -r 210af28c4087 Core/DicomParsing/Internals/DicomImageDecoder.cpp --- a/Core/DicomParsing/Internals/DicomImageDecoder.cpp Thu May 07 11:31:58 2020 +0200 +++ b/Core/DicomParsing/Internals/DicomImageDecoder.cpp Thu May 07 11:32:15 2020 +0200 @@ -676,7 +676,7 @@ ImageAccessor* DicomImageDecoder::Decode(DcmDataset& dataset, unsigned int frame) { - E_TransferSyntax syntax = dataset.getOriginalXfer(); + E_TransferSyntax syntax = dataset.getCurrentXfer(); /** * Deal with uncompressed, raw images. diff -r 37cf1889667a -r 210af28c4087 Core/DicomParsing/MemoryBufferTranscoder.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/MemoryBufferTranscoder.cpp Thu May 07 11:32:15 2020 +0200 @@ -0,0 +1,131 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 . + **/ + + +#include "../PrecompiledHeaders.h" +#include "MemoryBufferTranscoder.h" + +#include "../OrthancException.h" +#include "FromDcmtkBridge.h" + +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; + } + + + 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 (useDcmtk_) + { + return dcmtk_.TranscodeToBuffer(target, hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid); + } + else +#endif + { + return Transcode(target, hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid); + } + } + + + DcmFileFormat* MemoryBufferTranscoder::TranscodeToParsed(bool& hasSopInstanceUidChanged, + const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) + { +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + if (useDcmtk_) + { + return dcmtk_.TranscodeToParsed(hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid); + } + else +#endif + { + std::string transcoded; + if (Transcode(transcoded, hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid)) + { + return FromDcmtkBridge::LoadFromMemoryBuffer( + transcoded.empty() ? NULL : transcoded.c_str(), transcoded.size()); + } + else + { + return NULL; + } + } + } + + + bool MemoryBufferTranscoder::InplaceTranscode(bool& hasSopInstanceUidChanged, + DcmFileFormat& dicom, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) + { +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + if (useDcmtk_) + { + return dcmtk_.InplaceTranscode(hasSopInstanceUidChanged, dicom, allowedSyntaxes, allowNewSopInstanceUid); + } + else +#endif + { + // "HasInplaceTranscode()" should have been called + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } +} diff -r 37cf1889667a -r 210af28c4087 Core/DicomParsing/MemoryBufferTranscoder.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/MemoryBufferTranscoder.h Thu May 07 11:32:15 2020 +0200 @@ -0,0 +1,101 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 . + **/ + + +#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 + +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 */, + const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + 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 TranscodeToBuffer(std::string& target, + bool& hasSopInstanceUidChanged /* out */, + const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + + 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 useDcmtk_; + } + + virtual bool InplaceTranscode(bool& hasSopInstanceUidChanged /* out */, + DcmFileFormat& dicom, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + }; +} diff -r 37cf1889667a -r 210af28c4087 Core/DicomParsing/ParsedDicomFile.cpp --- a/Core/DicomParsing/ParsedDicomFile.cpp Thu May 07 11:31:58 2020 +0200 +++ b/Core/DicomParsing/ParsedDicomFile.cpp Thu May 07 11:32:15 2020 +0200 @@ -456,7 +456,7 @@ const UriComponents& uri) { DcmItem* dicom = pimpl_->file_->getDataset(); - E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer(); + E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getCurrentXfer(); // Special case: Accessing the pixel data if (uri.size() == 1 || @@ -1564,7 +1564,7 @@ pimpl_->frameIndex_->GetRawFrame(target, frameId); - E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer(); + E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getCurrentXfer(); switch (transferSyntax) { case EXS_JPEGProcess1: @@ -1625,7 +1625,22 @@ bool ParsedDicomFile::LookupTransferSyntax(std::string& result) { - return FromDcmtkBridge::LookupTransferSyntax(result, *pimpl_->file_); + // TODO - Shouldn't "dataset.getCurrentXfer()" be used instead of + // using the meta header? + const char* value = NULL; + + assert(pimpl_->file_ != NULL); + if (pimpl_->file_->getMetaInfo() != NULL && + pimpl_->file_->getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() && + value != NULL) + { + result.assign(value); + return true; + } + else + { + return false; + } } diff -r 37cf1889667a -r 210af28c4087 Core/Enumerations.h --- a/Core/Enumerations.h Thu May 07 11:31:58 2020 +0200 +++ b/Core/Enumerations.h Thu May 07 11:32:15 2020 +0200 @@ -279,13 +279,13 @@ DicomTransferSyntax_JPEG2000Multicomponent /*!< JPEG 2000 part 2 multicomponent extensions (lossless or lossy) */, DicomTransferSyntax_JPIPReferenced /*!< JPIP Referenced */, DicomTransferSyntax_JPIPReferencedDeflate /*!< JPIP Referenced Deflate */, - DicomTransferSyntax_MPEG2MainProfileAtMainLevel /*!< MPEG2 Main Profile at Main Level */, - DicomTransferSyntax_MPEG2MainProfileAtHighLevel /*!< MPEG2 Main Profile at High Level */, - DicomTransferSyntax_MPEG4HighProfileLevel4_1 /*!< MPEG4 High Profile / Level 4.1 */, - DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1 /*!< MPEG4 BD-compatible High Profile / Level 4.1 */, - DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo /*!< MPEG4 High Profile / Level 4.2 For 2D Video */, - DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo /*!< MPEG4 High Profile / Level 4.2 For 3D Video */, - DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2 /*!< 1.2.840.10008.1.2.4.106 */, + DicomTransferSyntax_MPEG2MainProfileAtMainLevel /*!< MPEG2 Main Profile / Main Level */, + DicomTransferSyntax_MPEG2MainProfileAtHighLevel /*!< MPEG2 Main Profile / High Level */, + DicomTransferSyntax_MPEG4HighProfileLevel4_1 /*!< MPEG4 AVC/H.264 High Profile / Level 4.1 */, + DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1 /*!< MPEG4 AVC/H.264 BD-compatible High Profile / Level 4.1 */, + DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo /*!< MPEG4 AVC/H.264 High Profile / Level 4.2 For 2D Video */, + DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo /*!< MPEG4 AVC/H.264 High Profile / Level 4.2 For 3D Video */, + DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2 /*!< MPEG4 AVC/H.264 Stereo High Profile / Level 4.2 */, DicomTransferSyntax_HEVCMainProfileLevel5_1 /*!< HEVC/H.265 Main Profile / Level 5.1 */, DicomTransferSyntax_HEVCMain10ProfileLevel5_1 /*!< HEVC/H.265 Main 10 Profile / Level 5.1 */, DicomTransferSyntax_RLELossless /*!< RLE - Run Length Encoding (lossless) */, diff -r 37cf1889667a -r 210af28c4087 NEWS --- a/NEWS Thu May 07 11:31:58 2020 +0200 +++ b/NEWS Thu May 07 11:32:15 2020 +0200 @@ -12,6 +12,11 @@ - "/queries/.../answers/../retrieve": "TargetAet" not mandatory anymore (defaults to the local AET) +Maintenance +----------- + +* Upgraded dependencies for static builds (notably on Windows and LSB): + - openssl 1.1.1g Version 1.6.1 (2020-04-21) diff -r 37cf1889667a -r 210af28c4087 OrthancServer/LuaScripting.cpp --- a/OrthancServer/LuaScripting.cpp Thu May 07 11:31:58 2020 +0200 +++ b/OrthancServer/LuaScripting.cpp Thu May 07 11:32:15 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 37cf1889667a -r 210af28c4087 OrthancServer/OrthancMoveRequestHandler.cpp --- a/OrthancServer/OrthancMoveRequestHandler.cpp Thu May 07 11:31:58 2020 +0200 +++ b/OrthancServer/OrthancMoveRequestHandler.cpp Thu May 07 11:32:15 2020 +0200 @@ -113,14 +113,15 @@ if (connection_.get() == NULL) { - connection_.reset(new DicomStoreUserConnection(localAet_, remote_)); + DicomAssociationParameters params(localAet_, remote_); + connection_.reset(new DicomStoreUserConnection(params)); } std::string sopClassUid, sopInstanceUid; // Unused 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 37cf1889667a -r 210af28c4087 OrthancServer/OrthancRestApi/OrthancRestModalities.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Thu May 07 11:31:58 2020 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Thu May 07 11:32:15 2020 +0200 @@ -54,12 +54,14 @@ namespace Orthanc { static const char* const KEY_LEVEL = "Level"; - static const char* const KEY_QUERY = "Query"; + static const char* const KEY_LOCAL_AET = "LocalAet"; static const char* const KEY_NORMALIZE = "Normalize"; + static const char* const KEY_QUERY = "Query"; static const char* const KEY_RESOURCES = "Resources"; + static const char* const KEY_TARGET_AET = "TargetAet"; + static const char* const KEY_TIMEOUT = "Timeout"; static const char* const SOP_CLASS_UID = "SOPClassUID"; static const char* const SOP_INSTANCE_UID = "SOPInstanceUID"; - static RemoteModalityParameters MyGetModalityUsingSymbolicName(const std::string& name) { @@ -68,45 +70,62 @@ } + static void InjectAssociationTimeout(DicomAssociationParameters& params, + const Json::Value& body) + { + if (body.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat, "Must provide a JSON object"); + } + else if (body.isMember(KEY_TIMEOUT)) + { + // New in Orthanc 1.7.0 + params.SetTimeout(SerializationToolbox::ReadUnsignedInteger(body, KEY_TIMEOUT)); + } + } + + static DicomAssociationParameters GetAssociationParameters(RestApiPostCall& call, + const Json::Value& body) + { + const std::string& localAet = + OrthancRestApi::GetContext(call).GetDefaultLocalApplicationEntityTitle(); + const RemoteModalityParameters remote = + MyGetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + + DicomAssociationParameters params(localAet, remote); + InjectAssociationTimeout(params, body); + + return params; + } + + + static DicomAssociationParameters GetAssociationParameters(RestApiPostCall& call) + { + Json::Value body; + call.ParseJsonRequest(body); + return GetAssociationParameters(call, body); + } + + /*************************************************************************** * DICOM C-Echo SCU ***************************************************************************/ static void DicomEcho(RestApiPostCall& call) { - ServerContext& context = OrthancRestApi::GetContext(call); - - const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = - MyGetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - - Json::Value request; - call.ParseJsonRequest(request); - int timeout = Toolbox::GetJsonIntegerField(request, "Timeout", -1); - - try - { - DicomControlUserConnection connection(localAet, remote); + DicomControlUserConnection connection(GetAssociationParameters(call)); - // New in Orthanc 1.7.0 - if (timeout != -1) - { - connection.SetTimeout(timeout); - } - - 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); } @@ -198,7 +217,6 @@ static void DicomFindPatient(RestApiPostCall& call) { LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); - ServerContext& context = OrthancRestApi::GetContext(call); DicomMap fields; DicomMap::SetupFindPatientTemplate(fields); @@ -207,14 +225,10 @@ return; } - const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = - MyGetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - DicomFindAnswers answers(false); { - DicomControlUserConnection connection(localAet, remote); + DicomControlUserConnection connection(GetAssociationParameters(call)); FindPatient(answers, connection, fields); } @@ -226,7 +240,6 @@ static void DicomFindStudy(RestApiPostCall& call) { LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); - ServerContext& context = OrthancRestApi::GetContext(call); DicomMap fields; DicomMap::SetupFindStudyTemplate(fields); @@ -241,14 +254,10 @@ return; } - const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = - MyGetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - DicomFindAnswers answers(false); { - DicomControlUserConnection connection(localAet, remote); + DicomControlUserConnection connection(GetAssociationParameters(call)); FindStudy(answers, connection, fields); } @@ -260,7 +269,6 @@ static void DicomFindSeries(RestApiPostCall& call) { LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); - ServerContext& context = OrthancRestApi::GetContext(call); DicomMap fields; DicomMap::SetupFindSeriesTemplate(fields); @@ -276,14 +284,10 @@ return; } - const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = - MyGetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - DicomFindAnswers answers(false); { - DicomControlUserConnection connection(localAet, remote); + DicomControlUserConnection connection(GetAssociationParameters(call)); FindSeries(answers, connection, fields); } @@ -295,7 +299,6 @@ static void DicomFindInstance(RestApiPostCall& call) { LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); - ServerContext& context = OrthancRestApi::GetContext(call); DicomMap fields; DicomMap::SetupFindInstanceTemplate(fields); @@ -312,14 +315,10 @@ return; } - const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = - MyGetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - DicomFindAnswers answers(false); { - DicomControlUserConnection connection(localAet, remote); + DicomControlUserConnection connection(GetAssociationParameters(call)); FindInstance(answers, connection, fields); } @@ -344,7 +343,6 @@ static void DicomFind(RestApiPostCall& call) { LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); - ServerContext& context = OrthancRestApi::GetContext(call); DicomMap m; DicomMap::SetupFindPatientTemplate(m); @@ -353,11 +351,7 @@ return; } - const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = - MyGetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - - DicomControlUserConnection connection(localAet, remote); + DicomControlUserConnection connection(GetAssociationParameters(call)); DicomFindAnswers patients(false); FindPatient(patients, connection, m); @@ -620,8 +614,8 @@ Json::Value body; if (call.ParseJsonRequest(body)) { - targetAet = Toolbox::GetJsonStringField(body, "TargetAet", context.GetDefaultLocalApplicationEntityTitle()); - timeout = Toolbox::GetJsonIntegerField(body, "Timeout", -1); + targetAet = Toolbox::GetJsonStringField(body, KEY_TARGET_AET, context.GetDefaultLocalApplicationEntityTitle()); + timeout = Toolbox::GetJsonIntegerField(body, KEY_TIMEOUT, -1); } else { @@ -643,7 +637,12 @@ job->SetTargetAet(targetAet); job->SetLocalAet(query.GetHandler().GetLocalAet()); job->SetRemoteModality(query.GetHandler().GetRemoteModality()); - job->SetTimeout(timeout); + + if (timeout >= 0) + { + // New in Orthanc 1.7.0 + job->SetTimeout(static_cast(timeout)); + } LOG(WARNING) << "Driving C-Move SCU on remote modality " << query.GetHandler().GetRemoteModality().GetApplicationEntityTitle() @@ -968,13 +967,11 @@ GetInstancesToExport(request, *job, remote, call); std::string localAet = Toolbox::GetJsonStringField - (request, "LocalAet", context.GetDefaultLocalApplicationEntityTitle()); + (request, KEY_LOCAL_AET, context.GetDefaultLocalApplicationEntityTitle()); std::string moveOriginatorAET = Toolbox::GetJsonStringField (request, "MoveOriginatorAet", context.GetDefaultLocalApplicationEntityTitle()); int moveOriginatorID = Toolbox::GetJsonIntegerField (request, "MoveOriginatorID", 0 /* By default, not a C-MOVE */); - int timeout = Toolbox::GetJsonIntegerField - (request, "Timeout", -1); job->SetLocalAet(localAet); job->SetRemoteModality(MyGetModalityUsingSymbolicName(remote)); @@ -991,7 +988,10 @@ } // New in Orthanc 1.7.0 - job->SetTimeout(timeout); + if (request.isMember(KEY_TIMEOUT)) + { + job->SetTimeout(SerializationToolbox::ReadUnsignedInteger(request, KEY_TIMEOUT)); + } OrthancRestApi::GetApi(call).SubmitCommandsJob (call, job.release(), true /* synchronous by default */, request); @@ -1000,17 +1000,12 @@ static void DicomStoreStraight(RestApiPostCall& call) { - ServerContext& context = OrthancRestApi::GetContext(call); - - const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = - MyGetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - - DicomStoreUserConnection connection(localAet, remote); + Json::Value body = Json::objectValue; // No body + 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; @@ -1044,22 +1039,18 @@ ResourceType level = StringToResourceType(request[KEY_LEVEL].asCString()); std::string localAet = Toolbox::GetJsonStringField - (request, "LocalAet", context.GetDefaultLocalApplicationEntityTitle()); + (request, KEY_LOCAL_AET, context.GetDefaultLocalApplicationEntityTitle()); std::string targetAet = Toolbox::GetJsonStringField - (request, "TargetAet", context.GetDefaultLocalApplicationEntityTitle()); - int timeout = Toolbox::GetJsonIntegerField - (request, "Timeout", -1); + (request, KEY_TARGET_AET, context.GetDefaultLocalApplicationEntityTitle()); const RemoteModalityParameters source = MyGetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - DicomControlUserConnection connection(localAet, source); + DicomAssociationParameters params(localAet, source); + InjectAssociationTimeout(params, request); - if (timeout > -1) - { - connection.SetTimeout(timeout); - } - + DicomControlUserConnection connection(params); + for (Json::Value::ArrayIndex i = 0; i < request[KEY_RESOURCES].size(); i++) { DicomMap resource; @@ -1325,15 +1316,9 @@ static void DicomFindWorklist(RestApiPostCall& call) { - ServerContext& context = OrthancRestApi::GetContext(call); - Json::Value json; if (call.ParseJsonRequest(json)) { - const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - const RemoteModalityParameters remote = - MyGetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - std::unique_ptr query (ParsedDicomFile::CreateFromJson(json, static_cast(0), "" /* no private creator */)); @@ -1341,7 +1326,7 @@ DicomFindAnswers answers(true); { - DicomControlUserConnection connection(localAet, remote); + DicomControlUserConnection connection(GetAssociationParameters(call, json)); connection.FindWorklist(answers, *query); } diff -r 37cf1889667a -r 210af28c4087 OrthancServer/QueryRetrieveHandler.cpp --- a/OrthancServer/QueryRetrieveHandler.cpp Thu May 07 11:31:58 2020 +0200 +++ b/OrthancServer/QueryRetrieveHandler.cpp Thu May 07 11:32:15 2020 +0200 @@ -82,7 +82,8 @@ FixQueryLua(fixed, context_, modality_.GetApplicationEntityTitle()); { - DicomControlUserConnection connection(localAet_, modality_); + DicomAssociationParameters params(localAet_, modality_); + DicomControlUserConnection connection(params); connection.Find(answers_, level_, fixed, findNormalized_); } diff -r 37cf1889667a -r 210af28c4087 OrthancServer/ServerContext.cpp --- a/OrthancServer/ServerContext.cpp Thu May 07 11:31:58 2020 +0200 +++ b/OrthancServer/ServerContext.cpp Thu May 07 11:32:15 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 37cf1889667a -r 210af28c4087 OrthancServer/ServerContext.h --- a/OrthancServer/ServerContext.h Thu May 07 11:31:58 2020 +0200 +++ b/OrthancServer/ServerContext.h Thu May 07 11:32:15 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 37cf1889667a -r 210af28c4087 OrthancServer/ServerJobs/DicomModalityStoreJob.cpp --- a/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp Thu May 07 11:31:58 2020 +0200 +++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp Thu May 07 11:32:15 2020 +0200 @@ -48,12 +48,7 @@ { if (connection_.get() == NULL) { - connection_.reset(new DicomStoreUserConnection(localAet_, remote_)); - - if (timeout_ > -1) - { - connection_->SetTimeout(timeout_); - } + connection_.reset(new DicomStoreUserConnection(parameters_)); } } @@ -64,7 +59,7 @@ OpenConnection(); LOG(INFO) << "Sending instance " << instance << " to modality \"" - << remote_.GetApplicationEntityTitle() << "\""; + << parameters_.GetRemoteModality().GetApplicationEntityTitle() << "\""; std::string dicom; @@ -79,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_) { @@ -108,7 +93,7 @@ assert(IsStarted()); connection_.reset(NULL); - const std::string& remoteAet = remote_.GetApplicationEntityTitle(); + const std::string& remoteAet = parameters_.GetRemoteModality().GetApplicationEntityTitle(); LOG(INFO) << "Sending storage commitment request to modality: " << remoteAet; @@ -120,8 +105,7 @@ std::vector a(sopClassUids_.begin(), sopClassUids_.end()); std::vector b(sopInstanceUids_.begin(), sopInstanceUids_.end()); - DicomAssociationParameters parameters(localAet_, remote_); - DicomAssociation::RequestStorageCommitment(parameters, transactionUid_, a, b); + DicomAssociation::RequestStorageCommitment(parameters_, transactionUid_, a, b); } } @@ -139,7 +123,6 @@ DicomModalityStoreJob::DicomModalityStoreJob(ServerContext& context) : context_(context), - localAet_("ORTHANC"), moveOriginatorId_(0), // By default, not a C-MOVE storageCommitment_(false) // By default, no storage commitment { @@ -155,7 +138,7 @@ } else { - localAet_ = aet; + parameters_.SetLocalApplicationEntityTitle(aet); } } @@ -168,11 +151,24 @@ } else { - remote_ = remote; + parameters_.SetRemoteModality(remote); } } + void DicomModalityStoreJob::SetTimeout(uint32_t seconds) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + parameters_.SetTimeout(seconds); + } + } + + const std::string& DicomModalityStoreJob::GetMoveOriginatorAet() const { if (HasMoveOriginator()) @@ -260,8 +256,8 @@ { SetOfInstancesJob::GetPublicContent(value); - value["LocalAet"] = localAet_; - value["RemoteAet"] = remote_.GetApplicationEntityTitle(); + value["LocalAet"] = parameters_.GetLocalApplicationEntityTitle(); + value["RemoteAet"] = parameters_.GetRemoteModality().GetApplicationEntityTitle(); if (HasMoveOriginator()) { @@ -276,12 +272,9 @@ } - static const char* LOCAL_AET = "LocalAet"; - static const char* REMOTE = "Remote"; static const char* MOVE_ORIGINATOR_AET = "MoveOriginatorAet"; static const char* MOVE_ORIGINATOR_ID = "MoveOriginatorId"; static const char* STORAGE_COMMITMENT = "StorageCommitment"; - static const char* TIMEOUT = "Timeout"; DicomModalityStoreJob::DicomModalityStoreJob(ServerContext& context, @@ -289,15 +282,12 @@ SetOfInstancesJob(serialized), context_(context) { - localAet_ = SerializationToolbox::ReadString(serialized, LOCAL_AET); - remote_ = RemoteModalityParameters(serialized[REMOTE]); moveOriginatorAet_ = SerializationToolbox::ReadString(serialized, MOVE_ORIGINATOR_AET); moveOriginatorId_ = static_cast (SerializationToolbox::ReadUnsignedInteger(serialized, MOVE_ORIGINATOR_ID)); EnableStorageCommitment(SerializationToolbox::ReadBoolean(serialized, STORAGE_COMMITMENT)); - // New in Orthanc in 1.7.0 - timeout_ = SerializationToolbox::ReadInteger(serialized, TIMEOUT, -1); + parameters_ = DicomAssociationParameters::UnserializeJob(serialized); } @@ -309,12 +299,10 @@ } else { - target[LOCAL_AET] = localAet_; - remote_.Serialize(target[REMOTE], true /* force advanced format */); + parameters_.SerializeJob(target); target[MOVE_ORIGINATOR_AET] = moveOriginatorAet_; target[MOVE_ORIGINATOR_ID] = moveOriginatorId_; target[STORAGE_COMMITMENT] = storageCommitment_; - target[TIMEOUT] = timeout_; return true; } } diff -r 37cf1889667a -r 210af28c4087 OrthancServer/ServerJobs/DicomModalityStoreJob.h --- a/OrthancServer/ServerJobs/DicomModalityStoreJob.h Thu May 07 11:31:58 2020 +0200 +++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.h Thu May 07 11:32:15 2020 +0200 @@ -47,9 +47,7 @@ { private: ServerContext& context_; - std::string localAet_; - RemoteModalityParameters remote_; - int timeout_; + DicomAssociationParameters parameters_; std::string moveOriginatorAet_; uint16_t moveOriginatorId_; std::unique_ptr connection_; @@ -75,29 +73,16 @@ DicomModalityStoreJob(ServerContext& context, const Json::Value& serialized); - const std::string& GetLocalAet() const + const DicomAssociationParameters& GetParameters() const { - return localAet_; + return parameters_; } void SetLocalAet(const std::string& aet); - const RemoteModalityParameters& GetRemoteModality() const - { - return remote_; - } - void SetRemoteModality(const RemoteModalityParameters& remote); - void SetTimeout(int timeout) - { - timeout_ = timeout; - } - - int GetTimeout() const - { - return timeout_; - } + void SetTimeout(uint32_t seconds); bool HasMoveOriginator() const { @@ -125,5 +110,10 @@ virtual void Reset() ORTHANC_OVERRIDE; void EnableStorageCommitment(bool enabled); + + bool HasStorageCommitment() const + { + return storageCommitment_; + } }; } diff -r 37cf1889667a -r 210af28c4087 OrthancServer/ServerJobs/DicomMoveScuJob.cpp --- a/OrthancServer/ServerJobs/DicomMoveScuJob.cpp Thu May 07 11:31:58 2020 +0200 +++ b/OrthancServer/ServerJobs/DicomMoveScuJob.cpp Thu May 07 11:32:15 2020 +0200 @@ -97,14 +97,9 @@ { if (connection_.get() == NULL) { - connection_.reset(new DicomControlUserConnection(localAet_, remote_)); + connection_.reset(new DicomControlUserConnection(parameters_)); } - if (timeout_ > -1) - { - connection_->SetTimeout(timeout_); - } - connection_->Move(targetAet_, findAnswer); } @@ -158,7 +153,7 @@ } else { - localAet_ = aet; + parameters_.SetLocalApplicationEntityTitle(aet); } } @@ -184,7 +179,20 @@ } else { - remote_ = remote; + parameters_.SetRemoteModality(remote); + } + } + + + void DicomMoveScuJob::SetTimeout(uint32_t seconds) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + parameters_.SetTimeout(seconds); } } @@ -198,9 +206,9 @@ void DicomMoveScuJob::GetPublicContent(Json::Value& value) { SetOfCommandsJob::GetPublicContent(value); - - value["LocalAet"] = localAet_; - value["RemoteAet"] = remote_.GetApplicationEntityTitle(); + + value["LocalAet"] = parameters_.GetLocalApplicationEntityTitle(); + value["RemoteAet"] = parameters_.GetRemoteModality().GetApplicationEntityTitle(); value["Query"] = query_; } @@ -211,18 +219,14 @@ context_(context), query_(Json::arrayValue) { - localAet_ = SerializationToolbox::ReadString(serialized, LOCAL_AET); + parameters_ = DicomAssociationParameters::UnserializeJob(serialized); targetAet_ = SerializationToolbox::ReadString(serialized, TARGET_AET); - remote_ = RemoteModalityParameters(serialized[REMOTE]); if (serialized.isMember(QUERY) && serialized[QUERY].type() == Json::arrayValue) { query_ = serialized[QUERY]; } - - // New in Orthanc in 1.7.0 - timeout_ = SerializationToolbox::ReadInteger(serialized, TIMEOUT, -1); } @@ -234,11 +238,9 @@ } else { - target[LOCAL_AET] = localAet_; + parameters_.SerializeJob(target); target[TARGET_AET] = targetAet_; target[QUERY] = query_; - target[TIMEOUT] = timeout_; - remote_.Serialize(target[REMOTE], true /* force advanced format */); return true; } } diff -r 37cf1889667a -r 210af28c4087 OrthancServer/ServerJobs/DicomMoveScuJob.h --- a/OrthancServer/ServerJobs/DicomMoveScuJob.h Thu May 07 11:31:58 2020 +0200 +++ b/OrthancServer/ServerJobs/DicomMoveScuJob.h Thu May 07 11:32:15 2020 +0200 @@ -49,12 +49,10 @@ class Command; class Unserializer; - ServerContext& context_; - std::string localAet_; - std::string targetAet_; - RemoteModalityParameters remote_; - int timeout_; - Json::Value query_; + ServerContext& context_; + DicomAssociationParameters parameters_; + std::string targetAet_; + Json::Value query_; std::unique_ptr connection_; @@ -74,39 +72,25 @@ void AddFindAnswer(QueryRetrieveHandler& query, size_t i); - - const std::string& GetLocalAet() const + + const DicomAssociationParameters& GetParameters() const { - return localAet_; + return parameters_; } + + void SetLocalAet(const std::string& aet); - void SetLocalAet(const std::string& aet); + void SetRemoteModality(const RemoteModalityParameters& remote); + + void SetTimeout(uint32_t timeout); const std::string& GetTargetAet() const { return targetAet_; } - + void SetTargetAet(const std::string& aet); - const RemoteModalityParameters& GetRemoteModality() const - { - return remote_; - } - - void SetRemoteModality(const RemoteModalityParameters& remote); - - void SetTimeout(int timeout) - { - timeout_ = timeout; - } - - int GetTimeout() const - { - return timeout_; - } - - virtual void Stop(JobStopReason reason); virtual void GetJobType(std::string& target) diff -r 37cf1889667a -r 210af28c4087 OrthancServer/ServerJobs/LuaJobManager.cpp --- a/OrthancServer/ServerJobs/LuaJobManager.cpp Thu May 07 11:31:58 2020 +0200 +++ b/OrthancServer/ServerJobs/LuaJobManager.cpp Thu May 07 11:32:15 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 37cf1889667a -r 210af28c4087 OrthancServer/ServerJobs/LuaJobManager.h --- a/OrthancServer/ServerJobs/LuaJobManager.h Thu May 07 11:31:58 2020 +0200 +++ b/OrthancServer/ServerJobs/LuaJobManager.h Thu May 07 11:32:15 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 37cf1889667a -r 210af28c4087 OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp --- a/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp Thu May 07 11:31:58 2020 +0200 +++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp Thu May 07 11:32:15 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 37cf1889667a -r 210af28c4087 OrthancServer/ServerJobs/Operations/StoreScuOperation.h --- a/OrthancServer/ServerJobs/Operations/StoreScuOperation.h Thu May 07 11:31:58 2020 +0200 +++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.h Thu May 07 11:32:15 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 37cf1889667a -r 210af28c4087 OrthancServer/ServerJobs/OrthancJobUnserializer.cpp --- a/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp Thu May 07 11:31:58 2020 +0200 +++ b/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp Thu May 07 11:32:15 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 37cf1889667a -r 210af28c4087 Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Thu May 07 11:31:58 2020 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Thu May 07 11:32:15 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 37cf1889667a -r 210af28c4087 Plugins/Engine/OrthancPlugins.h --- a/Plugins/Engine/OrthancPlugins.h Thu May 07 11:31:58 2020 +0200 +++ b/Plugins/Engine/OrthancPlugins.h Thu May 07 11:32:15 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 37cf1889667a -r 210af28c4087 Resources/CMake/OpenSslConfigurationStatic-1.1.1.cmake --- a/Resources/CMake/OpenSslConfigurationStatic-1.1.1.cmake Thu May 07 11:31:58 2020 +0200 +++ b/Resources/CMake/OpenSslConfigurationStatic-1.1.1.cmake Thu May 07 11:32:15 2020 +0200 @@ -1,6 +1,6 @@ -SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.1.1f) -SET(OPENSSL_URL "http://orthanc.osimis.io/ThirdPartyDownloads/openssl-1.1.1f.tar.gz") -SET(OPENSSL_MD5 "3f486f2f4435ef14b81814dbbc7b48bb") +SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.1.1g) +SET(OPENSSL_URL "http://orthanc.osimis.io/ThirdPartyDownloads/openssl-1.1.1g.tar.gz") +SET(OPENSSL_MD5 "76766e98997660138cdaf13a187bd234") if (IS_DIRECTORY "${OPENSSL_SOURCES_DIR}") set(FirstRun OFF) @@ -27,7 +27,7 @@ # Apply the patches execute_process( COMMAND ${PATCH_EXECUTABLE} -p0 -N -i - ${ORTHANC_ROOT}/Resources/Patches/openssl-1.1.1f.patch + ${ORTHANC_ROOT}/Resources/Patches/openssl-1.1.1g.patch WORKING_DIRECTORY ${CMAKE_BINARY_DIR} RESULT_VARIABLE Failure ) diff -r 37cf1889667a -r 210af28c4087 Resources/CMake/OrthancFrameworkConfiguration.cmake --- a/Resources/CMake/OrthancFrameworkConfiguration.cmake Thu May 07 11:31:58 2020 +0200 +++ b/Resources/CMake/OrthancFrameworkConfiguration.cmake Thu May 07 11:32:15 2020 +0200 @@ -501,6 +501,10 @@ # New in Orthanc 1.6.0 if (ENABLE_DCMTK_TRANSCODING) add_definitions(-DORTHANC_ENABLE_DCMTK_TRANSCODING=1) + list(APPEND ORTHANC_DICOM_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/DicomParsing/DcmtkTranscoder.cpp + ${ORTHANC_ROOT}/Core/DicomParsing/MemoryBufferTranscoder.cpp + ) else() add_definitions(-DORTHANC_ENABLE_DCMTK_TRANSCODING=0) endif() diff -r 37cf1889667a -r 210af28c4087 Resources/Configuration.json --- a/Resources/Configuration.json Thu May 07 11:31:58 2020 +0200 +++ b/Resources/Configuration.json Thu May 07 11:32:15 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 37cf1889667a -r 210af28c4087 Resources/DicomTransferSyntaxes.json --- a/Resources/DicomTransferSyntaxes.json Thu May 07 11:31:58 2020 +0200 +++ b/Resources/DicomTransferSyntaxes.json Thu May 07 11:32:15 2020 +0200 @@ -274,7 +274,7 @@ { "UID" : "1.2.840.10008.1.2.4.100", - "Name" : "MPEG2 Main Profile at Main Level", + "Name" : "MPEG2 Main Profile / Main Level", "Value" : "MPEG2MainProfileAtMainLevel", "Retired" : false, "DCMTK" : "EXS_MPEG2MainProfileAtMainLevel" @@ -282,7 +282,7 @@ { "UID" : "1.2.840.10008.1.2.4.101", - "Name" : "MPEG2 Main Profile at High Level", + "Name" : "MPEG2 Main Profile / High Level", "Value" : "MPEG2MainProfileAtHighLevel", "Retired" : false, "DCMTK" : "EXS_MPEG2MainProfileAtHighLevel" @@ -290,7 +290,7 @@ { "UID" : "1.2.840.10008.1.2.4.102", - "Name" : "MPEG4 High Profile / Level 4.1", + "Name" : "MPEG4 AVC/H.264 High Profile / Level 4.1", "Value" : "MPEG4HighProfileLevel4_1", "Retired" : false, "DCMTK" : "EXS_MPEG4HighProfileLevel4_1", @@ -299,7 +299,7 @@ { "UID" : "1.2.840.10008.1.2.4.103", - "Name" : "MPEG4 BD-compatible High Profile / Level 4.1", + "Name" : "MPEG4 AVC/H.264 BD-compatible High Profile / Level 4.1", "Value" : "MPEG4BDcompatibleHighProfileLevel4_1", "Retired" : false, "DCMTK" : "EXS_MPEG4BDcompatibleHighProfileLevel4_1", @@ -308,7 +308,7 @@ { "UID" : "1.2.840.10008.1.2.4.104", - "Name" : "MPEG4 High Profile / Level 4.2 For 2D Video", + "Name" : "MPEG4 AVC/H.264 High Profile / Level 4.2 For 2D Video", "Value" : "MPEG4HighProfileLevel4_2_For2DVideo", "Retired" : false, "DCMTK" : "EXS_MPEG4HighProfileLevel4_2_For2DVideo", @@ -317,7 +317,7 @@ { "UID" : "1.2.840.10008.1.2.4.105", - "Name" : "MPEG4 High Profile / Level 4.2 For 3D Video", + "Name" : "MPEG4 AVC/H.264 High Profile / Level 4.2 For 3D Video", "Value" : "MPEG4HighProfileLevel4_2_For3DVideo", "Retired" : false, "DCMTK" : "EXS_MPEG4HighProfileLevel4_2_For3DVideo", @@ -326,7 +326,7 @@ { "UID" : "1.2.840.10008.1.2.4.106", - "Name" : "1.2.840.10008.1.2.4.106", + "Name" : "MPEG4 AVC/H.264 Stereo High Profile / Level 4.2", "Value" : "MPEG4StereoHighProfileLevel4_2", "Retired" : false, "DCMTK" : "EXS_MPEG4StereoHighProfileLevel4_2", diff -r 37cf1889667a -r 210af28c4087 Resources/Graveyard/TestTranscoding.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/TestTranscoding.cpp Thu May 07 11:32:15 2020 +0200 @@ -0,0 +1,967 @@ + bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer, + DcmFileFormat& dicom, + DicomTransferSyntax syntax) + { + E_TransferSyntax xfer; + if (!LookupDcmtkTransferSyntax(xfer, syntax)) + { + return false; + } + else if (!dicom.validateMetaInfo(xfer).good()) + { + throw OrthancException(ErrorCode_InternalError, + "Cannot setup the transfer syntax to write a DICOM instance"); + } + else + { + return SaveToMemoryBufferInternal(buffer, dicom, xfer); + } + } + + + bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer, + DcmFileFormat& dicom) + { + E_TransferSyntax xfer = dicom.getDataset()->getCurrentXfer(); + if (xfer == EXS_Unknown) + { + throw OrthancException(ErrorCode_InternalError, + "Cannot write a DICOM instance with unknown transfer syntax"); + } + else if (!dicom.validateMetaInfo(xfer).good()) + { + throw OrthancException(ErrorCode_InternalError, + "Cannot setup the transfer syntax to write a DICOM instance"); + } + else + { + return SaveToMemoryBufferInternal(buffer, dicom, xfer); + } + } + + + + + +#include +#include +#include + +#include "../Core/DicomParsing/Internals/DicomFrameIndex.h" + +namespace Orthanc +{ + class IParsedDicomImage : public boost::noncopyable + { + public: + virtual ~IParsedDicomImage() + { + } + + virtual DicomTransferSyntax GetTransferSyntax() = 0; + + virtual std::string GetSopClassUid() = 0; + + virtual std::string GetSopInstanceUid() = 0; + + virtual unsigned int GetFramesCount() = 0; + + // Can return NULL, for compressed transfer syntaxes + virtual ImageAccessor* GetUncompressedFrame(unsigned int frame) = 0; + + virtual void GetCompressedFrame(std::string& target, + unsigned int frame) = 0; + + virtual void WriteToMemoryBuffer(std::string& target) = 0; + }; + + + class IDicomImageReader : public boost::noncopyable + { + public: + virtual ~IDicomImageReader() + { + } + + virtual IParsedDicomImage* Read(const void* data, + size_t size) = 0; + + virtual IParsedDicomImage* Transcode(const void* data, + size_t size, + DicomTransferSyntax syntax, + bool allowNewSopInstanceUid) = 0; + }; + + + class DcmtkImageReader : public IDicomImageReader + { + private: + class Image : public IParsedDicomImage + { + private: + std::unique_ptr dicom_; + std::unique_ptr index_; + DicomTransferSyntax transferSyntax_; + std::string sopClassUid_; + std::string sopInstanceUid_; + + static std::string GetStringTag(DcmDataset& dataset, + const DcmTagKey& tag) + { + const char* value = NULL; + + if (!dataset.findAndGetString(tag, value).good() || + value == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat, + "Missing SOP class/instance UID in DICOM instance"); + } + else + { + return std::string(value); + } + } + + public: + Image(DcmFileFormat* dicom, + DicomTransferSyntax syntax) : + dicom_(dicom), + transferSyntax_(syntax) + { + if (dicom == NULL || + dicom_->getDataset() == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + DcmDataset& dataset = *dicom_->getDataset(); + index_.reset(new DicomFrameIndex(dataset)); + + sopClassUid_ = GetStringTag(dataset, DCM_SOPClassUID); + sopInstanceUid_ = GetStringTag(dataset, DCM_SOPInstanceUID); + } + + virtual DicomTransferSyntax GetTransferSyntax() ORTHANC_OVERRIDE + { + return transferSyntax_; + } + + virtual std::string GetSopClassUid() ORTHANC_OVERRIDE + { + return sopClassUid_; + } + + virtual std::string GetSopInstanceUid() ORTHANC_OVERRIDE + { + return sopInstanceUid_; + } + + virtual unsigned int GetFramesCount() ORTHANC_OVERRIDE + { + return index_->GetFramesCount(); + } + + virtual void WriteToMemoryBuffer(std::string& target) ORTHANC_OVERRIDE + { + assert(dicom_.get() != NULL); + if (!FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, transferSyntax_)) + { + throw OrthancException(ErrorCode_InternalError, + "Cannot write the DICOM instance to a memory buffer"); + } + } + + virtual ImageAccessor* GetUncompressedFrame(unsigned int frame) ORTHANC_OVERRIDE + { + assert(dicom_.get() != NULL && + dicom_->getDataset() != NULL); + return DicomImageDecoder::Decode(*dicom_->getDataset(), frame); + } + + virtual void GetCompressedFrame(std::string& target, + unsigned int frame) ORTHANC_OVERRIDE + { + assert(index_.get() != NULL); + index_->GetRawFrame(target, frame); + } + }; + + unsigned int lossyQuality_; + + static DicomTransferSyntax DetectTransferSyntax(DcmFileFormat& dicom) + { + if (dicom.getDataset() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + DcmDataset& dataset = *dicom.getDataset(); + + E_TransferSyntax xfer = dataset.getCurrentXfer(); + if (xfer == EXS_Unknown) + { + dataset.updateOriginalXfer(); + xfer = dataset.getCurrentXfer(); + if (xfer == EXS_Unknown) + { + throw OrthancException(ErrorCode_BadFileFormat, + "Cannot determine the transfer syntax of the DICOM instance"); + } + } + + DicomTransferSyntax syntax; + if (FromDcmtkBridge::LookupOrthancTransferSyntax(syntax, xfer)) + { + return syntax; + } + else + { + throw OrthancException( + ErrorCode_BadFileFormat, + "Unsupported transfer syntax: " + boost::lexical_cast(xfer)); + } + } + + + static uint16_t GetBitsStored(DcmFileFormat& dicom) + { + if (dicom.getDataset() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + uint16_t bitsStored; + if (dicom.getDataset()->findAndGetUint16(DCM_BitsStored, bitsStored).good()) + { + return bitsStored; + } + else + { + throw OrthancException(ErrorCode_BadFileFormat, + "Missing \"Bits Stored\" tag in DICOM instance"); + } + } + + + public: + DcmtkImageReader() : + lossyQuality_(90) + { + } + + void SetLossyQuality(unsigned int quality) + { + if (quality <= 0 || + quality > 100) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + lossyQuality_ = quality; + } + } + + unsigned int GetLossyQuality() const + { + return lossyQuality_; + } + + virtual IParsedDicomImage* Read(const void* data, + size_t size) + { + std::unique_ptr dicom(FromDcmtkBridge::LoadFromMemoryBuffer(data, size)); + if (dicom.get() == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + DicomTransferSyntax transferSyntax = DetectTransferSyntax(*dicom); + + return new Image(dicom.release(), transferSyntax); + } + + virtual IParsedDicomImage* Transcode(const void* data, + size_t size, + DicomTransferSyntax syntax, + bool allowNewSopInstanceUid) + { + std::unique_ptr dicom(FromDcmtkBridge::LoadFromMemoryBuffer(data, size)); + if (dicom.get() == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + const uint16_t bitsStored = GetBitsStored(*dicom); + + if (syntax == DetectTransferSyntax(*dicom)) + { + // No transcoding is needed + return new Image(dicom.release(), syntax); + } + + if (syntax == DicomTransferSyntax_LittleEndianImplicit && + FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_LittleEndianImplicit, NULL)) + { + return new Image(dicom.release(), syntax); + } + + if (syntax == DicomTransferSyntax_LittleEndianExplicit && + FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_LittleEndianExplicit, NULL)) + { + return new Image(dicom.release(), syntax); + } + + if (syntax == DicomTransferSyntax_BigEndianExplicit && + FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_BigEndianExplicit, NULL)) + { + return new Image(dicom.release(), syntax); + } + + if (syntax == DicomTransferSyntax_DeflatedLittleEndianExplicit && + FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_DeflatedLittleEndianExplicit, NULL)) + { + return new Image(dicom.release(), syntax); + } + +#if ORTHANC_ENABLE_JPEG == 1 + if (syntax == DicomTransferSyntax_JPEGProcess1 && + allowNewSopInstanceUid && + bitsStored == 8) + { + DJ_RPLossy rpLossy(lossyQuality_); + + if (FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_JPEGProcess1, &rpLossy)) + { + return new Image(dicom.release(), syntax); + } + } +#endif + +#if ORTHANC_ENABLE_JPEG == 1 + if (syntax == DicomTransferSyntax_JPEGProcess2_4 && + allowNewSopInstanceUid && + bitsStored <= 12) + { + DJ_RPLossy rpLossy(lossyQuality_); + if (FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_JPEGProcess2_4, &rpLossy)) + { + return new Image(dicom.release(), syntax); + } + } +#endif + + //LOG(INFO) << "Unable to transcode DICOM image using the built-in reader"; + return NULL; + } + }; + + + + class IDicomTranscoder1 : public boost::noncopyable + { + public: + virtual ~IDicomTranscoder1() + { + } + + virtual DcmFileFormat& GetDicom() = 0; + + virtual DicomTransferSyntax GetTransferSyntax() = 0; + + virtual std::string GetSopClassUid() = 0; + + virtual std::string GetSopInstanceUid() = 0; + + virtual unsigned int GetFramesCount() = 0; + + virtual ImageAccessor* DecodeFrame(unsigned int frame) = 0; + + virtual void GetCompressedFrame(std::string& target, + unsigned int frame) = 0; + + // NB: Transcoding can change the value of "GetSopInstanceUid()" + // and "GetTransferSyntax()" if lossy compression is applied + virtual bool Transcode(std::string& target, + DicomTransferSyntax syntax, + bool allowNewSopInstanceUid) = 0; + + virtual void WriteToMemoryBuffer(std::string& target) = 0; + }; + + + class DcmtkTranscoder2 : public IDicomTranscoder1 + { + private: + std::unique_ptr dicom_; + std::unique_ptr index_; + DicomTransferSyntax transferSyntax_; + std::string sopClassUid_; + std::string sopInstanceUid_; + uint16_t bitsStored_; + unsigned int lossyQuality_; + + static std::string GetStringTag(DcmDataset& dataset, + const DcmTagKey& tag) + { + const char* value = NULL; + + if (!dataset.findAndGetString(tag, value).good() || + value == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat, + "Missing SOP class/instance UID in DICOM instance"); + } + else + { + return std::string(value); + } + } + + void Setup(DcmFileFormat* dicom) + { + lossyQuality_ = 90; + + dicom_.reset(dicom); + + if (dicom == NULL || + dicom_->getDataset() == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + DcmDataset& dataset = *dicom_->getDataset(); + index_.reset(new DicomFrameIndex(dataset)); + + E_TransferSyntax xfer = dataset.getCurrentXfer(); + if (xfer == EXS_Unknown) + { + dataset.updateOriginalXfer(); + xfer = dataset.getCurrentXfer(); + if (xfer == EXS_Unknown) + { + throw OrthancException(ErrorCode_BadFileFormat, + "Cannot determine the transfer syntax of the DICOM instance"); + } + } + + if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transferSyntax_, xfer)) + { + throw OrthancException( + ErrorCode_BadFileFormat, + "Unsupported transfer syntax: " + boost::lexical_cast(xfer)); + } + + if (!dataset.findAndGetUint16(DCM_BitsStored, bitsStored_).good()) + { + throw OrthancException(ErrorCode_BadFileFormat, + "Missing \"Bits Stored\" tag in DICOM instance"); + } + + sopClassUid_ = GetStringTag(dataset, DCM_SOPClassUID); + sopInstanceUid_ = GetStringTag(dataset, DCM_SOPInstanceUID); + } + + public: + DcmtkTranscoder2(DcmFileFormat* dicom) // Takes ownership + { + Setup(dicom); + } + + DcmtkTranscoder2(const void* dicom, + size_t size) + { + Setup(FromDcmtkBridge::LoadFromMemoryBuffer(dicom, size)); + } + + void SetLossyQuality(unsigned int quality) + { + if (quality <= 0 || + quality > 100) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + lossyQuality_ = quality; + } + } + + unsigned int GetLossyQuality() const + { + return lossyQuality_; + } + + unsigned int GetBitsStored() const + { + return bitsStored_; + } + + virtual DcmFileFormat& GetDicom() + { + assert(dicom_ != NULL); + return *dicom_; + } + + virtual DicomTransferSyntax GetTransferSyntax() ORTHANC_OVERRIDE + { + return transferSyntax_; + } + + virtual std::string GetSopClassUid() ORTHANC_OVERRIDE + { + return sopClassUid_; + } + + virtual std::string GetSopInstanceUid() ORTHANC_OVERRIDE + { + return sopInstanceUid_; + } + + virtual unsigned int GetFramesCount() ORTHANC_OVERRIDE + { + return index_->GetFramesCount(); + } + + virtual void WriteToMemoryBuffer(std::string& target) ORTHANC_OVERRIDE + { + if (!FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_)) + { + throw OrthancException(ErrorCode_InternalError, + "Cannot write the DICOM instance to a memory buffer"); + } + } + + virtual ImageAccessor* DecodeFrame(unsigned int frame) ORTHANC_OVERRIDE + { + assert(dicom_->getDataset() != NULL); + return DicomImageDecoder::Decode(*dicom_->getDataset(), frame); + } + + virtual void GetCompressedFrame(std::string& target, + unsigned int frame) ORTHANC_OVERRIDE + { + index_->GetRawFrame(target, frame); + } + + virtual bool Transcode(std::string& target, + DicomTransferSyntax syntax, + bool allowNewSopInstanceUid) ORTHANC_OVERRIDE + { + assert(dicom_ != NULL && + dicom_->getDataset() != NULL); + + if (syntax == GetTransferSyntax()) + { + printf("NO TRANSCODING\n"); + + // No change in the transfer syntax => simply serialize the current dataset + WriteToMemoryBuffer(target); + return true; + } + + printf(">> %d\n", bitsStored_); + + if (syntax == DicomTransferSyntax_LittleEndianImplicit && + FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) && + FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax)) + { + transferSyntax_ = DicomTransferSyntax_LittleEndianImplicit; + return true; + } + + if (syntax == DicomTransferSyntax_LittleEndianExplicit && + FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) && + FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax)) + { + transferSyntax_ = DicomTransferSyntax_LittleEndianExplicit; + return true; + } + + if (syntax == DicomTransferSyntax_BigEndianExplicit && + FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) && + FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax)) + { + transferSyntax_ = DicomTransferSyntax_BigEndianExplicit; + return true; + } + + if (syntax == DicomTransferSyntax_DeflatedLittleEndianExplicit && + FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) && + FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax)) + { + transferSyntax_ = DicomTransferSyntax_DeflatedLittleEndianExplicit; + return true; + } + +#if ORTHANC_ENABLE_JPEG == 1 + if (syntax == DicomTransferSyntax_JPEGProcess1 && + allowNewSopInstanceUid && + GetBitsStored() == 8) + { + DJ_RPLossy rpLossy(lossyQuality_); + + if (FromDcmtkBridge::Transcode(*dicom_, syntax, &rpLossy) && + FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax)) + { + transferSyntax_ = DicomTransferSyntax_JPEGProcess1; + sopInstanceUid_ = GetStringTag(*dicom_->getDataset(), DCM_SOPInstanceUID); + return true; + } + } +#endif + +#if ORTHANC_ENABLE_JPEG == 1 + if (syntax == DicomTransferSyntax_JPEGProcess2_4 && + allowNewSopInstanceUid && + GetBitsStored() <= 12) + { + DJ_RPLossy rpLossy(lossyQuality_); + if (FromDcmtkBridge::Transcode(*dicom_, syntax, &rpLossy) && + FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax)) + { + transferSyntax_ = DicomTransferSyntax_JPEGProcess2_4; + sopInstanceUid_ = GetStringTag(*dicom_->getDataset(), DCM_SOPInstanceUID); + return true; + } + } +#endif + + return false; + } + }; +} + + + + +#include + + +static void TestFile(const std::string& path) +{ + static unsigned int count = 0; + count++; + + + printf("** %s\n", path.c_str()); + + std::string s; + SystemToolbox::ReadFile(s, path); + + Orthanc::DcmtkTranscoder2 transcoder(s.c_str(), s.size()); + + /*if (transcoder.GetBitsStored() != 8) // TODO + return; */ + + { + char buf[1024]; + sprintf(buf, "/tmp/source-%06d.dcm", count); + printf(">> %s\n", buf); + Orthanc::SystemToolbox::WriteFile(s, buf); + } + + printf("[%s] [%s] [%s] %d %d\n", GetTransferSyntaxUid(transcoder.GetTransferSyntax()), + transcoder.GetSopClassUid().c_str(), transcoder.GetSopInstanceUid().c_str(), + transcoder.GetFramesCount(), transcoder.GetTransferSyntax()); + + for (size_t i = 0; i < transcoder.GetFramesCount(); i++) + { + std::string f; + transcoder.GetCompressedFrame(f, i); + + if (i == 0) + { + char buf[1024]; + sprintf(buf, "/tmp/frame-%06d.raw", count); + printf(">> %s\n", buf); + Orthanc::SystemToolbox::WriteFile(f, buf); + } + } + + { + std::string t; + transcoder.WriteToMemoryBuffer(t); + + Orthanc::DcmtkTranscoder2 transcoder2(t.c_str(), t.size()); + printf(">> %d %d ; %lu bytes\n", transcoder.GetTransferSyntax(), transcoder2.GetTransferSyntax(), t.size()); + } + + { + std::string a = transcoder.GetSopInstanceUid(); + DicomTransferSyntax b = transcoder.GetTransferSyntax(); + + DicomTransferSyntax syntax = DicomTransferSyntax_JPEGProcess2_4; + //DicomTransferSyntax syntax = DicomTransferSyntax_LittleEndianExplicit; + + std::string t; + bool ok = transcoder.Transcode(t, syntax, true); + printf("Transcoding: %d\n", ok); + + if (ok) + { + printf("[%s] => [%s]\n", a.c_str(), transcoder.GetSopInstanceUid().c_str()); + printf("[%s] => [%s]\n", GetTransferSyntaxUid(b), + GetTransferSyntaxUid(transcoder.GetTransferSyntax())); + + { + char buf[1024]; + sprintf(buf, "/tmp/transcoded-%06d.dcm", count); + printf(">> %s\n", buf); + Orthanc::SystemToolbox::WriteFile(t, buf); + } + + Orthanc::DcmtkTranscoder2 transcoder2(t.c_str(), t.size()); + printf(" => transcoded transfer syntax %d ; %lu bytes\n", transcoder2.GetTransferSyntax(), t.size()); + } + } + + printf("\n"); +} + +TEST(Toto, DISABLED_Transcode) +{ + //OFLog::configure(OFLogger::DEBUG_LOG_LEVEL); + + if (1) + { + const char* const PATH = "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes"; + + for (boost::filesystem::directory_iterator it(PATH); + it != boost::filesystem::directory_iterator(); ++it) + { + if (boost::filesystem::is_regular_file(it->status())) + { + TestFile(it->path().string()); + } + } + } + + if (0) + { + TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Multiframe.dcm"); + TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Issue44/Monochrome1-Jpeg.dcm"); + } + + if (0) + { + TestFile("/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.1.dcm"); + } +} + + +TEST(Toto, DISABLED_Transcode2) +{ + for (int i = 0; i <= DicomTransferSyntax_XML; i++) + { + DicomTransferSyntax a = (DicomTransferSyntax) i; + + std::string path = ("/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/" + + std::string(GetTransferSyntaxUid(a)) + ".dcm"); + if (Orthanc::SystemToolbox::IsRegularFile(path)) + { + printf("\n======= %s\n", GetTransferSyntaxUid(a)); + + std::string source; + Orthanc::SystemToolbox::ReadFile(source, path); + + DcmtkImageReader reader; + + { + std::unique_ptr image( + reader.Read(source.c_str(), source.size())); + ASSERT_TRUE(image.get() != NULL); + ASSERT_EQ(a, image->GetTransferSyntax()); + + std::string target; + image->WriteToMemoryBuffer(target); + } + + for (int j = 0; j <= DicomTransferSyntax_XML; j++) + { + DicomTransferSyntax b = (DicomTransferSyntax) j; + //if (a == b) continue; + + std::unique_ptr image( + reader.Transcode(source.c_str(), source.size(), b, true)); + if (image.get() != NULL) + { + printf("[%s] -> [%s]\n", GetTransferSyntaxUid(a), GetTransferSyntaxUid(b)); + + std::string target; + image->WriteToMemoryBuffer(target); + + char buf[1024]; + sprintf(buf, "/tmp/%s-%s.dcm", GetTransferSyntaxUid(a), GetTransferSyntaxUid(b)); + + SystemToolbox::WriteFile(target, buf); + } + else if (a != DicomTransferSyntax_JPEG2000 && + a != DicomTransferSyntax_JPEG2000LosslessOnly) + { + ASSERT_TRUE(b != DicomTransferSyntax_LittleEndianImplicit && + b != DicomTransferSyntax_LittleEndianExplicit && + b != DicomTransferSyntax_BigEndianExplicit && + b != DicomTransferSyntax_DeflatedLittleEndianExplicit); + } + } + } + } +} + + +#include "../Core/DicomNetworking/DicomAssociation.h" +#include "../Core/DicomNetworking/DicomControlUserConnection.h" +#include "../Core/DicomNetworking/DicomStoreUserConnection.h" + +TEST(Toto, DISABLED_DicomAssociation) +{ + DicomAssociationParameters params; + params.SetLocalApplicationEntityTitle("ORTHANC"); + params.SetRemoteApplicationEntityTitle("PACS"); + params.SetRemotePort(2001); + +#if 0 + DicomAssociation assoc; + assoc.ProposeGenericPresentationContext(UID_StorageCommitmentPushModelSOPClass); + assoc.ProposeGenericPresentationContext(UID_VerificationSOPClass); + assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage, + DicomTransferSyntax_JPEGProcess1); + assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage, + DicomTransferSyntax_JPEGProcess2_4); + assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage, + DicomTransferSyntax_JPEG2000); + + assoc.Open(params); + + int presID = ASC_findAcceptedPresentationContextID(&assoc.GetDcmtkAssociation(), UID_ComputedRadiographyImageStorage); + printf(">> %d\n", presID); + + std::map pc; + printf(">> %d\n", assoc.LookupAcceptedPresentationContext(pc, UID_ComputedRadiographyImageStorage)); + + for (std::map::const_iterator + it = pc.begin(); it != pc.end(); ++it) + { + printf("[%s] => %d\n", GetTransferSyntaxUid(it->first), it->second); + } +#else + { + DicomControlUserConnection assoc(params); + + try + { + printf(">> %d\n", assoc.Echo()); + } + catch (OrthancException&) + { + } + } + + params.SetRemoteApplicationEntityTitle("PACS"); + params.SetRemotePort(2000); + + { + DicomControlUserConnection assoc(params); + printf(">> %d\n", assoc.Echo()); + } + +#endif +} + +static void TestTranscode(DicomStoreUserConnection& scu, + const std::string& sopClassUid, + DicomTransferSyntax transferSyntax) +{ + std::set accepted; + + scu.LookupTranscoding(accepted, sopClassUid, transferSyntax); + if (accepted.empty()) + { + throw OrthancException(ErrorCode_NetworkProtocol, + "The SOP class is not supported by the remote modality"); + } + + { + unsigned int count = 0; + for (std::set::const_iterator + it = accepted.begin(); it != accepted.end(); ++it) + { + LOG(INFO) << "available for transcoding " << (count++) << ": " << sopClassUid + << " / " << GetTransferSyntaxUid(*it); + } + } + + if (accepted.find(transferSyntax) != accepted.end()) + { + printf("**** OK, without transcoding !! [%s]\n", GetTransferSyntaxUid(transferSyntax)); + } + else + { + // Transcoding - only in Orthanc >= 1.7.0 + + const DicomTransferSyntax uncompressed[] = { + DicomTransferSyntax_LittleEndianImplicit, // Default transfer syntax + DicomTransferSyntax_LittleEndianExplicit, + DicomTransferSyntax_BigEndianExplicit + }; + + bool found = false; + for (size_t i = 0; i < 3; i++) + { + if (accepted.find(uncompressed[i]) != accepted.end()) + { + printf("**** TRANSCODING to %s\n", GetTransferSyntaxUid(uncompressed[i])); + found = true; + break; + } + } + + if (!found) + { + printf("**** KO KO KO\n"); + } + } +} + + +TEST(Toto, DISABLED_Store) +{ + DicomAssociationParameters params; + params.SetLocalApplicationEntityTitle("ORTHANC"); + params.SetRemoteApplicationEntityTitle("STORESCP"); + params.SetRemotePort(2000); + + DicomStoreUserConnection assoc(params); + assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess1); + assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess2_4); + //assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_LittleEndianExplicit); + + //assoc.SetUncompressedSyntaxesProposed(false); // Necessary for transcoding + assoc.SetCommonClassesProposed(false); + assoc.SetRetiredBigEndianProposed(true); + TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_LittleEndianExplicit); + TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_JPEG2000); + TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_JPEG2000); +} + + +TEST(Toto, DISABLED_Store2) +{ + DicomAssociationParameters params; + params.SetLocalApplicationEntityTitle("ORTHANC"); + params.SetRemoteApplicationEntityTitle("STORESCP"); + params.SetRemotePort(2000); + + DicomStoreUserConnection assoc(params); + //assoc.SetCommonClassesProposed(false); + assoc.SetRetiredBigEndianProposed(true); + + std::string s; + Orthanc::SystemToolbox::ReadFile(s, "/tmp/i/" + std::string(GetTransferSyntaxUid(DicomTransferSyntax_BigEndianExplicit)) +".dcm"); + + std::string c, i; + assoc.Store(c, i, s.c_str(), s.size()); + printf("[%s] [%s]\n", c.c_str(), i.c_str()); +} + diff -r 37cf1889667a -r 210af28c4087 Resources/Patches/openssl-1.1.1d.patch --- a/Resources/Patches/openssl-1.1.1d.patch Thu May 07 11:31:58 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -diff -urEb openssl-1.1.1d.orig/crypto/rand/rand_unix.c openssl-1.1.1d/crypto/rand/rand_unix.c ---- openssl-1.1.1d.orig/crypto/rand/rand_unix.c 2019-09-10 15:13:07.000000000 +0200 -+++ openssl-1.1.1d/crypto/rand/rand_unix.c 2020-03-05 16:29:33.030136203 +0100 -@@ -340,7 +340,7 @@ - # endif - - /* Linux supports this since version 3.17 */ --# if defined(__linux) && defined(__NR_getrandom) -+# if defined(__linux) && defined(__NR_getrandom) && !defined(__LSB_VERSION__) - return syscall(__NR_getrandom, buf, buflen, 0); - # elif (defined(__FreeBSD__) || defined(__NetBSD__)) && defined(KERN_ARND) - return sysctl_random(buf, buflen); diff -r 37cf1889667a -r 210af28c4087 Resources/Patches/openssl-1.1.1f.patch --- a/Resources/Patches/openssl-1.1.1f.patch Thu May 07 11:31:58 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -diff -urEb openssl-1.1.1f.orig/crypto/rand/rand_unix.c openssl-1.1.1f/crypto/rand/rand_unix.c ---- openssl-1.1.1f.orig/crypto/rand/rand_unix.c 2020-03-31 14:17:45.000000000 +0200 -+++ openssl-1.1.1f/crypto/rand/rand_unix.c 2020-04-02 16:38:56.091240847 +0200 -@@ -445,6 +445,7 @@ - * system call and this should always succeed which renders - * this alternative but essentially identical source moot. - */ -+#if !defined(__LSB_VERSION__) // "syscall()" is not available in LSB - if (uname(&un) == 0) { - kernel[0] = atoi(un.release); - p = strchr(un.release, '.'); -@@ -455,6 +456,7 @@ - return 0; - } - } -+#endif - /* Open /dev/random and wait for it to be readable */ - if ((fd = open(DEVRANDOM_WAIT, O_RDONLY)) != -1) { - if (DEVRANDM_WAIT_USE_SELECT && fd < FD_SETSIZE) { diff -r 37cf1889667a -r 210af28c4087 Resources/Patches/openssl-1.1.1g.patch --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/openssl-1.1.1g.patch Thu May 07 11:32:15 2020 +0200 @@ -0,0 +1,19 @@ +diff -urEb openssl-1.1.1g.orig/crypto/rand/rand_unix.c openssl-1.1.1g/crypto/rand/rand_unix.c +--- openssl-1.1.1g.orig/crypto/rand/rand_unix.c 2020-05-05 17:58:08.785998440 +0200 ++++ openssl-1.1.1g/crypto/rand/rand_unix.c 2020-05-05 17:58:55.201881117 +0200 +@@ -445,6 +445,7 @@ + * system call and this should always succeed which renders + * this alternative but essentially identical source moot. + */ ++#if !defined(__LSB_VERSION__) // "syscall()" is not available in LSB + if (uname(&un) == 0) { + kernel[0] = atoi(un.release); + p = strchr(un.release, '.'); +@@ -455,6 +456,7 @@ + return 0; + } + } ++#endif + /* Open /dev/random and wait for it to be readable */ + if ((fd = open(DEVRANDOM_WAIT, O_RDONLY)) != -1) { + if (DEVRANDM_WAIT_USE_SELECT && fd < FD_SETSIZE) { diff -r 37cf1889667a -r 210af28c4087 UnitTestsSources/FromDcmtkTests.cpp --- a/UnitTestsSources/FromDcmtkTests.cpp Thu May 07 11:31:58 2020 +0200 +++ b/UnitTestsSources/FromDcmtkTests.cpp Thu May 07 11:32:15 2020 +0200 @@ -1924,630 +1924,98 @@ #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 -#include "../Core/DicomParsing/Internals/DicomFrameIndex.h" - -#include -#include -#include -#include // for DJ_RPLossy -#include // for DJ_RPLossless - +#include "../Core/DicomNetworking/DicomStoreUserConnection.h" +#include "../Core/DicomParsing/DcmtkTranscoder.h" -namespace Orthanc +TEST(Toto, DISABLED_Transcode3) { - class IDicomTranscoder : public boost::noncopyable - { - public: - virtual ~IDicomTranscoder() - { - } - - virtual DcmFileFormat& GetDicom() = 0; + DicomAssociationParameters p; + p.SetRemotePort(2000); - virtual DicomTransferSyntax GetTransferSyntax() = 0; - - virtual std::string GetSopClassUid() = 0; - - virtual std::string GetSopInstanceUid() = 0; - - virtual unsigned int GetFramesCount() = 0; - - virtual ImageAccessor* DecodeFrame(unsigned int frame) = 0; + DicomStoreUserConnection scu(p); + scu.SetCommonClassesProposed(false); + scu.SetRetiredBigEndianProposed(true); - virtual void GetCompressedFrame(std::string& target, - unsigned int frame) = 0; + DcmtkTranscoder transcoder; - // NB: Transcoding can change the value of "GetSopInstanceUid()" - // and "GetTransferSyntax()" if lossy compression is applied - virtual bool Transcode(std::string& target, - std::set syntaxes, - bool allowNewSopInstanceUid) = 0; - - virtual void WriteToMemoryBuffer(std::string& target) = 0; - }; - + for (int j = 0; j < 2; j++) + for (int i = 0; i <= DicomTransferSyntax_XML; i++) + { + DicomTransferSyntax a = (DicomTransferSyntax) i; - class DcmtkTranscoder : public IDicomTranscoder - { - private: - std::unique_ptr dicom_; - std::unique_ptr index_; - DicomTransferSyntax transferSyntax_; - std::string sopClassUid_; - std::string sopInstanceUid_; - uint16_t bitsStored_; - unsigned int lossyQuality_; - - static std::string GetStringTag(DcmDataset& dataset, - const DcmTagKey& tag) - { - const char* value = NULL; - - if (!dataset.findAndGetString(tag, value).good() || - value == NULL) - { - throw OrthancException(ErrorCode_BadFileFormat, - "Missing SOP class/instance UID in DICOM instance"); - } - else + std::string path = ("/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/" + + std::string(GetTransferSyntaxUid(a)) + ".dcm"); + if (Orthanc::SystemToolbox::IsRegularFile(path)) { - return std::string(value); - } - } + printf("\n======= %s\n", GetTransferSyntaxUid(a)); + + std::string source; + Orthanc::SystemToolbox::ReadFile(source, path); - void Setup(DcmFileFormat* dicom) - { - lossyQuality_ = 90; - - dicom_.reset(dicom); - - if (dicom == NULL || - dicom_->getDataset() == NULL) - { - throw OrthancException(ErrorCode_NullPointer); - } - - DcmDataset& dataset = *dicom_->getDataset(); - index_.reset(new DicomFrameIndex(dataset)); - - E_TransferSyntax xfer = dataset.getOriginalXfer(); - if (xfer == EXS_Unknown) - { - dataset.updateOriginalXfer(); - xfer = dataset.getOriginalXfer(); - if (xfer == EXS_Unknown) + std::string c, i; + try + { + scu.Transcode(c, i, transcoder, source.c_str(), source.size(), false, "", 0); + } + catch (OrthancException& e) { - throw OrthancException(ErrorCode_BadFileFormat, - "Cannot determine the transfer syntax of the DICOM instance"); + if (e.GetErrorCode() == ErrorCode_NotImplemented) + { + LOG(ERROR) << "cannot transcode " << GetTransferSyntaxUid(a); + } + else + { + throw e; + } } } - - if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transferSyntax_, xfer)) - { - throw OrthancException( - ErrorCode_BadFileFormat, - "Unsupported transfer syntax: " + boost::lexical_cast(xfer)); - } - - if (!dataset.findAndGetUint16(DCM_BitsStored, bitsStored_).good()) - { - throw OrthancException(ErrorCode_BadFileFormat, - "Missing \"Bits Stored\" tag in DICOM instance"); - } - - sopClassUid_ = GetStringTag(dataset, DCM_SOPClassUID); - sopInstanceUid_ = GetStringTag(dataset, DCM_SOPInstanceUID); } - - public: - DcmtkTranscoder(DcmFileFormat* dicom) // Takes ownership - { - Setup(dicom); - } - - DcmtkTranscoder(const void* dicom, - size_t size) - { - Setup(FromDcmtkBridge::LoadFromMemoryBuffer(dicom, size)); - } - - void SetLossyQuality(unsigned int quality) - { - if (quality <= 0 || - quality > 100) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - lossyQuality_ = quality; - } - } - - unsigned int GetLossyQuality() const - { - return lossyQuality_; - } - - unsigned int GetBitsStored() const - { - return bitsStored_; - } - - virtual DcmFileFormat& GetDicom() - { - assert(dicom_ != NULL); - return *dicom_; - } - - virtual DicomTransferSyntax GetTransferSyntax() ORTHANC_OVERRIDE - { - return transferSyntax_; - } - - virtual std::string GetSopClassUid() ORTHANC_OVERRIDE - { - return sopClassUid_; - } - - virtual std::string GetSopInstanceUid() ORTHANC_OVERRIDE - { - return sopInstanceUid_; - } - - virtual unsigned int GetFramesCount() ORTHANC_OVERRIDE - { - return index_->GetFramesCount(); - } - - virtual void WriteToMemoryBuffer(std::string& target) ORTHANC_OVERRIDE - { - if (!FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_)) - { - throw OrthancException(ErrorCode_InternalError, - "Cannot write the DICOM instance to a memory buffer"); - } - } - - virtual ImageAccessor* DecodeFrame(unsigned int frame) ORTHANC_OVERRIDE - { - assert(dicom_->getDataset() != NULL); - return DicomImageDecoder::Decode(*dicom_->getDataset(), frame); - } - - virtual void GetCompressedFrame(std::string& target, - unsigned int frame) ORTHANC_OVERRIDE - { - index_->GetRawFrame(target, frame); - } - - virtual bool Transcode(std::string& target, - std::set syntaxes, - bool allowNewSopInstanceUid) ORTHANC_OVERRIDE - { - assert(dicom_ != NULL && - dicom_->getDataset() != NULL); - - if (syntaxes.find(GetTransferSyntax()) != syntaxes.end()) - { - printf("NO TRANSCODING\n"); - - // No change in the transfer syntax => simply serialize the current dataset - WriteToMemoryBuffer(target); - return true; - } - - printf(">> %d\n", bitsStored_); - - DJ_RPLossy rpLossy(lossyQuality_); - - if (syntaxes.find(DicomTransferSyntax_LittleEndianImplicit) != syntaxes.end() && - FromDcmtkBridge::Transcode(target, *dicom_, DicomTransferSyntax_LittleEndianImplicit, NULL)) - { - transferSyntax_ = DicomTransferSyntax_LittleEndianImplicit; - return true; - } - else if (syntaxes.find(DicomTransferSyntax_LittleEndianExplicit) != syntaxes.end() && - FromDcmtkBridge::Transcode(target, *dicom_, DicomTransferSyntax_LittleEndianExplicit, NULL)) - { - transferSyntax_ = DicomTransferSyntax_LittleEndianExplicit; - return true; - } - else if (syntaxes.find(DicomTransferSyntax_BigEndianExplicit) != syntaxes.end() && - FromDcmtkBridge::Transcode(target, *dicom_, DicomTransferSyntax_BigEndianExplicit, NULL)) - { - transferSyntax_ = DicomTransferSyntax_BigEndianExplicit; - return true; - } - else if (syntaxes.find(DicomTransferSyntax_JPEGProcess1) != syntaxes.end() && - allowNewSopInstanceUid && - GetBitsStored() == 8 && - FromDcmtkBridge::Transcode(target, *dicom_, DicomTransferSyntax_JPEGProcess1, &rpLossy)) - { - transferSyntax_ = DicomTransferSyntax_JPEGProcess1; - sopInstanceUid_ = GetStringTag(*dicom_->getDataset(), DCM_SOPInstanceUID); - return true; - } - else if (syntaxes.find(DicomTransferSyntax_JPEGProcess2_4) != syntaxes.end() && - allowNewSopInstanceUid && - GetBitsStored() <= 12 && - FromDcmtkBridge::Transcode(target, *dicom_, DicomTransferSyntax_JPEGProcess2_4, &rpLossy)) - { - transferSyntax_ = DicomTransferSyntax_JPEGProcess2_4; - sopInstanceUid_ = GetStringTag(*dicom_->getDataset(), DCM_SOPInstanceUID); - return true; - } - else - { - return false; - } - } - }; } - - -static bool Transcode(std::string& buffer, - DcmDataset& dataSet, - E_TransferSyntax xfer) +TEST(Toto, DISABLED_Transcode4) { - // Determine the transfer syntax which shall be used to write the - // information to the file. We always switch to the Little Endian - // syntax, with explicit length. - - // http://support.dcmtk.org/docs/dcxfer_8h-source.html - - - /** - * Note that up to Orthanc 0.7.1 (inclusive), the - * "EXS_LittleEndianExplicit" was always used to save the DICOM - * dataset into memory. We now keep the original transfer syntax - * (if available). - **/ - //E_TransferSyntax xfer = dataSet.getOriginalXfer(); - if (xfer == EXS_Unknown) - { - // No information about the original transfer syntax: This is - // most probably a DICOM dataset that was read from memory. - xfer = EXS_LittleEndianExplicit; - } - - E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength; + std::string source; + Orthanc::SystemToolbox::ReadFile(source, "/home/jodogne/Subversion/orthanc-tests/Database/KarstenHilbertRF.dcm"); - // Create the meta-header information - DcmFileFormat ff(&dataSet); - ff.validateMetaInfo(xfer); - ff.removeInvalidGroups(); - - // Create a memory buffer with the proper size - { - const uint32_t estimatedSize = ff.calcElementLength(xfer, encodingType); // (*) - buffer.resize(estimatedSize); - } - - DcmOutputBufferStream ob(&buffer[0], buffer.size()); + std::unique_ptr toto(FromDcmtkBridge::LoadFromMemoryBuffer(source.c_str(), source.size())); + DicomTransferSyntax sourceSyntax; + ASSERT_TRUE(FromDcmtkBridge::LookupOrthancTransferSyntax(sourceSyntax, *toto)); - // Fill the memory buffer with the meta-header and the dataset - ff.transferInit(); - OFCondition c = ff.write(ob, xfer, encodingType, NULL, - /*opt_groupLength*/ EGL_recalcGL, - /*opt_paddingType*/ EPD_withoutPadding); - ff.transferEnd(); + DcmtkTranscoder transcoder; - if (c.good()) - { - // The DICOM file is successfully written, truncate the target - // buffer if its size was overestimated by (*) - ob.flush(); - - size_t effectiveSize = static_cast(ob.tell()); - if (effectiveSize < buffer.size()) - { - buffer.resize(effectiveSize); - } - - return true; - } - else + for (int i = 0; i <= DicomTransferSyntax_XML; i++) { - // Error - buffer.clear(); - return false; - } -} - - -#include - - -static void TestFile(const std::string& path) -{ - static unsigned int count = 0; - count++; - - - printf("** %s\n", path.c_str()); - - std::string s; - SystemToolbox::ReadFile(s, path); - - Orthanc::DcmtkTranscoder transcoder(s.c_str(), s.size()); - - /*if (transcoder.GetBitsStored() != 8) // TODO - return; */ - - { - char buf[1024]; - sprintf(buf, "/tmp/source-%06d.dcm", count); - printf(">> %s\n", buf); - Orthanc::SystemToolbox::WriteFile(s, buf); - } - - printf("[%s] [%s] [%s] %d %d\n", GetTransferSyntaxUid(transcoder.GetTransferSyntax()), - transcoder.GetSopClassUid().c_str(), transcoder.GetSopInstanceUid().c_str(), - transcoder.GetFramesCount(), transcoder.GetTransferSyntax()); - - for (size_t i = 0; i < transcoder.GetFramesCount(); i++) - { - std::string f; - transcoder.GetCompressedFrame(f, i); - - if (i == 0) - { - char buf[1024]; - sprintf(buf, "/tmp/frame-%06d.raw", count); - printf(">> %s\n", buf); - Orthanc::SystemToolbox::WriteFile(f, buf); - } - } - - { - std::string t; - transcoder.WriteToMemoryBuffer(t); - - Orthanc::DcmtkTranscoder transcoder2(t.c_str(), t.size()); - printf(">> %d %d ; %lu bytes\n", transcoder.GetTransferSyntax(), transcoder2.GetTransferSyntax(), t.size()); - } - - { - std::string a = transcoder.GetSopInstanceUid(); - DicomTransferSyntax b = transcoder.GetTransferSyntax(); - - std::set syntaxes; - syntaxes.insert(DicomTransferSyntax_JPEGProcess2_4); - //syntaxes.insert(DicomTransferSyntax_LittleEndianExplicit); + DicomTransferSyntax a = (DicomTransferSyntax) i; + std::set s; + s.insert(a); std::string t; - bool ok = transcoder.Transcode(t, syntaxes, true); - printf("Transcoding: %d\n", ok); - if (ok) + bool hasSopInstanceUidChanged; + + if (!transcoder.TranscodeToBuffer(t, hasSopInstanceUidChanged, source.c_str(), source.size(), s, true)) { - printf("[%s] => [%s]\n", a.c_str(), transcoder.GetSopInstanceUid().c_str()); - printf("[%s] => [%s]\n", GetTransferSyntaxUid(b), - GetTransferSyntaxUid(transcoder.GetTransferSyntax())); - - { - char buf[1024]; - sprintf(buf, "/tmp/transcoded-%06d.dcm", count); - printf(">> %s\n", buf); - Orthanc::SystemToolbox::WriteFile(t, buf); - } - - Orthanc::DcmtkTranscoder transcoder2(t.c_str(), t.size()); - printf(" => transcoded transfer syntax %d ; %lu bytes\n", transcoder2.GetTransferSyntax(), t.size()); - } - } - - printf("\n"); -} - -TEST(Toto, DISABLED_Transcode) -{ - //OFLog::configure(OFLogger::DEBUG_LOG_LEVEL); - - if (0) - { - std::string s; - //SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.4.50.dcm"); - //SystemToolbox::ReadFile(s, "/home/jodogne/DICOM/Alain.dcm"); - //SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/Brainix/Epi/IM-0001-0002.dcm"); - SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.1.dcm"); - - std::unique_ptr dicom(FromDcmtkBridge::LoadFromMemoryBuffer(s.c_str(), s.size())); - - // less /home/jodogne/Downloads/dcmtk-3.6.4/dcmdata/include/dcmtk/dcmdata/dcxfer.h - printf(">> %d\n", dicom->getDataset()->getOriginalXfer()); // => 4 == EXS_JPEGProcess1 - - const DcmRepresentationParameter *p; - -#if 0 - E_TransferSyntax target = EXS_LittleEndianExplicit; - p = NULL; -#elif 0 - E_TransferSyntax target = EXS_JPEGProcess14SV1; - DJ_RPLossless rp_lossless(6, 0); - p = &rp_lossless; -#else - E_TransferSyntax target = EXS_JPEGProcess1; - DJ_RPLossy rp_lossy(90); // quality - p = &rp_lossy; -#endif - - ASSERT_TRUE(dicom->getDataset()->chooseRepresentation(target, p).good()); - ASSERT_TRUE(dicom->getDataset()->canWriteXfer(target)); - - std::string t; - ASSERT_TRUE(Transcode(t, *dicom->getDataset(), target)); - - SystemToolbox::WriteFile(s, "source.dcm"); - SystemToolbox::WriteFile(t, "target.dcm"); - } - - if (1) - { - const char* const PATH = "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes"; - - for (boost::filesystem::directory_iterator it(PATH); - it != boost::filesystem::directory_iterator(); ++it) - { - if (boost::filesystem::is_regular_file(it->status())) - { - TestFile(it->path().string()); - } + printf("**************** CANNOT: [%s] => [%s]\n", + GetTransferSyntaxUid(sourceSyntax), GetTransferSyntaxUid(a)); } - } - - if (0) - { - TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Multiframe.dcm"); - TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Issue44/Monochrome1-Jpeg.dcm"); - } - - if (0) - { - TestFile("/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.1.dcm"); - } -} - - - -#include "../Core/DicomNetworking/DicomAssociation.h" -#include "../Core/DicomNetworking/DicomControlUserConnection.h" -#include "../Core/DicomNetworking/DicomStoreUserConnection.h" - -TEST(Toto, DISABLED_DicomAssociation) -{ - DicomAssociationParameters params; - params.SetLocalApplicationEntityTitle("ORTHANC"); - params.SetRemoteApplicationEntityTitle("PACS"); - params.SetRemotePort(2001); - -#if 0 - DicomAssociation assoc; - assoc.ProposeGenericPresentationContext(UID_StorageCommitmentPushModelSOPClass); - assoc.ProposeGenericPresentationContext(UID_VerificationSOPClass); - assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage, - DicomTransferSyntax_JPEGProcess1); - assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage, - DicomTransferSyntax_JPEGProcess2_4); - assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage, - DicomTransferSyntax_JPEG2000); - - assoc.Open(params); - - int presID = ASC_findAcceptedPresentationContextID(&assoc.GetDcmtkAssociation(), UID_ComputedRadiographyImageStorage); - printf(">> %d\n", presID); - - std::map pc; - printf(">> %d\n", assoc.LookupAcceptedPresentationContext(pc, UID_ComputedRadiographyImageStorage)); - - for (std::map::const_iterator - it = pc.begin(); it != pc.end(); ++it) - { - printf("[%s] => %d\n", GetTransferSyntaxUid(it->first), it->second); - } -#else - { - DicomControlUserConnection assoc(params); - - try + else { - printf(">> %d\n", assoc.Echo()); - } - catch (OrthancException&) - { - } - } - - params.SetRemoteApplicationEntityTitle("PACS"); - params.SetRemotePort(2000); - - { - DicomControlUserConnection assoc(params); - printf(">> %d\n", assoc.Echo()); - } - -#endif -} - -static void TestTranscode(DicomStoreUserConnection& scu, - const std::string& sopClassUid, - DicomTransferSyntax transferSyntax) -{ - uint8_t id; + bool lossy = (a == DicomTransferSyntax_JPEGProcess1 || + a == DicomTransferSyntax_JPEGProcess2_4 || + a == DicomTransferSyntax_JPEGLSLossy); - if (scu.NegotiatePresentationContext(id, sopClassUid, transferSyntax)) - { - printf("**** OK, without transcoding !! %d\n", id); - } - else - { - // Transcoding - only in Orthanc >= 1.7.0 - - const DicomTransferSyntax uncompressed[] = { - DicomTransferSyntax_LittleEndianImplicit, // Default transfer syntax - DicomTransferSyntax_LittleEndianExplicit, - DicomTransferSyntax_BigEndianExplicit - }; - - bool found = false; - for (size_t i = 0; i < 3; i++) - { - if (scu.LookupPresentationContext(id, sopClassUid, uncompressed[i])) + printf("SIZE: %lu\n", t.size()); + if (hasSopInstanceUidChanged) { - printf("**** TRANSCODING to %s => %d\n", - GetTransferSyntaxUid(uncompressed[i]), id); - found = true; - break; + ASSERT_TRUE(lossy); } - } - - if (!found) - { - printf("**** KO KO KO\n"); + else + { + ASSERT_FALSE(lossy); + } } } } - -TEST(Toto, DISABLED_Store) -{ - DicomAssociationParameters params; - params.SetLocalApplicationEntityTitle("ORTHANC"); - params.SetRemoteApplicationEntityTitle("STORESCP"); - params.SetRemotePort(2000); - - DicomStoreUserConnection assoc(params); - assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess1); - assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess2_4); - //assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_LittleEndianExplicit); - - //assoc.SetUncompressedSyntaxesProposed(false); // Necessary for transcoding - //assoc.SetCommonClassesProposed(false); - TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_JPEG2000); - TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_LittleEndianExplicit); -} - - -TEST(Toto, DISABLED_Store2) -{ - DicomAssociationParameters params; - params.SetLocalApplicationEntityTitle("ORTHANC"); - params.SetRemoteApplicationEntityTitle("STORESCP"); - params.SetRemotePort(2000); - - DicomStoreUserConnection assoc(params); - //assoc.SetCommonClassesProposed(false); - assoc.SetRetiredBigEndianProposed(true); - - std::string s; - Orthanc::SystemToolbox::ReadFile(s, "/tmp/i/" + std::string(GetTransferSyntaxUid(DicomTransferSyntax_BigEndianExplicit)) +".dcm"); - - std::string c, i; - assoc.Store(c, i, s.c_str(), s.size()); - printf("[%s] [%s]\n", c.c_str(), i.c_str()); -} - #endif diff -r 37cf1889667a -r 210af28c4087 UnitTestsSources/MultiThreadingTests.cpp --- a/UnitTestsSources/MultiThreadingTests.cpp Thu May 07 11:31:58 2020 +0200 +++ b/UnitTestsSources/MultiThreadingTests.cpp Thu May 07 11:32:15 2020 +0200 @@ -62,6 +62,7 @@ #include "../OrthancServer/ServerJobs/ArchiveJob.h" #include "../OrthancServer/ServerJobs/DicomModalityStoreJob.h" +#include "../OrthancServer/ServerJobs/DicomMoveScuJob.h" #include "../OrthancServer/ServerJobs/MergeStudyJob.h" #include "../OrthancServer/ServerJobs/OrthancPeerStoreJob.h" #include "../OrthancServer/ServerJobs/ResourceModificationJob.h" @@ -1414,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); @@ -1514,11 +1515,11 @@ job.reset(unserializer.UnserializeJob(s)); DicomModalityStoreJob& tmp = dynamic_cast(*job); - ASSERT_EQ("LOCAL", tmp.GetLocalAet()); - ASSERT_EQ("REMOTE", tmp.GetRemoteModality().GetApplicationEntityTitle()); - ASSERT_EQ("192.168.1.1", tmp.GetRemoteModality().GetHost()); - ASSERT_EQ(1000, tmp.GetRemoteModality().GetPortNumber()); - ASSERT_EQ(ModalityManufacturer_StoreScp, tmp.GetRemoteModality().GetManufacturer()); + ASSERT_EQ("LOCAL", tmp.GetParameters().GetLocalApplicationEntityTitle()); + ASSERT_EQ("REMOTE", tmp.GetParameters().GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("192.168.1.1", tmp.GetParameters().GetRemoteModality().GetHost()); + ASSERT_EQ(1000, tmp.GetParameters().GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_StoreScp, tmp.GetParameters().GetRemoteModality().GetManufacturer()); ASSERT_TRUE(tmp.HasMoveOriginator()); ASSERT_EQ("MOVESCU", tmp.GetMoveOriginatorAet()); ASSERT_EQ(42, tmp.GetMoveOriginatorId()); @@ -1902,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; @@ -1932,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"; @@ -1998,6 +2001,7 @@ ASSERT_EQ(104u, modality.GetPortNumber()); ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction)); ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + ASSERT_TRUE(modality.IsTranscodingAllowed()); } { @@ -2007,6 +2011,7 @@ s["AET"] = "AET"; s["Host"] = "host"; s["Port"] = "104"; + s["AllowTranscoding"] = false; RemoteModalityParameters modality(s); ASSERT_TRUE(modality.IsAdvancedFormatNeeded()); @@ -2015,6 +2020,7 @@ ASSERT_EQ(104u, modality.GetPortNumber()); ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction)); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + ASSERT_FALSE(modality.IsTranscodingAllowed()); } { @@ -2032,5 +2038,143 @@ ASSERT_EQ(104u, modality.GetPortNumber()); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction)); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + ASSERT_TRUE(modality.IsTranscodingAllowed()); } } + + +TEST_F(OrthancJobsSerialization, DicomAssociationParameters) +{ + Json::Value v; + + { + v = Json::objectValue; + DicomAssociationParameters p; + p.SerializeJob(v); + } + + { + DicomAssociationParameters p = DicomAssociationParameters::UnserializeJob(v); + ASSERT_EQ("ORTHANC", p.GetLocalApplicationEntityTitle()); + ASSERT_EQ("ANY-SCP", p.GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ(104u, p.GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, p.GetRemoteModality().GetManufacturer()); + ASSERT_EQ("127.0.0.1", p.GetRemoteModality().GetHost()); + ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), p.GetTimeout()); + } + + { + v = Json::objectValue; + DicomAssociationParameters p; + p.SetLocalApplicationEntityTitle("HELLO"); + p.SetRemoteApplicationEntityTitle("WORLD"); + p.SetRemotePort(42); + p.SetRemoteHost("MY_HOST"); + p.SetTimeout(43); + p.SerializeJob(v); + } + + { + DicomAssociationParameters p = DicomAssociationParameters::UnserializeJob(v); + ASSERT_EQ("HELLO", p.GetLocalApplicationEntityTitle()); + ASSERT_EQ("WORLD", p.GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ(42u, p.GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, p.GetRemoteModality().GetManufacturer()); + ASSERT_EQ("MY_HOST", p.GetRemoteModality().GetHost()); + ASSERT_EQ(43u, p.GetTimeout()); + } + + { + DicomModalityStoreJob job(GetContext()); + job.Serialize(v); + } + + { + OrthancJobUnserializer unserializer(GetContext()); + std::unique_ptr job( + dynamic_cast(unserializer.UnserializeJob(v))); + ASSERT_EQ("ORTHANC", job->GetParameters().GetLocalApplicationEntityTitle()); + ASSERT_EQ("ANY-SCP", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("127.0.0.1", job->GetParameters().GetRemoteModality().GetHost()); + ASSERT_EQ(104u, job->GetParameters().GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer()); + ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), job->GetParameters().GetTimeout()); + ASSERT_FALSE(job->HasMoveOriginator()); + ASSERT_THROW(job->GetMoveOriginatorAet(), OrthancException); + ASSERT_THROW(job->GetMoveOriginatorId(), OrthancException); + ASSERT_FALSE(job->HasStorageCommitment()); + } + + { + RemoteModalityParameters r; + r.SetApplicationEntityTitle("HELLO"); + r.SetPortNumber(42); + r.SetHost("MY_HOST"); + + DicomModalityStoreJob job(GetContext()); + job.SetLocalAet("WORLD"); + job.SetRemoteModality(r); + job.SetTimeout(43); + job.SetMoveOriginator("ORIGINATOR", 100); + job.EnableStorageCommitment(true); + job.Serialize(v); + } + + { + OrthancJobUnserializer unserializer(GetContext()); + std::unique_ptr job( + dynamic_cast(unserializer.UnserializeJob(v))); + ASSERT_EQ("WORLD", job->GetParameters().GetLocalApplicationEntityTitle()); + ASSERT_EQ("HELLO", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("MY_HOST", job->GetParameters().GetRemoteModality().GetHost()); + ASSERT_EQ(42u, job->GetParameters().GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer()); + ASSERT_EQ(43u, job->GetParameters().GetTimeout()); + ASSERT_TRUE(job->HasMoveOriginator()); + ASSERT_EQ("ORIGINATOR", job->GetMoveOriginatorAet()); + ASSERT_EQ(100, job->GetMoveOriginatorId()); + ASSERT_TRUE(job->HasStorageCommitment()); + } + + { + DicomMoveScuJob job(GetContext()); + job.Serialize(v); + } + + { + OrthancJobUnserializer unserializer(GetContext()); + std::unique_ptr job( + dynamic_cast(unserializer.UnserializeJob(v))); + ASSERT_EQ("ORTHANC", job->GetParameters().GetLocalApplicationEntityTitle()); + ASSERT_EQ("ANY-SCP", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("127.0.0.1", job->GetParameters().GetRemoteModality().GetHost()); + ASSERT_EQ(104u, job->GetParameters().GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer()); + ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), job->GetParameters().GetTimeout()); + } + + { + RemoteModalityParameters r; + r.SetApplicationEntityTitle("HELLO"); + r.SetPortNumber(42); + r.SetHost("MY_HOST"); + + DicomMoveScuJob job(GetContext()); + job.SetLocalAet("WORLD"); + job.SetRemoteModality(r); + job.SetTimeout(43); + job.Serialize(v); + } + + { + OrthancJobUnserializer unserializer(GetContext()); + std::unique_ptr job( + dynamic_cast(unserializer.UnserializeJob(v))); + ASSERT_EQ("WORLD", job->GetParameters().GetLocalApplicationEntityTitle()); + ASSERT_EQ("HELLO", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("MY_HOST", job->GetParameters().GetRemoteModality().GetHost()); + ASSERT_EQ(42u, job->GetParameters().GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer()); + ASSERT_EQ(43u, job->GetParameters().GetTimeout()); + } +} diff -r 37cf1889667a -r 210af28c4087 UnitTestsSources/VersionsTests.cpp --- a/UnitTestsSources/VersionsTests.cpp Thu May 07 11:31:58 2020 +0200 +++ b/UnitTestsSources/VersionsTests.cpp Thu May 07 11:32:15 2020 +0200 @@ -185,7 +185,7 @@ TEST(Version, OpenSslStatic) { ASSERT_TRUE(OPENSSL_VERSION_NUMBER == 0x1000210fL /* openssl-1.0.2p */ || - OPENSSL_VERSION_NUMBER == 0x1010106fL /* openssl-1.1.1f */); + OPENSSL_VERSION_NUMBER == 0x1010107fL /* openssl-1.1.1g */); } #endif