# HG changeset patch # User Sebastien Jodogne # Date 1588005260 -7200 # Node ID e1c2bc9a5cc1014dd635ff36a11642fedba99f7a # Parent ce5c4b9fa09a152421f99123fb84833e96e3d5f1# Parent ff0718a4633a0ea37c968db9383ce3ea264e7ee9 integration mainline->transcoding diff -r ff0718a4633a -r e1c2bc9a5cc1 CMakeLists.txt --- a/CMakeLists.txt Mon Apr 27 18:33:33 2020 +0200 +++ b/CMakeLists.txt Mon Apr 27 18:34:20 2020 +0200 @@ -26,7 +26,7 @@ set(ENABLE_ZLIB ON) # To test transcoding -#set(ENABLE_DCMTK_TRANSCODING ON) +set(ENABLE_DCMTK_TRANSCODING ON) set(HAS_EMBEDDED_RESOURCES ON) diff -r ff0718a4633a -r e1c2bc9a5cc1 Core/DicomNetworking/DicomStoreUserConnection.cpp --- a/Core/DicomNetworking/DicomStoreUserConnection.cpp Mon Apr 27 18:33:33 2020 +0200 +++ b/Core/DicomNetworking/DicomStoreUserConnection.cpp Mon Apr 27 18:34:20 2020 +0200 @@ -116,16 +116,31 @@ return false; } + + + 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), - association_(new DicomAssociation), - proposeCommonClasses_(true), - proposeUncompressedSyntaxes_(true), - proposeRetiredBigEndian_(false) + parameters_(params) { + Setup(); } @@ -147,6 +162,32 @@ } + void DicomStoreUserConnection::LookupParameters(std::string& sopClassUid, + std::string& sopInstanceUid, + DicomTransferSyntax& transferSyntax, + DcmDataset& dataset) + { + OFString a, b; + if (!dataset.findAndGetOFString(DCM_SOPClassUID, a).good() || + !dataset.findAndGetOFString(DCM_SOPInstanceUID, b).good()) + { + throw OrthancException(ErrorCode_NoSopClassOrInstance, + "Unable to determine the SOP class/instance for C-STORE with AET " + + parameters_.GetRemoteApplicationEntityTitle()); + } + + sopClassUid.assign(a.c_str()); + sopInstanceUid.assign(b.c_str()); + + if (!FromDcmtkBridge::LookupOrthancTransferSyntax( + transferSyntax, dataset.getOriginalXfer())) + { + throw OrthancException(ErrorCode_InternalError, + "Unknown transfer syntax from DCMTK"); + } + } + + bool DicomStoreUserConnection::NegotiatePresentationContext( uint8_t& presentationContextId, const std::string& sopClassUid, @@ -163,8 +204,12 @@ } // The association must be re-negotiated - LOG(INFO) << "Re-negociating DICOM association with " - << parameters_.GetRemoteApplicationEntityTitle(); + if (association_->IsOpen()) + { + LOG(INFO) << "Re-negociating DICOM association with " + << parameters_.GetRemoteApplicationEntityTitle(); + } + association_->ClearPresentationContexts(); PrepareStorageClass(sopClassUid, transferSyntax); @@ -249,32 +294,18 @@ const std::string& moveOriginatorAET, uint16_t moveOriginatorID) { - OFString a, b; - if (!dataset.findAndGetOFString(DCM_SOPClassUID, a).good() || - !dataset.findAndGetOFString(DCM_SOPInstanceUID, b).good()) + DicomTransferSyntax transferSyntax; + LookupParameters(sopClassUid, sopInstanceUid, transferSyntax, dataset); + + uint8_t presID; + if (!NegotiatePresentationContext(presID, sopClassUid, transferSyntax)) { - throw OrthancException(ErrorCode_NoSopClassOrInstance, - "Unable to determine the SOP class/instance for C-STORE with AET " + - parameters_.GetRemoteApplicationEntityTitle()); - } - - sopClassUid.assign(a.c_str()); - sopInstanceUid.assign(b.c_str()); - - DicomTransferSyntax transferSyntax; - if (!FromDcmtkBridge::LookupOrthancTransferSyntax( - transferSyntax, dataset.getOriginalXfer())) - { - throw OrthancException(ErrorCode_InternalError, - "Unknown transfer syntax from DCMTK"); - } - - // Figure out which accepted presentation context should be used - uint8_t presID; - if (!NegotiatePresentationContext(presID, sopClassUid.c_str(), transferSyntax)) - { - throw OrthancException(ErrorCode_InternalError, - "No valid presentation context was negotiated upfront"); + throw OrthancException(ErrorCode_NetworkProtocol, + "No valid presentation context was negotiated for " + "SOP class UID [" + sopClassUid + "] and transfer " + "syntax [" + GetTransferSyntaxUid(transferSyntax) + "] " + "while sending to modality [" + + parameters_.GetRemoteApplicationEntityTitle() + "]"); } // Prepare the transmission of data @@ -338,6 +369,11 @@ const std::string& moveOriginatorAET, uint16_t moveOriginatorID) { + if (parsed.GetDcmtkObject().getDataset() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + Store(sopClassUid, sopInstanceUid, *parsed.GetDcmtkObject().getDataset(), moveOriginatorAET, moveOriginatorID); } @@ -353,6 +389,12 @@ std::unique_ptr dicom( FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size)); + if (dicom.get() == NULL || + dicom->getDataset() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + Store(sopClassUid, sopInstanceUid, *dicom->getDataset(), moveOriginatorAET, moveOriginatorID); } diff -r ff0718a4633a -r e1c2bc9a5cc1 Core/DicomNetworking/DicomStoreUserConnection.h --- a/Core/DicomNetworking/DicomStoreUserConnection.h Mon Apr 27 18:33:33 2020 +0200 +++ b/Core/DicomNetworking/DicomStoreUserConnection.h Mon Apr 27 18:34:20 2020 +0200 @@ -72,22 +72,22 @@ typedef std::map > StorageClasses; DicomAssociationParameters parameters_; - boost::shared_ptr association_; + boost::shared_ptr association_; // "shared_ptr" is for PImpl StorageClasses storageClasses_; 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); - - // Should only be used if transcoding - bool LookupPresentationContext(uint8_t& presentationContextId, - const std::string& sopClassUid, - DicomTransferSyntax transferSyntax); public: + DicomStoreUserConnection(const std::string& localAet, + const RemoteModalityParameters& remote); + DicomStoreUserConnection(const DicomAssociationParameters& params); const DicomAssociationParameters& GetParameters() const @@ -128,11 +128,24 @@ void PrepareStorageClass(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, @@ -145,11 +158,20 @@ const std::string& moveOriginatorAET, uint16_t moveOriginatorID); + public: void Store(std::string& sopClassUid, std::string& sopInstanceUid, const void* buffer, size_t size, 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 + } }; } diff -r ff0718a4633a -r e1c2bc9a5cc1 Core/DicomNetworking/DicomUserConnection.cpp --- a/Core/DicomNetworking/DicomUserConnection.cpp Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1822 +0,0 @@ -/** - * 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 . - **/ - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - -Copyright (C) 1994-2011, OFFIS e.V. -All rights reserved. - -This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - -#include "../PrecompiledHeaders.h" -#include "DicomUserConnection.h" - -#if !defined(DCMTK_VERSION_NUMBER) -# error The macro DCMTK_VERSION_NUMBER must be defined -#endif - -#include "../Compatibility.h" -#include "../DicomFormat/DicomArray.h" -#include "../Logging.h" -#include "../OrthancException.h" -#include "../DicomParsing/FromDcmtkBridge.h" -#include "../DicomParsing/ToDcmtkBridge.h" -#include "NetworkingCompatibility.h" - -#include -#include -#include -#include -#include -#include - -#include - - -static const char* DEFAULT_PREFERRED_TRANSFER_SYNTAX = UID_LittleEndianImplicitTransferSyntax; - -/** - * "If we have more than 64 storage SOP classes, tools such as - * storescu will fail because they attempt to negotiate two - * presentation contexts for each SOP class, and there is a total - * limit of 128 contexts for one association." - **/ -static const unsigned int MAXIMUM_STORAGE_SOP_CLASSES = 64; - - -namespace Orthanc -{ - // By default, the timeout for DICOM SCU (client) connections is set to 10 seconds - static uint32_t defaultTimeout_ = 10; - - struct DicomUserConnection::PImpl - { - // Connection state - uint32_t dimseTimeout_; - uint32_t acseTimeout_; - T_ASC_Network* net_; - T_ASC_Parameters* params_; - T_ASC_Association* assoc_; - - bool IsOpen() const - { - return assoc_ != NULL; - } - - void CheckIsOpen() const; - - void Store(std::string& sopClassUidOut /* out */, - std::string& sopInstanceUidOut /* out */, - DcmInputStream& is, - DicomUserConnection& connection, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID); - }; - - - static void Check(const OFCondition& cond, - const std::string& aet, - const std::string& command) - { - if (cond.bad()) - { - // Reformat the error message from DCMTK by turning multiline - // errors into a single line - - std::string s(cond.text()); - std::string info; - info.reserve(s.size()); - - bool isMultiline = false; - for (size_t i = 0; i < s.size(); i++) - { - if (s[i] == '\r') - { - // Ignore - } - else if (s[i] == '\n') - { - if (isMultiline) - { - info += "; "; - } - else - { - info += " ("; - isMultiline = true; - } - } - else - { - info.push_back(s[i]); - } - } - - if (isMultiline) - { - info += ")"; - } - - throw OrthancException(ErrorCode_NetworkProtocol, - "DicomUserConnection - " + command + - " to AET \"" + aet + "\": " + info); - } - } - - void DicomUserConnection::PImpl::CheckIsOpen() const - { - if (!IsOpen()) - { - throw OrthancException(ErrorCode_NetworkProtocol, - "DicomUserConnection: First open the connection"); - } - } - - - void DicomUserConnection::CheckIsOpen() const - { - pimpl_->CheckIsOpen(); - } - - - static void RegisterStorageSOPClass(T_ASC_Parameters* params, - unsigned int& presentationContextId, - const std::string& sopClass, - const char* asPreferred[], - std::vector& asFallback, - const std::string& aet) - { - Check(ASC_addPresentationContext(params, presentationContextId, - sopClass.c_str(), asPreferred, 1), - aet, "initializing"); - presentationContextId += 2; - - if (asFallback.size() > 0) - { - Check(ASC_addPresentationContext(params, presentationContextId, - sopClass.c_str(), &asFallback[0], asFallback.size()), - aet, "initializing"); - presentationContextId += 2; - } - } - - - void DicomUserConnection::SetupPresentationContexts(Mode mode, - const std::string& preferredTransferSyntax) - { - // Flatten an array with the preferred transfer syntax - const char* asPreferred[1] = { preferredTransferSyntax.c_str() }; - - // Setup the fallback transfer syntaxes - std::set fallbackSyntaxes; - fallbackSyntaxes.insert(UID_LittleEndianExplicitTransferSyntax); - fallbackSyntaxes.insert(UID_BigEndianExplicitTransferSyntax); - fallbackSyntaxes.insert(UID_LittleEndianImplicitTransferSyntax); - fallbackSyntaxes.erase(preferredTransferSyntax); - - // Flatten an array with the fallback transfer syntaxes - std::vector asFallback; - asFallback.reserve(fallbackSyntaxes.size()); - for (std::set::const_iterator - it = fallbackSyntaxes.begin(); it != fallbackSyntaxes.end(); ++it) - { - asFallback.push_back(it->c_str()); - } - - CheckStorageSOPClassesInvariant(); - - switch (mode) - { - case Mode_Generic: - { - unsigned int presentationContextId = 1; - - for (std::list::const_iterator it = reservedStorageSOPClasses_.begin(); - it != reservedStorageSOPClasses_.end(); ++it) - { - RegisterStorageSOPClass(pimpl_->params_, presentationContextId, - *it, asPreferred, asFallback, remoteAet_); - } - - for (std::set::const_iterator it = storageSOPClasses_.begin(); - it != storageSOPClasses_.end(); ++it) - { - RegisterStorageSOPClass(pimpl_->params_, presentationContextId, - *it, asPreferred, asFallback, remoteAet_); - } - - for (std::set::const_iterator it = defaultStorageSOPClasses_.begin(); - it != defaultStorageSOPClasses_.end(); ++it) - { - RegisterStorageSOPClass(pimpl_->params_, presentationContextId, - *it, asPreferred, asFallback, remoteAet_); - } - - break; - } - - case Mode_RequestStorageCommitment: - case Mode_ReportStorageCommitment: - { - const char* as = UID_StorageCommitmentPushModelSOPClass; - - std::vector ts; - ts.push_back(UID_LittleEndianExplicitTransferSyntax); - ts.push_back(UID_LittleEndianImplicitTransferSyntax); - - T_ASC_SC_ROLE role; - switch (mode) - { - case Mode_RequestStorageCommitment: - role = ASC_SC_ROLE_DEFAULT; - break; - - case Mode_ReportStorageCommitment: - role = ASC_SC_ROLE_SCP; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - Check(ASC_addPresentationContext(pimpl_->params_, 1 /*presentationContextId*/, - as, &ts[0], ts.size(), role), - remoteAet_, "initializing"); - - break; - } - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - - static bool IsGenericTransferSyntax(const std::string& syntax) - { - return (syntax == UID_LittleEndianExplicitTransferSyntax || - syntax == UID_BigEndianExplicitTransferSyntax || - syntax == UID_LittleEndianImplicitTransferSyntax); - } - - - void DicomUserConnection::PImpl::Store(std::string& sopClassUidOut, - std::string& sopInstanceUidOut, - DcmInputStream& is, - DicomUserConnection& connection, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID) - { - DcmFileFormat dcmff; - Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength), - connection.remoteAet_, "C-STORE"); - - // Determine the storage SOP class UID for this instance - OFString sopClassUid; - if (dcmff.getDataset()->findAndGetOFString(DCM_SOPClassUID, sopClassUid).good()) - { - connection.AddStorageSOPClass(sopClassUid.c_str()); - } - - // Determine whether a new presentation context must be - // negotiated, depending on the transfer syntax of this instance - DcmXfer xfer(dcmff.getDataset()->getOriginalXfer()); - const std::string syntax(xfer.getXferID()); - bool isGeneric = IsGenericTransferSyntax(syntax); - - bool renegotiate; - - if (!IsOpen()) - { - renegotiate = true; - } - else if (isGeneric) - { - // Are we making a generic-to-specific or specific-to-generic change of - // the transfer syntax? If this is the case, renegotiate the connection. - renegotiate = !IsGenericTransferSyntax(connection.GetPreferredTransferSyntax()); - - if (renegotiate) - { - LOG(INFO) << "Use of non-generic transfer syntax: the C-Store associated must be renegotiated"; - } - } - else - { - // We are using a specific transfer syntax. Renegotiate if the - // current connection does not match this transfer syntax. - renegotiate = (syntax != connection.GetPreferredTransferSyntax()); - - if (renegotiate) - { - LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated"; - } - } - - if (renegotiate) - { - if (isGeneric) - { - connection.ResetPreferredTransferSyntax(); - } - else - { - connection.SetPreferredTransferSyntax(syntax); - } - } - - if (!connection.IsOpen()) - { - connection.Open(); - } - - // Figure out which SOP class and SOP instance is encapsulated in the file - DIC_UI sopClass; - DIC_UI sopInstance; - -#if DCMTK_VERSION_NUMBER >= 364 - if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClass, sizeof(sopClass), sopInstance, sizeof(sopInstance))) -#else - if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClass, sopInstance)) -#endif - { - throw OrthancException(ErrorCode_NoSopClassOrInstance, - "Unable to determine the SOP class/instance for C-STORE with AET " + - connection.remoteAet_); - } - - sopClassUidOut.assign(sopClass); - sopInstanceUidOut.assign(sopInstance); - - // Figure out which of the accepted presentation contexts should be used - int presID = ASC_findAcceptedPresentationContextID(assoc_, sopClass); - if (presID == 0) - { - const char *modalityName = dcmSOPClassUIDToModality(sopClass); - if (modalityName == NULL) modalityName = dcmFindNameOfUID(sopClass); - if (modalityName == NULL) modalityName = "unknown SOP class"; - throw OrthancException(ErrorCode_NoPresentationContext, - "Unable to determine the accepted presentation contexts for C-STORE with AET " + - connection.remoteAet_ + " (" + std::string(modalityName) + ")"); - } - - // Prepare the transmission of data - T_DIMSE_C_StoreRQ request; - memset(&request, 0, sizeof(request)); - request.MessageID = assoc_->nextMsgID++; - strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); - request.Priority = DIMSE_PRIORITY_MEDIUM; - request.DataSetType = DIMSE_DATASET_PRESENT; - strncpy(request.AffectedSOPInstanceUID, sopInstance, DIC_UI_LEN); - - if (!moveOriginatorAET.empty()) - { - strncpy(request.MoveOriginatorApplicationEntityTitle, - moveOriginatorAET.c_str(), DIC_AE_LEN); - request.opts = O_STORE_MOVEORIGINATORAETITLE; - - request.MoveOriginatorID = moveOriginatorID; // The type DIC_US is an alias for uint16_t - request.opts |= O_STORE_MOVEORIGINATORID; - } - - // Finally conduct transmission of data - T_DIMSE_C_StoreRSP response; - DcmDataset* statusDetail = NULL; - Check(DIMSE_storeUser(assoc_, presID, &request, - NULL, dcmff.getDataset(), /*progressCallback*/ NULL, NULL, - /*opt_blockMode*/ (dimseTimeout_ ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), - /*opt_dimse_timeout*/ dimseTimeout_, - &response, &statusDetail, NULL), - connection.remoteAet_, "C-STORE"); - - if (statusDetail != NULL) - { - delete statusDetail; - } - - - /** - * New in Orthanc 1.6.0: Deal with failures during C-STORE. - * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_B.2.3.html#table_B.2-1 - **/ - - if (response.DimseStatus != 0x0000 && // Success - response.DimseStatus != 0xB000 && // Warning - Coercion of Data Elements - response.DimseStatus != 0xB007 && // Warning - Data Set does not match SOP Class - response.DimseStatus != 0xB006) // Warning - Elements Discarded - { - char buf[16]; - sprintf(buf, "%04X", response.DimseStatus); - throw OrthancException(ErrorCode_NetworkProtocol, - "C-STORE SCU to AET \"" + connection.remoteAet_ + - "\" has failed with DIMSE status 0x" + buf); - } - } - - - namespace - { - struct FindPayload - { - DicomFindAnswers* answers; - const char* level; - bool isWorklist; - }; - } - - - static void FindCallback( - /* in */ - void *callbackData, - T_DIMSE_C_FindRQ *request, /* original find request */ - int responseCount, - T_DIMSE_C_FindRSP *response, /* pending response received */ - DcmDataset *responseIdentifiers /* pending response identifiers */ - ) - { - FindPayload& payload = *reinterpret_cast(callbackData); - - if (responseIdentifiers != NULL) - { - if (payload.isWorklist) - { - ParsedDicomFile answer(*responseIdentifiers); - payload.answers->Add(answer); - } - else - { - DicomMap m; - FromDcmtkBridge::ExtractDicomSummary(m, *responseIdentifiers); - - if (!m.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) - { - m.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, payload.level, false); - } - - payload.answers->Add(m); - } - } - } - - - static void NormalizeFindQuery(DicomMap& fixedQuery, - ResourceType level, - const DicomMap& fields) - { - std::set allowedTags; - - // WARNING: Do not add "break" or reorder items in this switch-case! - switch (level) - { - case ResourceType_Instance: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Instance); - - case ResourceType_Series: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Series); - - case ResourceType_Study: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Study); - - case ResourceType_Patient: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Patient); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - switch (level) - { - case ResourceType_Patient: - allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES); - allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES); - allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES); - break; - - case ResourceType_Study: - allowedTags.insert(DICOM_TAG_MODALITIES_IN_STUDY); - allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES); - allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES); - allowedTags.insert(DICOM_TAG_SOP_CLASSES_IN_STUDY); - break; - - case ResourceType_Series: - allowedTags.insert(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES); - break; - - default: - break; - } - - allowedTags.insert(DICOM_TAG_SPECIFIC_CHARACTER_SET); - - DicomArray query(fields); - for (size_t i = 0; i < query.GetSize(); i++) - { - const DicomTag& tag = query.GetElement(i).GetTag(); - if (allowedTags.find(tag) == allowedTags.end()) - { - LOG(WARNING) << "Tag not allowed for this C-Find level, will be ignored: " << tag; - } - else - { - fixedQuery.SetValue(tag, query.GetElement(i).GetValue()); - } - } - } - - - static ParsedDicomFile* ConvertQueryFields(const DicomMap& fields, - ModalityManufacturer manufacturer) - { - // Fix outgoing C-Find requests issue for Syngo.Via and its - // solution was reported by Emsy Chan by private mail on - // 2015-06-17. According to Robert van Ommen (2015-11-30), the - // same fix is required for Agfa Impax. This was generalized for - // generic manufacturer since it seems to affect PhilipsADW, - // GEWAServer as well: - // https://bitbucket.org/sjodogne/orthanc/issues/31/ - - switch (manufacturer) - { - case ModalityManufacturer_GenericNoWildcardInDates: - case ModalityManufacturer_GenericNoUniversalWildcard: - { - std::unique_ptr fix(fields.Clone()); - - std::set tags; - fix->GetTags(tags); - - for (std::set::const_iterator it = tags.begin(); it != tags.end(); ++it) - { - // Replace a "*" wildcard query by an empty query ("") for - // "date" or "all" value representations depending on the - // type of manufacturer. - if (manufacturer == ModalityManufacturer_GenericNoUniversalWildcard || - (manufacturer == ModalityManufacturer_GenericNoWildcardInDates && - FromDcmtkBridge::LookupValueRepresentation(*it) == ValueRepresentation_Date)) - { - const DicomValue* value = fix->TestAndGetValue(*it); - - if (value != NULL && - !value->IsNull() && - value->GetContent() == "*") - { - fix->SetValue(*it, "", false); - } - } - } - - return new ParsedDicomFile(*fix, GetDefaultDicomEncoding(), false /* be strict */); - } - - default: - return new ParsedDicomFile(fields, GetDefaultDicomEncoding(), false /* be strict */); - } - } - - - static void ExecuteFind(DicomFindAnswers& answers, - T_ASC_Association* association, - DcmDataset* dataset, - const char* sopClass, - bool isWorklist, - const char* level, - uint32_t dimseTimeout, - const std::string& remoteAet) - { - assert(isWorklist ^ (level != NULL)); - - FindPayload payload; - payload.answers = &answers; - payload.level = level; - payload.isWorklist = isWorklist; - - // Figure out which of the accepted presentation contexts should be used - int presID = ASC_findAcceptedPresentationContextID(association, sopClass); - if (presID == 0) - { - throw OrthancException(ErrorCode_DicomFindUnavailable, - "Remote AET is " + remoteAet); - } - - T_DIMSE_C_FindRQ request; - memset(&request, 0, sizeof(request)); - request.MessageID = association->nextMsgID++; - strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); - request.Priority = DIMSE_PRIORITY_MEDIUM; - request.DataSetType = DIMSE_DATASET_PRESENT; - - T_DIMSE_C_FindRSP response; - DcmDataset* statusDetail = NULL; - -#if DCMTK_VERSION_NUMBER >= 364 - int responseCount; -#endif - - OFCondition cond = DIMSE_findUser(association, presID, &request, dataset, -#if DCMTK_VERSION_NUMBER >= 364 - responseCount, -#endif - FindCallback, &payload, - /*opt_blockMode*/ (dimseTimeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), - /*opt_dimse_timeout*/ dimseTimeout, - &response, &statusDetail); - - if (statusDetail) - { - delete statusDetail; - } - - Check(cond, remoteAet, "C-FIND"); - - - /** - * New in Orthanc 1.6.0: Deal with failures during C-FIND. - * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.html#table_C.4-1 - **/ - - if (response.DimseStatus != 0x0000 && // Success - response.DimseStatus != 0xFF00 && // Pending - Matches are continuing - response.DimseStatus != 0xFF01) // Pending - Matches are continuing - { - char buf[16]; - sprintf(buf, "%04X", response.DimseStatus); - - if (response.DimseStatus == STATUS_FIND_Failed_UnableToProcess) - { - throw OrthancException(ErrorCode_NetworkProtocol, - HttpStatus_422_UnprocessableEntity, - "C-FIND SCU to AET \"" + remoteAet + - "\" has failed with DIMSE status 0x" + buf + - " (unable to process - invalid query ?)" - ); - } - else - { - throw OrthancException(ErrorCode_NetworkProtocol, - "C-FIND SCU to AET \"" + remoteAet + - "\" has failed with DIMSE status 0x" + buf); - } - } - - } - - - void DicomUserConnection::Find(DicomFindAnswers& result, - ResourceType level, - const DicomMap& originalFields, - bool normalize) - { - CheckIsOpen(); - - std::unique_ptr query; - - if (normalize) - { - DicomMap fields; - NormalizeFindQuery(fields, level, originalFields); - query.reset(ConvertQueryFields(fields, manufacturer_)); - } - else - { - query.reset(new ParsedDicomFile(originalFields, - GetDefaultDicomEncoding(), - false /* be strict */)); - } - - DcmDataset* dataset = query->GetDcmtkObject().getDataset(); - - const char* clevel = NULL; - const char* sopClass = NULL; - - switch (level) - { - case ResourceType_Patient: - clevel = "PATIENT"; - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "PATIENT"); - sopClass = UID_FINDPatientRootQueryRetrieveInformationModel; - break; - - case ResourceType_Study: - clevel = "STUDY"; - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "STUDY"); - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - break; - - case ResourceType_Series: - clevel = "SERIES"; - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "SERIES"); - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - break; - - case ResourceType_Instance: - clevel = "IMAGE"; - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE"); - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - - const char* universal; - if (manufacturer_ == ModalityManufacturer_GE) - { - universal = "*"; - } - else - { - universal = ""; - } - - - // Add the expected tags for this query level. - // WARNING: Do not reorder or add "break" in this switch-case! - switch (level) - { - case ResourceType_Instance: - if (!dataset->tagExists(DCM_SOPInstanceUID)) - { - DU_putStringDOElement(dataset, DCM_SOPInstanceUID, universal); - } - - case ResourceType_Series: - if (!dataset->tagExists(DCM_SeriesInstanceUID)) - { - DU_putStringDOElement(dataset, DCM_SeriesInstanceUID, universal); - } - - case ResourceType_Study: - if (!dataset->tagExists(DCM_AccessionNumber)) - { - DU_putStringDOElement(dataset, DCM_AccessionNumber, universal); - } - - if (!dataset->tagExists(DCM_StudyInstanceUID)) - { - DU_putStringDOElement(dataset, DCM_StudyInstanceUID, universal); - } - - case ResourceType_Patient: - if (!dataset->tagExists(DCM_PatientID)) - { - DU_putStringDOElement(dataset, DCM_PatientID, universal); - } - - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - assert(clevel != NULL && sopClass != NULL); - ExecuteFind(result, pimpl_->assoc_, dataset, sopClass, false, clevel, - pimpl_->dimseTimeout_, remoteAet_); - } - - - void DicomUserConnection::MoveInternal(const std::string& targetAet, - ResourceType level, - const DicomMap& fields) - { - CheckIsOpen(); - - std::unique_ptr query(ConvertQueryFields(fields, manufacturer_)); - DcmDataset* dataset = query->GetDcmtkObject().getDataset(); - - const char* sopClass = UID_MOVEStudyRootQueryRetrieveInformationModel; - switch (level) - { - case ResourceType_Patient: - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "PATIENT"); - break; - - case ResourceType_Study: - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "STUDY"); - break; - - case ResourceType_Series: - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "SERIES"); - break; - - case ResourceType_Instance: - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE"); - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - // Figure out which of the accepted presentation contexts should be used - int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass); - if (presID == 0) - { - throw OrthancException(ErrorCode_DicomMoveUnavailable, - "Remote AET is " + remoteAet_); - } - - T_DIMSE_C_MoveRQ request; - memset(&request, 0, sizeof(request)); - request.MessageID = pimpl_->assoc_->nextMsgID++; - strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); - request.Priority = DIMSE_PRIORITY_MEDIUM; - request.DataSetType = DIMSE_DATASET_PRESENT; - strncpy(request.MoveDestination, targetAet.c_str(), DIC_AE_LEN); - - T_DIMSE_C_MoveRSP response; - DcmDataset* statusDetail = NULL; - DcmDataset* responseIdentifiers = NULL; - OFCondition cond = DIMSE_moveUser(pimpl_->assoc_, presID, &request, dataset, - NULL, NULL, - /*opt_blockMode*/ (pimpl_->dimseTimeout_ ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), - /*opt_dimse_timeout*/ pimpl_->dimseTimeout_, - pimpl_->net_, NULL, NULL, - &response, &statusDetail, &responseIdentifiers); - - if (statusDetail) - { - delete statusDetail; - } - - if (responseIdentifiers) - { - delete responseIdentifiers; - } - - Check(cond, remoteAet_, "C-MOVE"); - - - /** - * New in Orthanc 1.6.0: Deal with failures during C-MOVE. - * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.2.html#table_C.4-2 - **/ - - if (response.DimseStatus != 0x0000 && // Success - response.DimseStatus != 0xFF00) // Pending - Sub-operations are continuing - { - char buf[16]; - sprintf(buf, "%04X", response.DimseStatus); - - if (response.DimseStatus == STATUS_MOVE_Failed_UnableToProcess) - { - throw OrthancException(ErrorCode_NetworkProtocol, - HttpStatus_422_UnprocessableEntity, - "C-MOVE SCU to AET \"" + remoteAet_ + - "\" has failed with DIMSE status 0x" + buf + - " (unable to process - resource not found ?)" - ); - } - else - { - throw OrthancException(ErrorCode_NetworkProtocol, - "C-MOVE SCU to AET \"" + remoteAet_ + - "\" has failed with DIMSE status 0x" + buf); - } - } - } - - - void DicomUserConnection::ResetStorageSOPClasses() - { - CheckStorageSOPClassesInvariant(); - - storageSOPClasses_.clear(); - defaultStorageSOPClasses_.clear(); - - // Copy the short list of storage SOP classes from DCMTK, making - // room for the 5 SOP classes reserved for C-ECHO, C-FIND, C-MOVE at (**). - - std::set uncommon; - uncommon.insert(UID_BlendingSoftcopyPresentationStateStorage); - uncommon.insert(UID_GrayscaleSoftcopyPresentationStateStorage); - uncommon.insert(UID_ColorSoftcopyPresentationStateStorage); - uncommon.insert(UID_PseudoColorSoftcopyPresentationStateStorage); - uncommon.insert(UID_XAXRFGrayscaleSoftcopyPresentationStateStorage); - - // Add the storage syntaxes for C-STORE - for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++) - { - if (uncommon.find(dcmShortSCUStorageSOPClassUIDs[i]) == uncommon.end()) - { - defaultStorageSOPClasses_.insert(dcmShortSCUStorageSOPClassUIDs[i]); - } - } - - CheckStorageSOPClassesInvariant(); - } - - - void DicomUserConnection::DefaultSetup() - { - preferredTransferSyntax_ = DEFAULT_PREFERRED_TRANSFER_SYNTAX; - localAet_ = "STORESCU"; - remoteAet_ = "ANY-SCP"; - remoteHost_ = "127.0.0.1"; - remotePort_ = 104; - manufacturer_ = ModalityManufacturer_Generic; - - SetTimeout(defaultTimeout_); - pimpl_->net_ = NULL; - pimpl_->params_ = NULL; - pimpl_->assoc_ = NULL; - - // SOP classes for C-ECHO, C-FIND and C-MOVE (**) - reservedStorageSOPClasses_.push_back(UID_VerificationSOPClass); - reservedStorageSOPClasses_.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); - reservedStorageSOPClasses_.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); - reservedStorageSOPClasses_.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); - reservedStorageSOPClasses_.push_back(UID_FINDModalityWorklistInformationModel); - - ResetStorageSOPClasses(); - } - - - DicomUserConnection::DicomUserConnection() : - pimpl_(new PImpl) - { - DefaultSetup(); - } - - - DicomUserConnection::DicomUserConnection(const std::string& localAet, - const RemoteModalityParameters& remote) : - pimpl_(new PImpl) - { - DefaultSetup(); - SetLocalApplicationEntityTitle(localAet); - SetRemoteModality(remote); - } - - - DicomUserConnection::~DicomUserConnection() - { - Close(); - } - - - void DicomUserConnection::SetRemoteModality(const RemoteModalityParameters& parameters) - { - SetRemoteApplicationEntityTitle(parameters.GetApplicationEntityTitle()); - SetRemoteHost(parameters.GetHost()); - SetRemotePort(parameters.GetPortNumber()); - SetRemoteManufacturer(parameters.GetManufacturer()); - } - - - void DicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet) - { - if (localAet_ != aet) - { - Close(); - localAet_ = aet; - } - } - - void DicomUserConnection::SetRemoteApplicationEntityTitle(const std::string& aet) - { - if (remoteAet_ != aet) - { - Close(); - remoteAet_ = aet; - } - } - - void DicomUserConnection::SetRemoteManufacturer(ModalityManufacturer manufacturer) - { - if (manufacturer_ != manufacturer) - { - Close(); - manufacturer_ = manufacturer; - } - } - - void DicomUserConnection::ResetPreferredTransferSyntax() - { - SetPreferredTransferSyntax(DEFAULT_PREFERRED_TRANSFER_SYNTAX); - } - - void DicomUserConnection::SetPreferredTransferSyntax(const std::string& preferredTransferSyntax) - { - if (preferredTransferSyntax_ != preferredTransferSyntax) - { - Close(); - preferredTransferSyntax_ = preferredTransferSyntax; - } - } - - - void DicomUserConnection::SetRemoteHost(const std::string& host) - { - if (remoteHost_ != host) - { - if (host.size() > HOST_NAME_MAX - 10) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Invalid host name (too long): " + host); - } - - Close(); - remoteHost_ = host; - } - } - - void DicomUserConnection::SetRemotePort(uint16_t port) - { - if (remotePort_ != port) - { - Close(); - remotePort_ = port; - } - } - - void DicomUserConnection::OpenInternal(Mode mode) - { - if (IsOpen()) - { - // Don't reopen the connection - return; - } - - LOG(INFO) << "Opening a DICOM SCU connection from AET \"" << GetLocalApplicationEntityTitle() - << "\" to AET \"" << GetRemoteApplicationEntityTitle() << "\" on host " - << GetRemoteHost() << ":" << GetRemotePort() - << " (manufacturer: " << EnumerationToString(GetRemoteManufacturer()) << ")"; - - Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ pimpl_->acseTimeout_, &pimpl_->net_), remoteAet_, "connecting"); - Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU), remoteAet_, "connecting"); - - // Set this application's title and the called application's title in the params - Check(ASC_setAPTitles(pimpl_->params_, localAet_.c_str(), remoteAet_.c_str(), NULL), - remoteAet_, "connecting"); - - // Set the network addresses of the local and remote entities - char localHost[HOST_NAME_MAX]; - gethostname(localHost, HOST_NAME_MAX - 1); - - char remoteHostAndPort[HOST_NAME_MAX]; - -#ifdef _MSC_VER - _snprintf -#else - snprintf -#endif - (remoteHostAndPort, HOST_NAME_MAX - 1, "%s:%d", remoteHost_.c_str(), remotePort_); - - Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, remoteHostAndPort), - remoteAet_, "connecting"); - - // Set various options - Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false), - remoteAet_, "connecting"); - - SetupPresentationContexts(mode, preferredTransferSyntax_); - - // Do the association - Check(ASC_requestAssociation(pimpl_->net_, pimpl_->params_, &pimpl_->assoc_), - remoteAet_, "connecting"); - - if (ASC_countAcceptedPresentationContexts(pimpl_->params_) == 0) - { - throw OrthancException(ErrorCode_NoPresentationContext, - "Unable to negotiate a presentation context with AET " + - remoteAet_); - } - } - - void DicomUserConnection::Close() - { - if (pimpl_->assoc_ != NULL) - { - ASC_releaseAssociation(pimpl_->assoc_); - ASC_destroyAssociation(&pimpl_->assoc_); - pimpl_->assoc_ = NULL; - pimpl_->params_ = NULL; - } - else - { - if (pimpl_->params_ != NULL) - { - ASC_destroyAssociationParameters(&pimpl_->params_); - pimpl_->params_ = NULL; - } - } - - if (pimpl_->net_ != NULL) - { - ASC_dropNetwork(&pimpl_->net_); - pimpl_->net_ = NULL; - } - } - - bool DicomUserConnection::IsOpen() const - { - return pimpl_->IsOpen(); - } - - void DicomUserConnection::Store(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - const void* buffer, - size_t size, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID) - { - // Prepare an input stream for the memory buffer - DcmInputBufferStream is; - if (size > 0) - is.setBuffer(buffer, size); - is.setEos(); - - pimpl_->Store(sopClassUid, sopInstanceUid, is, *this, moveOriginatorAET, moveOriginatorID); - } - - void DicomUserConnection::Store(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - const std::string& buffer, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID) - { - if (buffer.size() > 0) - Store(sopClassUid, sopInstanceUid, &buffer[0], buffer.size(), - moveOriginatorAET, moveOriginatorID); - else - Store(sopClassUid, sopInstanceUid, NULL, 0, moveOriginatorAET, moveOriginatorID); - } - - void DicomUserConnection::StoreFile(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - const std::string& path, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID) - { - // Prepare an input stream for the file - DcmInputFileStream is(path.c_str()); - pimpl_->Store(sopClassUid, sopInstanceUid, is, *this, moveOriginatorAET, moveOriginatorID); - } - - bool DicomUserConnection::Echo() - { - CheckIsOpen(); - DIC_US status; - Check(DIMSE_echoUser(pimpl_->assoc_, pimpl_->assoc_->nextMsgID++, - /*opt_blockMode*/ (pimpl_->dimseTimeout_ ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), - /*opt_dimse_timeout*/ pimpl_->dimseTimeout_, - &status, NULL), remoteAet_, "C-ECHO"); - return status == STATUS_Success; - } - - - static void TestAndCopyTag(DicomMap& result, - const DicomMap& source, - const DicomTag& tag) - { - if (!source.HasTag(tag)) - { - throw OrthancException(ErrorCode_BadRequest); - } - else - { - result.SetValue(tag, source.GetValue(tag)); - } - } - - - void DicomUserConnection::Move(const std::string& targetAet, - ResourceType level, - const DicomMap& findResult) - { - DicomMap move; - switch (level) - { - case ResourceType_Patient: - TestAndCopyTag(move, findResult, DICOM_TAG_PATIENT_ID); - break; - - case ResourceType_Study: - TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); - break; - - case ResourceType_Series: - TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); - TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID); - break; - - case ResourceType_Instance: - TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); - TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID); - TestAndCopyTag(move, findResult, DICOM_TAG_SOP_INSTANCE_UID); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - MoveInternal(targetAet, level, move); - } - - - void DicomUserConnection::Move(const std::string& targetAet, - const DicomMap& findResult) - { - if (!findResult.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) - { - throw OrthancException(ErrorCode_InternalError); - } - - const std::string tmp = findResult.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).GetContent(); - ResourceType level = StringToResourceType(tmp.c_str()); - - Move(targetAet, level, findResult); - } - - - void DicomUserConnection::MovePatient(const std::string& targetAet, - const std::string& patientId) - { - DicomMap query; - query.SetValue(DICOM_TAG_PATIENT_ID, patientId, false); - MoveInternal(targetAet, ResourceType_Patient, query); - } - - void DicomUserConnection::MoveStudy(const std::string& targetAet, - const std::string& studyUid) - { - DicomMap query; - query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); - MoveInternal(targetAet, ResourceType_Study, query); - } - - void DicomUserConnection::MoveSeries(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid) - { - DicomMap query; - query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); - query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false); - MoveInternal(targetAet, ResourceType_Series, query); - } - - void DicomUserConnection::MoveInstance(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid, - const std::string& instanceUid) - { - DicomMap query; - query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); - query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false); - query.SetValue(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, false); - MoveInternal(targetAet, ResourceType_Instance, query); - } - - - void DicomUserConnection::SetTimeout(uint32_t seconds) - { - if (seconds == 0) - { - DisableTimeout(); - } - else - { - dcmConnectionTimeout.set(seconds); - pimpl_->dimseTimeout_ = seconds; - pimpl_->acseTimeout_ = seconds; // Timeout used during association negociation and ASC_releaseAssociation() - } - } - - - void DicomUserConnection::DisableTimeout() - { - /** - * Global timeout (seconds) for connecting to remote hosts. - * Default value is -1 which selects infinite timeout, i.e. blocking connect(). - */ - dcmConnectionTimeout.set(-1); - pimpl_->dimseTimeout_ = 0; - pimpl_->acseTimeout_ = 10; // Timeout used during association negociation and ASC_releaseAssociation() - } - - - void DicomUserConnection::CheckStorageSOPClassesInvariant() const - { - assert(storageSOPClasses_.size() + - defaultStorageSOPClasses_.size() + - reservedStorageSOPClasses_.size() <= MAXIMUM_STORAGE_SOP_CLASSES); - } - - void DicomUserConnection::AddStorageSOPClass(const char* sop) - { - CheckStorageSOPClassesInvariant(); - - if (storageSOPClasses_.find(sop) != storageSOPClasses_.end()) - { - // This storage SOP class is already explicitly registered. Do - // nothing. - return; - } - - if (defaultStorageSOPClasses_.find(sop) != defaultStorageSOPClasses_.end()) - { - // This storage SOP class is not explicitly registered, but is - // used by default. Just register it explicitly. - defaultStorageSOPClasses_.erase(sop); - storageSOPClasses_.insert(sop); - - CheckStorageSOPClassesInvariant(); - return; - } - - // This storage SOP class is neither explicitly, nor implicitly - // registered. Close the connection and register it explicitly. - - Close(); - - if (reservedStorageSOPClasses_.size() + - storageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES) // (*) - { - // The maximum number of SOP classes is reached - ResetStorageSOPClasses(); - defaultStorageSOPClasses_.erase(sop); - } - else if (reservedStorageSOPClasses_.size() + storageSOPClasses_.size() + - defaultStorageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES) - { - // Make room in the default storage syntaxes - assert(!defaultStorageSOPClasses_.empty()); // Necessarily true because condition (*) is false - defaultStorageSOPClasses_.erase(*defaultStorageSOPClasses_.rbegin()); - } - - // Explicitly register the new storage syntax - storageSOPClasses_.insert(sop); - - CheckStorageSOPClassesInvariant(); - } - - - void DicomUserConnection::FindWorklist(DicomFindAnswers& result, - ParsedDicomFile& query) - { - CheckIsOpen(); - - DcmDataset* dataset = query.GetDcmtkObject().getDataset(); - const char* sopClass = UID_FINDModalityWorklistInformationModel; - - ExecuteFind(result, pimpl_->assoc_, dataset, sopClass, true, - NULL, pimpl_->dimseTimeout_, remoteAet_); - } - - - void DicomUserConnection::SetDefaultTimeout(uint32_t seconds) - { - LOG(INFO) << "Default timeout for DICOM connections if Orthanc acts as SCU (client): " - << seconds << " seconds (0 = no timeout)"; - defaultTimeout_ = seconds; - } - - - bool DicomUserConnection::IsSameAssociation(const std::string& localAet, - const RemoteModalityParameters& remote) const - { - return (localAet_ == localAet && - remoteAet_ == remote.GetApplicationEntityTitle() && - remoteHost_ == remote.GetHost() && - remotePort_ == remote.GetPortNumber() && - manufacturer_ == remote.GetManufacturer()); - } - - - static void FillSopSequence(DcmDataset& dataset, - const DcmTagKey& tag, - const std::vector& sopClassUids, - const std::vector& sopInstanceUids, - const std::vector& failureReasons, - bool hasFailureReasons) - { - assert(sopClassUids.size() == sopInstanceUids.size() && - (hasFailureReasons ? - failureReasons.size() == sopClassUids.size() : - failureReasons.empty())); - - if (sopInstanceUids.empty()) - { - // Add an empty sequence - if (!dataset.insertEmptyElement(tag).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - } - else - { - for (size_t i = 0; i < sopClassUids.size(); i++) - { - std::unique_ptr item(new DcmItem); - if (!item->putAndInsertString(DCM_ReferencedSOPClassUID, sopClassUids[i].c_str()).good() || - !item->putAndInsertString(DCM_ReferencedSOPInstanceUID, sopInstanceUids[i].c_str()).good() || - (hasFailureReasons && - !item->putAndInsertUint16(DCM_FailureReason, failureReasons[i]).good()) || - !dataset.insertSequenceItem(tag, item.release()).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - } - } - } - - - - - void DicomUserConnection::ReportStorageCommitment( - const std::string& transactionUid, - const std::vector& sopClassUids, - const std::vector& sopInstanceUids, - const std::vector& failureReasons) - { - if (sopClassUids.size() != sopInstanceUids.size() || - sopClassUids.size() != failureReasons.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - if (IsOpen()) - { - Close(); - } - - std::vector successSopClassUids, successSopInstanceUids, failedSopClassUids, failedSopInstanceUids; - std::vector failedReasons; - - successSopClassUids.reserve(sopClassUids.size()); - successSopInstanceUids.reserve(sopClassUids.size()); - failedSopClassUids.reserve(sopClassUids.size()); - failedSopInstanceUids.reserve(sopClassUids.size()); - failedReasons.reserve(sopClassUids.size()); - - for (size_t i = 0; i < sopClassUids.size(); i++) - { - switch (failureReasons[i]) - { - case StorageCommitmentFailureReason_Success: - successSopClassUids.push_back(sopClassUids[i]); - successSopInstanceUids.push_back(sopInstanceUids[i]); - break; - - case StorageCommitmentFailureReason_ProcessingFailure: - case StorageCommitmentFailureReason_NoSuchObjectInstance: - case StorageCommitmentFailureReason_ResourceLimitation: - case StorageCommitmentFailureReason_ReferencedSOPClassNotSupported: - case StorageCommitmentFailureReason_ClassInstanceConflict: - case StorageCommitmentFailureReason_DuplicateTransactionUID: - failedSopClassUids.push_back(sopClassUids[i]); - failedSopInstanceUids.push_back(sopInstanceUids[i]); - failedReasons.push_back(failureReasons[i]); - break; - - default: - { - char buf[16]; - sprintf(buf, "%04xH", failureReasons[i]); - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Unsupported failure reason for storage commitment: " + std::string(buf)); - } - } - } - - try - { - OpenInternal(Mode_ReportStorageCommitment); - - /** - * N-EVENT-REPORT - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-1 - * - * Status code: - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.1.1.8 - **/ - - /** - * Send the "EVENT_REPORT_RQ" request - **/ - - LOG(INFO) << "Reporting modality \"" << remoteAet_ - << "\" about storage commitment transaction: " << transactionUid - << " (" << successSopClassUids.size() << " successes, " - << failedSopClassUids.size() << " failures)"; - const DIC_US messageId = pimpl_->assoc_->nextMsgID++; - - { - T_DIMSE_Message message; - memset(&message, 0, sizeof(message)); - message.CommandField = DIMSE_N_EVENT_REPORT_RQ; - - T_DIMSE_N_EventReportRQ& content = message.msg.NEventReportRQ; - content.MessageID = messageId; - strncpy(content.AffectedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); - strncpy(content.AffectedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); - content.DataSetType = DIMSE_DATASET_PRESENT; - - DcmDataset dataset; - if (!dataset.putAndInsertString(DCM_TransactionUID, transactionUid.c_str()).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - - { - std::vector empty; - FillSopSequence(dataset, DCM_ReferencedSOPSequence, successSopClassUids, - successSopInstanceUids, empty, false); - } - - // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html - if (failedSopClassUids.empty()) - { - content.EventTypeID = 1; // "Storage Commitment Request Successful" - } - else - { - content.EventTypeID = 2; // "Storage Commitment Request Complete - Failures Exist" - - // Failure reason - // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part03/sect_C.14.html#sect_C.14.1.1 - FillSopSequence(dataset, DCM_FailedSOPSequence, failedSopClassUids, - failedSopInstanceUids, failedReasons, true); - } - - int presID = ASC_findAcceptedPresentationContextID( - pimpl_->assoc_, UID_StorageCommitmentPushModelSOPClass); - if (presID == 0) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "Unable to send N-EVENT-REPORT request to AET: " + remoteAet_); - } - - if (!DIMSE_sendMessageUsingMemoryData( - pimpl_->assoc_, presID, &message, NULL /* status detail */, - &dataset, NULL /* callback */, NULL /* callback context */, - NULL /* commandSet */).good()) - { - throw OrthancException(ErrorCode_NetworkProtocol); - } - } - - /** - * Read the "EVENT_REPORT_RSP" response - **/ - - { - T_ASC_PresentationContextID presID = 0; - T_DIMSE_Message message; - - const int timeout = pimpl_->dimseTimeout_; - if (!DIMSE_receiveCommand(pimpl_->assoc_, - (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout, - &presID, &message, NULL /* no statusDetail */).good() || - message.CommandField != DIMSE_N_EVENT_REPORT_RSP) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "Unable to read N-EVENT-REPORT response from AET: " + remoteAet_); - } - - const T_DIMSE_N_EventReportRSP& content = message.msg.NEventReportRSP; - if (content.MessageIDBeingRespondedTo != messageId || - !(content.opts & O_NEVENTREPORT_AFFECTEDSOPCLASSUID) || - !(content.opts & O_NEVENTREPORT_AFFECTEDSOPINSTANCEUID) || - //(content.opts & O_NEVENTREPORT_EVENTTYPEID) || // Pedantic test - The "content.EventTypeID" is not used by Orthanc - std::string(content.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || - std::string(content.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance || - content.DataSetType != DIMSE_DATASET_NULL) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "Badly formatted N-EVENT-REPORT response from AET: " + remoteAet_); - } - - if (content.DimseStatus != 0 /* success */) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "The request cannot be handled by remote AET: " + remoteAet_); - } - } - - Close(); - } - catch (OrthancException&) - { - Close(); - throw; - } - } - - - - void DicomUserConnection::RequestStorageCommitment( - const std::string& transactionUid, - const std::vector& sopClassUids, - const std::vector& sopInstanceUids) - { - if (sopClassUids.size() != sopInstanceUids.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - for (size_t i = 0; i < sopClassUids.size(); i++) - { - if (sopClassUids[i].empty() || - sopInstanceUids[i].empty()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "The SOP class/instance UIDs cannot be empty, found: \"" + - sopClassUids[i] + "\" / \"" + sopInstanceUids[i] + "\""); - } - } - - if (transactionUid.size() < 5 || - transactionUid.substr(0, 5) != "2.25.") - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - if (IsOpen()) - { - Close(); - } - - try - { - OpenInternal(Mode_RequestStorageCommitment); - - /** - * N-ACTION - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-4 - * - * Status code: - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.1.1.8 - **/ - - /** - * Send the "N_ACTION_RQ" request - **/ - - LOG(INFO) << "Request to modality \"" << remoteAet_ - << "\" about storage commitment for " << sopClassUids.size() - << " instances, with transaction UID: " << transactionUid; - const DIC_US messageId = pimpl_->assoc_->nextMsgID++; - - { - T_DIMSE_Message message; - memset(&message, 0, sizeof(message)); - message.CommandField = DIMSE_N_ACTION_RQ; - - T_DIMSE_N_ActionRQ& content = message.msg.NActionRQ; - content.MessageID = messageId; - strncpy(content.RequestedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); - strncpy(content.RequestedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); - content.ActionTypeID = 1; // "Request Storage Commitment" - content.DataSetType = DIMSE_DATASET_PRESENT; - - DcmDataset dataset; - if (!dataset.putAndInsertString(DCM_TransactionUID, transactionUid.c_str()).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - - { - std::vector empty; - FillSopSequence(dataset, DCM_ReferencedSOPSequence, sopClassUids, sopInstanceUids, empty, false); - } - - int presID = ASC_findAcceptedPresentationContextID( - pimpl_->assoc_, UID_StorageCommitmentPushModelSOPClass); - if (presID == 0) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "Unable to send N-ACTION request to AET: " + remoteAet_); - } - - if (!DIMSE_sendMessageUsingMemoryData( - pimpl_->assoc_, presID, &message, NULL /* status detail */, - &dataset, NULL /* callback */, NULL /* callback context */, - NULL /* commandSet */).good()) - { - throw OrthancException(ErrorCode_NetworkProtocol); - } - } - - /** - * Read the "N_ACTION_RSP" response - **/ - - { - T_ASC_PresentationContextID presID = 0; - T_DIMSE_Message message; - - const int timeout = pimpl_->dimseTimeout_; - if (!DIMSE_receiveCommand(pimpl_->assoc_, - (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout, - &presID, &message, NULL /* no statusDetail */).good() || - message.CommandField != DIMSE_N_ACTION_RSP) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "Unable to read N-ACTION response from AET: " + remoteAet_); - } - - const T_DIMSE_N_ActionRSP& content = message.msg.NActionRSP; - if (content.MessageIDBeingRespondedTo != messageId || - !(content.opts & O_NACTION_AFFECTEDSOPCLASSUID) || - !(content.opts & O_NACTION_AFFECTEDSOPINSTANCEUID) || - //(content.opts & O_NACTION_ACTIONTYPEID) || // Pedantic test - The "content.ActionTypeID" is not used by Orthanc - std::string(content.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || - std::string(content.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance || - content.DataSetType != DIMSE_DATASET_NULL) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "Badly formatted N-ACTION response from AET: " + remoteAet_); - } - - if (content.DimseStatus != 0 /* success */) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "The request cannot be handled by remote AET: " + remoteAet_); - } - } - - Close(); - } - catch (OrthancException&) - { - Close(); - throw; - } - } -} diff -r ff0718a4633a -r e1c2bc9a5cc1 Core/DicomNetworking/DicomUserConnection.h --- a/Core/DicomNetworking/DicomUserConnection.h Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,253 +0,0 @@ -/** - * 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 ORTHANC_ENABLE_DCMTK_NETWORKING != 1 -# error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1 -#endif - -#include "DicomFindAnswers.h" -#include "../Enumerations.h" -#include "RemoteModalityParameters.h" - -#include -#include -#include -#include - -namespace Orthanc -{ - class DicomUserConnection : public boost::noncopyable - { - private: - struct PImpl; - boost::shared_ptr pimpl_; - - enum Mode - { - Mode_Generic, - Mode_ReportStorageCommitment, - Mode_RequestStorageCommitment - }; - - // Connection parameters - std::string preferredTransferSyntax_; - std::string localAet_; - std::string remoteAet_; - std::string remoteHost_; - uint16_t remotePort_; - ModalityManufacturer manufacturer_; - std::set storageSOPClasses_; - std::list reservedStorageSOPClasses_; - std::set defaultStorageSOPClasses_; - - void CheckIsOpen() const; - - void SetupPresentationContexts(Mode mode, - const std::string& preferredTransferSyntax); - - void MoveInternal(const std::string& targetAet, - ResourceType level, - const DicomMap& fields); - - void ResetStorageSOPClasses(); - - void CheckStorageSOPClassesInvariant() const; - - void DefaultSetup(); - - void OpenInternal(Mode mode); - - public: - DicomUserConnection(); - - ~DicomUserConnection(); - - // This constructor corresponds to behavior of the old class - // "ReusableDicomUserConnection", without the call to "Open()" - DicomUserConnection(const std::string& localAet, - const RemoteModalityParameters& remote); - - void SetRemoteModality(const RemoteModalityParameters& parameters); - - void SetLocalApplicationEntityTitle(const std::string& aet); - - const std::string& GetLocalApplicationEntityTitle() const - { - return localAet_; - } - - void SetRemoteApplicationEntityTitle(const std::string& aet); - - const std::string& GetRemoteApplicationEntityTitle() const - { - return remoteAet_; - } - - void SetRemoteHost(const std::string& host); - - const std::string& GetRemoteHost() const - { - return remoteHost_; - } - - void SetRemotePort(uint16_t port); - - uint16_t GetRemotePort() const - { - return remotePort_; - } - - void SetRemoteManufacturer(ModalityManufacturer manufacturer); - - ModalityManufacturer GetRemoteManufacturer() const - { - return manufacturer_; - } - - void ResetPreferredTransferSyntax(); - - void SetPreferredTransferSyntax(const std::string& preferredTransferSyntax); - - const std::string& GetPreferredTransferSyntax() const - { - return preferredTransferSyntax_; - } - - void AddStorageSOPClass(const char* sop); - - void Open() - { - OpenInternal(Mode_Generic); - } - - void Close(); - - bool IsOpen() const; - - bool Echo(); - - void Store(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - const void* buffer, - size_t size, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID); - - void Store(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - const void* buffer, - size_t size) - { - Store(sopClassUid, sopInstanceUid, buffer, size, "", 0); // Not a C-Move - } - - void Store(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - const std::string& buffer, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID); - - void Store(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - const std::string& buffer) - { - Store(sopClassUid, sopInstanceUid, buffer, "", 0); // Not a C-Move - } - - void StoreFile(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - const std::string& path, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID); - - void StoreFile(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - const std::string& path) - { - StoreFile(sopClassUid, sopInstanceUid, path, "", 0); // Not a C-Move - } - - void Find(DicomFindAnswers& result, - ResourceType level, - const DicomMap& fields, - bool normalize); // Whether to normalize the DICOM query - - void Move(const std::string& targetAet, - ResourceType level, - const DicomMap& findResult); - - void Move(const std::string& targetAet, - const DicomMap& findResult); - - void MovePatient(const std::string& targetAet, - const std::string& patientId); - - void MoveStudy(const std::string& targetAet, - const std::string& studyUid); - - void MoveSeries(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid); - - void MoveInstance(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid, - const std::string& instanceUid); - - void SetTimeout(uint32_t seconds); - - void DisableTimeout(); - - void FindWorklist(DicomFindAnswers& result, - ParsedDicomFile& query); - - static void SetDefaultTimeout(uint32_t seconds); - - bool IsSameAssociation(const std::string& localAet, - const RemoteModalityParameters& remote) const; - - void ReportStorageCommitment( - const std::string& transactionUid, - const std::vector& sopClassUids, - const std::vector& sopInstanceUids, - const std::vector& failureReasons); - - // transactionUid: To be generated by Toolbox::GenerateDicomPrivateUniqueIdentifier() - void RequestStorageCommitment( - const std::string& transactionUid, - const std::vector& sopClassUids, - const std::vector& sopInstanceUids); - }; -} diff -r ff0718a4633a -r e1c2bc9a5cc1 Core/DicomNetworking/TimeoutDicomConnectionManager.cpp --- a/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp Mon Apr 27 18:33:33 2020 +0200 +++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp Mon Apr 27 18:34:20 2020 +0200 @@ -62,7 +62,7 @@ } - DicomUserConnection& TimeoutDicomConnectionManager::Lock::GetConnection() + DicomStoreUserConnection& TimeoutDicomConnectionManager::Lock::GetConnection() { if (that_.connection_.get() == NULL) { @@ -87,10 +87,12 @@ void TimeoutDicomConnectionManager::OpenInternal(const std::string& localAet, const RemoteModalityParameters& remote) { + DicomAssociationParameters other(localAet, remote); + if (connection_.get() == NULL || - !connection_->IsSameAssociation(localAet, remote)) + !connection_->GetParameters().IsEqual(other)) { - connection_.reset(new DicomUserConnection(localAet, remote)); + connection_.reset(new DicomStoreUserConnection(other)); } } @@ -101,7 +103,7 @@ if (connection_.get() != NULL) { LOG(INFO) << "Closing inactive DICOM association with modality: " - << connection_->GetRemoteApplicationEntityTitle(); + << connection_->GetParameters().GetRemoteApplicationEntityTitle(); connection_.reset(NULL); } diff -r ff0718a4633a -r e1c2bc9a5cc1 Core/DicomNetworking/TimeoutDicomConnectionManager.h --- a/Core/DicomNetworking/TimeoutDicomConnectionManager.h Mon Apr 27 18:33:33 2020 +0200 +++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.h Mon Apr 27 18:34:20 2020 +0200 @@ -43,7 +43,7 @@ #include "../Compatibility.h" -#include "DicomUserConnection.h" +#include "DicomStoreUserConnection.h" #include #include @@ -56,10 +56,10 @@ class TimeoutDicomConnectionManager : public boost::noncopyable { private: - boost::mutex mutex_; - std::unique_ptr connection_; - boost::posix_time::ptime lastUse_; - boost::posix_time::time_duration timeout_; + boost::mutex mutex_; + std::unique_ptr connection_; + boost::posix_time::ptime lastUse_; + boost::posix_time::time_duration timeout_; // Mutex must be locked void TouchInternal(); @@ -85,7 +85,7 @@ ~Lock(); - DicomUserConnection& GetConnection(); + DicomStoreUserConnection& GetConnection(); }; TimeoutDicomConnectionManager() : diff -r ff0718a4633a -r e1c2bc9a5cc1 Core/DicomParsing/FromDcmtkBridge.cpp --- a/Core/DicomParsing/FromDcmtkBridge.cpp Mon Apr 27 18:33:33 2020 +0200 +++ b/Core/DicomParsing/FromDcmtkBridge.cpp Mon Apr 27 18:34:20 2020 +0200 @@ -121,6 +121,13 @@ #endif +#include +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 +# include +# include // include to support color images +#endif + + namespace Orthanc { static bool IsBinaryTag(const DcmTag& key) @@ -1198,51 +1205,30 @@ } } - bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer, - DcmDataset& dataSet) + + + static bool SaveToMemoryBufferInternal(std::string& buffer, + DcmFileFormat& dicom, + E_TransferSyntax xfer) { - // 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; - // 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); // (*) + const uint32_t estimatedSize = dicom.calcElementLength(xfer, encodingType); // (*) buffer.resize(estimatedSize); } DcmOutputBufferStream ob(&buffer[0], buffer.size()); // 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(); + dicom.transferInit(); + OFCondition c = dicom.write(ob, xfer, encodingType, NULL, + /*opt_groupLength*/ EGL_recalcGL, + /*opt_paddingType*/ EPD_noChange, + /*padlen*/ 0, /*subPadlen*/ 0, /*instanceLength*/ 0, + EWM_updateMeta /* creates new SOP instance UID on lossy */); + dicom.transferEnd(); if (c.good()) { @@ -1265,6 +1251,86 @@ return false; } } + + + bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer, + DcmDataset& dataSet) + { + // Determine the transfer syntax which shall be used to write the + // information to the file. If not possible, 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; + } + + // Create the meta-header information + DcmFileFormat ff(&dataSet); + ff.validateMetaInfo(xfer); + ff.removeInvalidGroups(); + + return SaveToMemoryBufferInternal(buffer, ff, xfer); + } + + + 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, + DicomTransferSyntax syntax, + const DcmRepresentationParameter* representation) + { + E_TransferSyntax xfer; + if (!LookupDcmtkTransferSyntax(xfer, syntax)) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + 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); + } + } ValueRepresentation FromDcmtkBridge::LookupValueRepresentation(const DicomTag& tag) @@ -2073,6 +2139,12 @@ DJEncoderRegistration::registerCodecs(); # endif #endif + + LOG(INFO) << "Registering RLE codecs in DCMTK"; + DcmRLEDecoderRegistration::registerCodecs(); +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + DcmRLEEncoderRegistration::registerCodecs(); +#endif } @@ -2093,6 +2165,11 @@ DJEncoderRegistration::cleanup(); # endif #endif + + DcmRLEDecoderRegistration::cleanup(); +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + DcmRLEEncoderRegistration::cleanup(); +#endif } diff -r ff0718a4633a -r e1c2bc9a5cc1 Core/DicomParsing/FromDcmtkBridge.h --- a/Core/DicomParsing/FromDcmtkBridge.h Mon Apr 27 18:33:33 2020 +0200 +++ b/Core/DicomParsing/FromDcmtkBridge.h Mon Apr 27 18:34:20 2020 +0200 @@ -205,6 +205,14 @@ static bool SaveToMemoryBuffer(std::string& buffer, DcmDataset& dataSet); + static bool SaveToMemoryBuffer(std::string& buffer, + DcmFileFormat& dicom); + + static bool Transcode(std::string& buffer, + DcmFileFormat& dicom, + DicomTransferSyntax syntax, + const DcmRepresentationParameter* representation); + static ValueRepresentation Convert(DcmEVR vr); static ValueRepresentation LookupValueRepresentation(const DicomTag& tag); diff -r ff0718a4633a -r e1c2bc9a5cc1 Core/Enumerations.h --- a/Core/Enumerations.h Mon Apr 27 18:33:33 2020 +0200 +++ b/Core/Enumerations.h Mon Apr 27 18:34:20 2020 +0200 @@ -753,7 +753,7 @@ DicomAssociationRole_Scu, DicomAssociationRole_Scp }; - + /** * WARNING: Do not change the explicit values in the enumerations diff -r ff0718a4633a -r e1c2bc9a5cc1 OrthancServer/OrthancMoveRequestHandler.cpp --- a/OrthancServer/OrthancMoveRequestHandler.cpp Mon Apr 27 18:33:33 2020 +0200 +++ b/OrthancServer/OrthancMoveRequestHandler.cpp Mon Apr 27 18:34:20 2020 +0200 @@ -59,7 +59,7 @@ RemoteModalityParameters remote_; std::string originatorAet_; uint16_t originatorId_; - std::unique_ptr connection_; + std::unique_ptr connection_; public: SynchronousMove(ServerContext& context, @@ -113,11 +113,14 @@ if (connection_.get() == NULL) { - connection_.reset(new DicomUserConnection(localAet_, remote_)); + connection_.reset(new DicomStoreUserConnection(localAet_, remote_)); } std::string sopClassUid, sopInstanceUid; // Unused - connection_->Store(sopClassUid, sopInstanceUid, dicom, originatorAet_, originatorId_); + + const void* data = dicom.empty() ? NULL : dicom.c_str(); + connection_->Store(sopClassUid, sopInstanceUid, data, dicom.size(), + originatorAet_, originatorId_); return Status_Success; } diff -r ff0718a4633a -r e1c2bc9a5cc1 OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Mon Apr 27 18:33:33 2020 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Mon Apr 27 18:34:20 2020 +0200 @@ -227,7 +227,7 @@ toStore.SetParsedDicomFile(dicom); ServerContext& context = OrthancRestApi::GetContext(call); - StoreStatus status = context.Store(id, toStore); + StoreStatus status = context.Store(id, toStore, StoreInstanceMode_Default); if (status == StoreStatus_Failure) { diff -r ff0718a4633a -r e1c2bc9a5cc1 OrthancServer/OrthancRestApi/OrthancRestApi.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Mon Apr 27 18:33:33 2020 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Mon Apr 27 18:34:20 2020 +0200 @@ -140,7 +140,7 @@ } std::string publicId; - StoreStatus status = context.Store(publicId, toStore); + StoreStatus status = context.Store(publicId, toStore, StoreInstanceMode_Default); OrthancRestApi::GetApi(call).AnswerStoredInstance(call, toStore, status); } diff -r ff0718a4633a -r e1c2bc9a5cc1 OrthancServer/OrthancRestApi/OrthancRestModalities.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Mon Apr 27 18:33:33 2020 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Mon Apr 27 18:34:20 2020 +0200 @@ -981,8 +981,7 @@ RemoteModalityParameters remote = MyGetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - DicomUserConnection connection(localAet, remote); - connection.Open(); + DicomStoreUserConnection connection(localAet, remote); std::string sopClassUid, sopInstanceUid; connection.Store(sopClassUid, sopInstanceUid, diff -r ff0718a4633a -r e1c2bc9a5cc1 OrthancServer/ServerContext.cpp --- a/OrthancServer/ServerContext.cpp Mon Apr 27 18:33:33 2020 +0200 +++ b/OrthancServer/ServerContext.cpp Mon Apr 27 18:34:20 2020 +0200 @@ -242,7 +242,8 @@ isJobsEngineUnserialized_(false), metricsRegistry_(new MetricsRegistry), isHttpServerSecure_(true), - isExecuteLuaEnabled_(false) + isExecuteLuaEnabled_(false), + overwriteInstances_(false) { { OrthancConfiguration::ReaderLock lock; @@ -339,8 +340,28 @@ StoreStatus ServerContext::Store(std::string& resultPublicId, - DicomInstanceToStore& dicom) + DicomInstanceToStore& dicom, + StoreInstanceMode mode) { + bool overwrite; + switch (mode) + { + case StoreInstanceMode_Default: + overwrite = overwriteInstances_; + break; + + case StoreInstanceMode_OverwriteDuplicate: + overwrite = true; + break; + + case StoreInstanceMode_IgnoreDuplicate: + overwrite = false; + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + try { MetricsRegistry::Timer timer(GetMetricsRegistry(), "orthanc_store_dicom_duration_ms"); @@ -404,7 +425,8 @@ typedef std::map InstanceMetadata; InstanceMetadata instanceMetadata; - StoreStatus status = index_.Store(instanceMetadata, dicom, attachments); + StoreStatus status = index_.Store( + instanceMetadata, dicom, attachments, overwrite); // Only keep the metadata for the "instance" level dicom.GetMetadata().clear(); diff -r ff0718a4633a -r e1c2bc9a5cc1 OrthancServer/ServerContext.h --- a/OrthancServer/ServerContext.h Mon Apr 27 18:33:33 2020 +0200 +++ b/OrthancServer/ServerContext.h Mon Apr 27 18:34:20 2020 +0200 @@ -221,6 +221,7 @@ std::unique_ptr metricsRegistry_; bool isHttpServerSecure_; bool isExecuteLuaEnabled_; + bool overwriteInstances_; std::unique_ptr storageCommitmentReports_; @@ -275,7 +276,8 @@ size_t size); StoreStatus Store(std::string& resultPublicId, - DicomInstanceToStore& dicom); + DicomInstanceToStore& dicom, + StoreInstanceMode mode); void AnswerAttachment(RestApiOutput& output, const std::string& resourceId, @@ -426,6 +428,16 @@ return isExecuteLuaEnabled_; } + void SetOverwriteInstances(bool overwrite) + { + overwriteInstances_ = overwrite; + } + + bool IsOverwriteInstances() const + { + return overwriteInstances_; + } + virtual IStorageCommitmentFactory::ILookupHandler* CreateStorageCommitment(const std::string& jobId, const std::string& transactionUid, diff -r ff0718a4633a -r e1c2bc9a5cc1 OrthancServer/ServerEnumerations.h --- a/OrthancServer/ServerEnumerations.h Mon Apr 27 18:33:33 2020 +0200 +++ b/OrthancServer/ServerEnumerations.h Mon Apr 27 18:34:20 2020 +0200 @@ -90,6 +90,13 @@ FindStorageAccessMode_DiskOnLookupAndAnswer }; + enum StoreInstanceMode + { + StoreInstanceMode_Default, + StoreInstanceMode_OverwriteDuplicate, + StoreInstanceMode_IgnoreDuplicate + }; + /** * WARNING: Do not change the explicit values in the enumerations diff -r ff0718a4633a -r e1c2bc9a5cc1 OrthancServer/ServerIndex.cpp --- a/OrthancServer/ServerIndex.cpp Mon Apr 27 18:33:33 2020 +0200 +++ b/OrthancServer/ServerIndex.cpp Mon Apr 27 18:34:20 2020 +0200 @@ -675,7 +675,6 @@ db_(db), maximumStorageSize_(0), maximumPatients_(0), - overwrite_(false), mainDicomTagsRegistry_(new MainDicomTagsRegistry) { listener_.reset(new Listener(context)); @@ -753,7 +752,8 @@ StoreStatus ServerIndex::Store(std::map& instanceMetadata, DicomInstanceToStore& instanceToStore, - const Attachments& attachments) + const Attachments& attachments, + bool overwrite) { boost::mutex::scoped_lock lock(mutex_); @@ -784,7 +784,7 @@ { // The instance already exists - if (overwrite_) + if (overwrite) { // Overwrite the old instance LOG(INFO) << "Overwriting instance: " << hashInstance; @@ -1660,12 +1660,6 @@ StandaloneRecycling(); } - void ServerIndex::SetOverwriteInstances(bool overwrite) - { - boost::mutex::scoped_lock lock(mutex_); - overwrite_ = overwrite; - } - void ServerIndex::StandaloneRecycling() { diff -r ff0718a4633a -r e1c2bc9a5cc1 OrthancServer/ServerIndex.h --- a/OrthancServer/ServerIndex.h Mon Apr 27 18:33:33 2020 +0200 +++ b/OrthancServer/ServerIndex.h Mon Apr 27 18:34:20 2020 +0200 @@ -71,7 +71,6 @@ uint64_t maximumStorageSize_; unsigned int maximumPatients_; - bool overwrite_; std::unique_ptr mainDicomTagsRegistry_; static void FlushThread(ServerIndex* that, @@ -139,11 +138,10 @@ // "count == 0" means no limit on the number of patients void SetMaximumPatientCount(unsigned int count); - void SetOverwriteInstances(bool overwrite); - StoreStatus Store(std::map& instanceMetadata, DicomInstanceToStore& instance, - const Attachments& attachments); + const Attachments& attachments, + bool overwrite); void GetGlobalStatistics(/* out */ uint64_t& diskSize, /* out */ uint64_t& uncompressedSize, diff -r ff0718a4633a -r e1c2bc9a5cc1 OrthancServer/ServerJobs/DicomModalityStoreJob.cpp --- a/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp Mon Apr 27 18:33:33 2020 +0200 +++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp Mon Apr 27 18:34:20 2020 +0200 @@ -35,6 +35,7 @@ #include "DicomModalityStoreJob.h" #include "../../Core/Compatibility.h" +#include "../../Core/DicomNetworking/DicomAssociation.h" #include "../../Core/Logging.h" #include "../../Core/SerializationToolbox.h" #include "../ServerContext.h" @@ -47,7 +48,7 @@ { if (connection_.get() == NULL) { - connection_.reset(new DicomUserConnection(localAet_, remote_)); + connection_.reset(new DicomStoreUserConnection(localAet_, remote_)); } } @@ -74,13 +75,16 @@ std::string sopClassUid, sopInstanceUid; + const void* data = dicom.empty() ? NULL : dicom.c_str(); + if (HasMoveOriginator()) { - connection_->Store(sopClassUid, sopInstanceUid, dicom, moveOriginatorAet_, moveOriginatorId_); + connection_->Store(sopClassUid, sopInstanceUid, data, dicom.size(), + moveOriginatorAet_, moveOriginatorId_); } else { - connection_->Store(sopClassUid, sopInstanceUid, dicom); + connection_->Store(sopClassUid, sopInstanceUid, data, dicom.size()); } if (storageCommitment_) @@ -96,6 +100,9 @@ if (sopClassUids_.size() == GetInstancesCount()) { + assert(IsStarted()); + connection_.reset(NULL); + const std::string& remoteAet = remote_.GetApplicationEntityTitle(); LOG(INFO) << "Sending storage commitment request to modality: " << remoteAet; @@ -105,12 +112,11 @@ context_.GetStorageCommitmentReports().Store( transactionUid_, new StorageCommitmentReports::Report(remoteAet)); - assert(IsStarted()); - OpenConnection(); - std::vector a(sopClassUids_.begin(), sopClassUids_.end()); std::vector b(sopInstanceUids_.begin(), sopInstanceUids_.end()); - connection_->RequestStorageCommitment(transactionUid_, a, b); + + DicomAssociationParameters parameters(localAet_, remote_); + DicomAssociation::RequestStorageCommitment(parameters, transactionUid_, a, b); } } diff -r ff0718a4633a -r e1c2bc9a5cc1 OrthancServer/ServerJobs/DicomModalityStoreJob.h --- a/OrthancServer/ServerJobs/DicomModalityStoreJob.h Mon Apr 27 18:33:33 2020 +0200 +++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.h Mon Apr 27 18:34:20 2020 +0200 @@ -35,7 +35,9 @@ #include "../../Core/Compatibility.h" #include "../../Core/JobsEngine/SetOfInstancesJob.h" -#include "../../Core/DicomNetworking/DicomUserConnection.h" +#include "../../Core/DicomNetworking/DicomStoreUserConnection.h" + +#include namespace Orthanc { @@ -44,13 +46,13 @@ class DicomModalityStoreJob : public SetOfInstancesJob { private: - ServerContext& context_; - std::string localAet_; - RemoteModalityParameters remote_; - std::string moveOriginatorAet_; - uint16_t moveOriginatorId_; - std::unique_ptr connection_; - bool storageCommitment_; + ServerContext& context_; + std::string localAet_; + RemoteModalityParameters remote_; + std::string moveOriginatorAet_; + uint16_t moveOriginatorId_; + std::unique_ptr connection_; + bool storageCommitment_; // For storage commitment std::string transactionUid_; diff -r ff0718a4633a -r e1c2bc9a5cc1 OrthancServer/ServerJobs/MergeStudyJob.cpp --- a/OrthancServer/ServerJobs/MergeStudyJob.cpp Mon Apr 27 18:33:33 2020 +0200 +++ b/OrthancServer/ServerJobs/MergeStudyJob.cpp Mon Apr 27 18:34:20 2020 +0200 @@ -145,7 +145,8 @@ toStore.SetParsedDicomFile(*modified); std::string modifiedInstance; - if (context_.Store(modifiedInstance, toStore) != StoreStatus_Success) + if (context_.Store(modifiedInstance, toStore, + StoreInstanceMode_Default) != StoreStatus_Success) { LOG(ERROR) << "Error while storing a modified instance " << instance; return false; diff -r ff0718a4633a -r e1c2bc9a5cc1 OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp --- a/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp Mon Apr 27 18:33:33 2020 +0200 +++ b/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp Mon Apr 27 18:34:20 2020 +0200 @@ -113,7 +113,7 @@ toStore.AddMetadata(ResourceType_Instance, MetadataType_ModifiedFrom, instance.GetId()); std::string modifiedId; - context_.Store(modifiedId, toStore); + context_.Store(modifiedId, toStore, StoreInstanceMode_Default); // Only chain with other commands if this command succeeds outputs.Append(new DicomInstanceOperationValue(instance.GetServerContext(), modifiedId)); diff -r ff0718a4633a -r e1c2bc9a5cc1 OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp --- a/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp Mon Apr 27 18:33:33 2020 +0200 +++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp Mon Apr 27 18:34:20 2020 +0200 @@ -63,8 +63,10 @@ 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, dicom); + lock.GetConnection().Store(sopClassUid, sopInstanceUid, data, dicom.size()); } catch (OrthancException& e) { diff -r ff0718a4633a -r e1c2bc9a5cc1 OrthancServer/ServerJobs/ResourceModificationJob.cpp --- a/OrthancServer/ServerJobs/ResourceModificationJob.cpp Mon Apr 27 18:33:33 2020 +0200 +++ b/OrthancServer/ServerJobs/ResourceModificationJob.cpp Mon Apr 27 18:34:20 2020 +0200 @@ -211,7 +211,8 @@ **/ std::string modifiedInstance; - if (context_.Store(modifiedInstance, toStore) != StoreStatus_Success) + if (context_.Store(modifiedInstance, toStore, + StoreInstanceMode_Default) != StoreStatus_Success) { throw OrthancException(ErrorCode_CannotStoreInstance, "Error while storing a modified instance " + instance); diff -r ff0718a4633a -r e1c2bc9a5cc1 OrthancServer/ServerJobs/SplitStudyJob.cpp --- a/OrthancServer/ServerJobs/SplitStudyJob.cpp Mon Apr 27 18:33:33 2020 +0200 +++ b/OrthancServer/ServerJobs/SplitStudyJob.cpp Mon Apr 27 18:34:20 2020 +0200 @@ -138,7 +138,8 @@ toStore.SetParsedDicomFile(*modified); std::string modifiedInstance; - if (context_.Store(modifiedInstance, toStore) != StoreStatus_Success) + if (context_.Store(modifiedInstance, toStore, + StoreInstanceMode_Default) != StoreStatus_Success) { LOG(ERROR) << "Error while storing a modified instance " << instance; return false; diff -r ff0718a4633a -r e1c2bc9a5cc1 OrthancServer/main.cpp --- a/OrthancServer/main.cpp Mon Apr 27 18:33:33 2020 +0200 +++ b/OrthancServer/main.cpp Mon Apr 27 18:34:20 2020 +0200 @@ -88,7 +88,7 @@ toStore.SetJson(dicomJson); std::string id; - context_.Store(id, toStore); + context_.Store(id, toStore, StoreInstanceMode_Default); } } }; @@ -1289,7 +1289,6 @@ HttpClient::SetDefaultProxy(lock.GetConfiguration().GetStringParameter("HttpProxy", "")); - DicomUserConnection::SetDefaultTimeout(lock.GetConfiguration().GetUnsignedIntegerParameter("DicomScuTimeout", 10)); DicomAssociationParameters::SetDefaultTimeout(lock.GetConfiguration().GetUnsignedIntegerParameter("DicomScuTimeout", 10)); maxCompletedJobs = lock.GetConfiguration().GetUnsignedIntegerParameter("JobsHistorySize", 10); @@ -1309,7 +1308,7 @@ context.SetStoreMD5ForAttachments(lock.GetConfiguration().GetBooleanParameter("StoreMD5ForAttachments", true)); // New option in Orthanc 1.4.2 - context.GetIndex().SetOverwriteInstances(lock.GetConfiguration().GetBooleanParameter("OverwriteInstances", false)); + context.SetOverwriteInstances(lock.GetConfiguration().GetBooleanParameter("OverwriteInstances", false)); try { diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/CMake/DcmtkConfiguration.cmake --- a/Resources/CMake/DcmtkConfiguration.cmake Mon Apr 27 18:33:33 2020 +0200 +++ b/Resources/CMake/DcmtkConfiguration.cmake Mon Apr 27 18:34:20 2020 +0200 @@ -36,6 +36,14 @@ ) endif() + if (ENABLE_DCMTK_TRANSCODING) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmimgle/libsrc DCMTK_SOURCES) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmimage/libsrc DCMTK_SOURCES) + include_directories( + ${DCMTK_SOURCES_DIR}/dcmimage/include + ) + endif() + if (ENABLE_DCMTK_JPEG) AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc DCMTK_SOURCES) AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libijg8 DCMTK_SOURCES) @@ -56,17 +64,21 @@ ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg8/jaricom.c ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg12/jaricom.c ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg24/jaricom.c + ) - # Disable support for encoding JPEG (modification in Orthanc 1.0.1) - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djcodece.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsv1.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencbas.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencpro.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djenclol.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencode.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencext.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsps.cc - ) + if (NOT ENABLE_DCMTK_TRANSCODING) + list(REMOVE_ITEM DCMTK_SOURCES + # Disable support for encoding JPEG (modification in Orthanc 1.0.1) + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djcodece.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsv1.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencbas.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencpro.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djenclol.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencode.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencext.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsps.cc + ) + endif() endif() @@ -78,15 +90,18 @@ ${DCMTK_SOURCES_DIR}/dcmjpls/include ${DCMTK_SOURCES_DIR}/dcmjpls/libcharls ) - list(REMOVE_ITEM DCMTK_SOURCES - ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djcodece.cc - - # Disable support for encoding JPEG-LS (modification in Orthanc 1.0.1) - ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djencode.cc - ) list(APPEND DCMTK_SOURCES ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djrplol.cc ) + + if (NOT ENABLE_DCMTK_TRANSCODING) + list(REMOVE_ITEM DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djcodece.cc + + # Disable support for encoding JPEG-LS (modification in Orthanc 1.0.1) + ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djencode.cc + ) + endif() endif() diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/CMake/OrthancFrameworkConfiguration.cmake --- a/Resources/CMake/OrthancFrameworkConfiguration.cmake Mon Apr 27 18:33:33 2020 +0200 +++ b/Resources/CMake/OrthancFrameworkConfiguration.cmake Mon Apr 27 18:34:20 2020 +0200 @@ -484,10 +484,9 @@ ${ORTHANC_ROOT}/Core/DicomNetworking/DicomAssociation.cpp ${ORTHANC_ROOT}/Core/DicomNetworking/DicomAssociationParameters.cpp ${ORTHANC_ROOT}/Core/DicomNetworking/DicomControlUserConnection.cpp - ${ORTHANC_ROOT}/Core/DicomNetworking/DicomStoreUserConnection.cpp ${ORTHANC_ROOT}/Core/DicomNetworking/DicomFindAnswers.cpp ${ORTHANC_ROOT}/Core/DicomNetworking/DicomServer.cpp - ${ORTHANC_ROOT}/Core/DicomNetworking/DicomUserConnection.cpp + ${ORTHANC_ROOT}/Core/DicomNetworking/DicomStoreUserConnection.cpp ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/CommandDispatcher.cpp ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/FindScp.cpp ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/MoveScp.cpp diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/Graveyard/OldScheduler/CallSystemCommand.cpp --- a/Resources/Graveyard/OldScheduler/CallSystemCommand.cpp Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +0,0 @@ -/** - * 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 "../PrecompiledHeadersServer.h" -#include "CallSystemCommand.h" - -#include "../../Core/Logging.h" -#include "../../Core/Toolbox.h" -#include "../../Core/TemporaryFile.h" - -namespace Orthanc -{ - CallSystemCommand::CallSystemCommand(ServerContext& context, - const std::string& command, - const std::vector& arguments) : - context_(context), - command_(command), - arguments_(arguments) - { - } - - bool CallSystemCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Calling system command " << command_ << " on instance " << *it; - - try - { - std::string dicom; - context_.ReadDicom(dicom, *it); - - TemporaryFile tmp; - tmp.Write(dicom); - - std::vector args = arguments_; - args.push_back(tmp.GetPath()); - - SystemToolbox::ExecuteSystemCommand(command_, args); - - // Only chain with other commands if this command succeeds - outputs.push_back(*it); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Unable to call system command " << command_ - << " on instance " << *it << " in a Lua script: " << e.What(); - } - } - - return true; - } -} diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/Graveyard/OldScheduler/CallSystemCommand.h --- a/Resources/Graveyard/OldScheduler/CallSystemCommand.h Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -/** - * 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 "IServerCommand.h" -#include "../ServerContext.h" - -namespace Orthanc -{ - class CallSystemCommand : public IServerCommand - { - private: - ServerContext& context_; - std::string command_; - std::vector arguments_; - - public: - CallSystemCommand(ServerContext& context, - const std::string& command, - const std::vector& arguments); - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -} diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/Graveyard/OldScheduler/DeleteInstanceCommand.cpp --- a/Resources/Graveyard/OldScheduler/DeleteInstanceCommand.cpp Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ -/** - * 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 "../PrecompiledHeadersServer.h" -#include "DeleteInstanceCommand.h" - -#include "../../Core/Logging.h" - -namespace Orthanc -{ - bool DeleteInstanceCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Deleting instance " << *it; - - try - { - Json::Value tmp; - context_.DeleteResource(tmp, *it, ResourceType_Instance); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Unable to delete instance " << *it << ": " << e.What(); - } - } - - return true; - } -} diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/Graveyard/OldScheduler/DeleteInstanceCommand.h --- a/Resources/Graveyard/OldScheduler/DeleteInstanceCommand.h Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -/** - * 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 "IServerCommand.h" -#include "../ServerContext.h" - -namespace Orthanc -{ - class DeleteInstanceCommand : public IServerCommand - { - private: - ServerContext& context_; - - public: - DeleteInstanceCommand(ServerContext& context) : context_(context) - { - } - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -} diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/Graveyard/OldScheduler/IServerCommand.h --- a/Resources/Graveyard/OldScheduler/IServerCommand.h Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -/** - * 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 -#include -#include - -namespace Orthanc -{ - class IServerCommand : public boost::noncopyable - { - public: - typedef std::list ListOfStrings; - - virtual ~IServerCommand() - { - } - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) = 0; - }; -} diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/Graveyard/OldScheduler/ModifyInstanceCommand.cpp --- a/Resources/Graveyard/OldScheduler/ModifyInstanceCommand.cpp Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,124 +0,0 @@ -/** - * 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 "../PrecompiledHeadersServer.h" -#include "ModifyInstanceCommand.h" - -#include "../../Core/Logging.h" - -namespace Orthanc -{ - ModifyInstanceCommand::ModifyInstanceCommand(ServerContext& context, - RequestOrigin origin, - DicomModification* modification) : - context_(context), - origin_(origin), - modification_(modification) - { - modification_->SetAllowManualIdentifiers(true); - - if (modification_->IsReplaced(DICOM_TAG_PATIENT_ID)) - { - modification_->SetLevel(ResourceType_Patient); - } - else if (modification_->IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) - { - modification_->SetLevel(ResourceType_Study); - } - else if (modification_->IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) - { - modification_->SetLevel(ResourceType_Series); - } - else - { - modification_->SetLevel(ResourceType_Instance); - } - - if (origin_ != RequestOrigin_Lua) - { - // TODO If issued from HTTP, "remoteIp" and "username" must be provided - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - ModifyInstanceCommand::~ModifyInstanceCommand() - { - if (modification_) - { - delete modification_; - } - } - - - bool ModifyInstanceCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Modifying resource " << *it; - - try - { - std::auto_ptr modified; - - { - ServerContext::DicomCacheLocker lock(context_, *it); - modified.reset(lock.GetDicom().Clone(true)); - } - - modification_->Apply(*modified); - - DicomInstanceToStore toStore; - assert(origin_ == RequestOrigin_Lua); - toStore.SetLuaOrigin(); - toStore.SetParsedDicomFile(*modified); - // TODO other metadata - toStore.AddMetadata(ResourceType_Instance, MetadataType_ModifiedFrom, *it); - - std::string modifiedId; - context_.Store(modifiedId, toStore); - - // Only chain with other commands if this command succeeds - outputs.push_back(modifiedId); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Unable to modify instance " << *it << ": " << e.What(); - } - } - - return true; - } -} diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/Graveyard/OldScheduler/ModifyInstanceCommand.h --- a/Resources/Graveyard/OldScheduler/ModifyInstanceCommand.h Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -/** - * 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 "IServerCommand.h" -#include "../ServerContext.h" -#include "../../Core/DicomParsing/DicomModification.h" - -namespace Orthanc -{ - class ModifyInstanceCommand : public IServerCommand - { - private: - ServerContext& context_; - RequestOrigin origin_; - DicomModification* modification_; - - public: - ModifyInstanceCommand(ServerContext& context, - RequestOrigin origin, - DicomModification* modification); // takes the ownership - - virtual ~ModifyInstanceCommand(); - - const DicomModification& GetModification() const - { - return *modification_; - } - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -} diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.cpp --- a/Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.cpp Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,188 +0,0 @@ -/** - * 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 "ReusableDicomUserConnection.h" - -#include "../Logging.h" -#include "../OrthancException.h" - -namespace Orthanc -{ - static boost::posix_time::ptime Now() - { - return boost::posix_time::microsec_clock::local_time(); - } - - void ReusableDicomUserConnection::Open(const std::string& localAet, - const RemoteModalityParameters& remote) - { - if (connection_ != NULL && - connection_->GetLocalApplicationEntityTitle() == localAet && - connection_->GetRemoteApplicationEntityTitle() == remote.GetApplicationEntityTitle() && - connection_->GetRemoteHost() == remote.GetHost() && - connection_->GetRemotePort() == remote.GetPort() && - connection_->GetRemoteManufacturer() == remote.GetManufacturer()) - { - // The current connection can be reused - LOG(INFO) << "Reusing the previous SCU connection"; - return; - } - - Close(); - - connection_ = new DicomUserConnection(); - connection_->SetLocalApplicationEntityTitle(localAet); - connection_->SetRemoteModality(remote); - connection_->Open(); - } - - void ReusableDicomUserConnection::Close() - { - if (connection_ != NULL) - { - delete connection_; - connection_ = NULL; - } - } - - void ReusableDicomUserConnection::CloseThread(ReusableDicomUserConnection* that) - { - for (;;) - { - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - if (!that->continue_) - { - //LOG(INFO) << "Finishing the thread watching the global SCU connection"; - return; - } - - { - boost::mutex::scoped_lock lock(that->mutex_); - if (that->connection_ != NULL && - Now() >= that->lastUse_ + that->timeBeforeClose_) - { - LOG(INFO) << "Closing the global SCU connection after timeout"; - that->Close(); - } - } - } - } - - - ReusableDicomUserConnection::Locker::Locker(ReusableDicomUserConnection& that, - const std::string& localAet, - const RemoteModalityParameters& remote) : - ::Orthanc::Locker(that) - { - that.Open(localAet, remote); - connection_ = that.connection_; - } - - - DicomUserConnection& ReusableDicomUserConnection::Locker::GetConnection() - { - if (connection_ == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - return *connection_; - } - - ReusableDicomUserConnection::ReusableDicomUserConnection() : - connection_(NULL), - timeBeforeClose_(boost::posix_time::seconds(5)) // By default, close connection after 5 seconds - { - lastUse_ = Now(); - continue_ = true; - closeThread_ = boost::thread(CloseThread, this); - } - - ReusableDicomUserConnection::~ReusableDicomUserConnection() - { - if (continue_) - { - LOG(ERROR) << "INTERNAL ERROR: ReusableDicomUserConnection::Finalize() should be invoked manually to avoid mess in the destruction order!"; - Finalize(); - } - } - - void ReusableDicomUserConnection::SetMillisecondsBeforeClose(uint64_t ms) - { - boost::mutex::scoped_lock lock(mutex_); - - if (ms == 0) - { - ms = 1; - } - - timeBeforeClose_ = boost::posix_time::milliseconds(ms); - } - - void ReusableDicomUserConnection::Lock() - { - mutex_.lock(); - } - - void ReusableDicomUserConnection::Unlock() - { - if (connection_ != NULL && - connection_->GetRemoteManufacturer() == ModalityManufacturer_StoreScp) - { - // "storescp" from DCMTK has problems when reusing a - // connection. Always close. - Close(); - } - - lastUse_ = Now(); - mutex_.unlock(); - } - - - void ReusableDicomUserConnection::Finalize() - { - if (continue_) - { - continue_ = false; - - if (closeThread_.joinable()) - { - closeThread_.join(); - } - - Close(); - } - } -} - diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.h --- a/Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.h Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -/** - * 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 "DicomUserConnection.h" -#include "../../Core/MultiThreading/Locker.h" - -#include -#include - -namespace Orthanc -{ - class ReusableDicomUserConnection : public ILockable - { - private: - boost::mutex mutex_; - DicomUserConnection* connection_; - bool continue_; - boost::posix_time::time_duration timeBeforeClose_; - boost::posix_time::ptime lastUse_; - boost::thread closeThread_; - - void Open(const std::string& localAet, - const RemoteModalityParameters& remote); - - void Close(); - - static void CloseThread(ReusableDicomUserConnection* that); - - protected: - virtual void Lock(); - - virtual void Unlock(); - - public: - class Locker : public ::Orthanc::Locker - { - private: - DicomUserConnection* connection_; - - public: - Locker(ReusableDicomUserConnection& that, - const std::string& localAet, - const RemoteModalityParameters& remote); - - DicomUserConnection& GetConnection(); - }; - - ReusableDicomUserConnection(); - - virtual ~ReusableDicomUserConnection(); - - void SetMillisecondsBeforeClose(uint64_t ms); - - void Finalize(); - }; -} - diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/Graveyard/OldScheduler/ServerCommandInstance.cpp --- a/Resources/Graveyard/OldScheduler/ServerCommandInstance.cpp Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +0,0 @@ -/** - * 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 "../PrecompiledHeadersServer.h" -#include "ServerCommandInstance.h" - -#include "../../Core/OrthancException.h" - -namespace Orthanc -{ - bool ServerCommandInstance::Execute(IListener& listener) - { - ListOfStrings outputs; - - bool success = false; - - try - { - if (command_->Apply(outputs, inputs_)) - { - success = true; - } - } - catch (OrthancException&) - { - } - - if (!success) - { - listener.SignalFailure(jobId_); - return true; - } - - for (std::list::iterator - it = next_.begin(); it != next_.end(); ++it) - { - for (ListOfStrings::const_iterator - output = outputs.begin(); output != outputs.end(); ++output) - { - (*it)->AddInput(*output); - } - } - - listener.SignalSuccess(jobId_); - return true; - } - - - ServerCommandInstance::ServerCommandInstance(IServerCommand *command, - const std::string& jobId) : - command_(command), - jobId_(jobId), - connectedToSink_(false) - { - if (command_ == NULL) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - ServerCommandInstance::~ServerCommandInstance() - { - if (command_ != NULL) - { - delete command_; - } - } -} diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/Graveyard/OldScheduler/ServerCommandInstance.h --- a/Resources/Graveyard/OldScheduler/ServerCommandInstance.h Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,105 +0,0 @@ -/** - * 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 "../../Core/IDynamicObject.h" -#include "IServerCommand.h" - -namespace Orthanc -{ - class ServerCommandInstance : public IDynamicObject - { - friend class ServerScheduler; - - public: - class IListener - { - public: - virtual ~IListener() - { - } - - virtual void SignalSuccess(const std::string& jobId) = 0; - - virtual void SignalFailure(const std::string& jobId) = 0; - }; - - private: - typedef IServerCommand::ListOfStrings ListOfStrings; - - IServerCommand *command_; - std::string jobId_; - ListOfStrings inputs_; - std::list next_; - bool connectedToSink_; - - bool Execute(IListener& listener); - - public: - ServerCommandInstance(IServerCommand *command, - const std::string& jobId); - - virtual ~ServerCommandInstance(); - - const std::string& GetJobId() const - { - return jobId_; - } - - void AddInput(const std::string& input) - { - inputs_.push_back(input); - } - - void ConnectOutput(ServerCommandInstance& next) - { - next_.push_back(&next); - } - - void SetConnectedToSink(bool connected = true) - { - connectedToSink_ = connected; - } - - bool IsConnectedToSink() const - { - return connectedToSink_; - } - - const std::list& GetNextCommands() const - { - return next_; - } - }; -} diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/Graveyard/OldScheduler/ServerJob.cpp --- a/Resources/Graveyard/OldScheduler/ServerJob.cpp Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,147 +0,0 @@ -/** - * 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 "../PrecompiledHeadersServer.h" -#include "ServerJob.h" - -#include "../../Core/OrthancException.h" -#include "../../Core/Toolbox.h" - -namespace Orthanc -{ - void ServerJob::CheckOrdering() - { - std::map index; - - unsigned int count = 0; - for (std::list::const_iterator - it = filters_.begin(); it != filters_.end(); ++it) - { - index[*it] = count++; - } - - for (std::list::const_iterator - it = filters_.begin(); it != filters_.end(); ++it) - { - const std::list& nextCommands = (*it)->GetNextCommands(); - - for (std::list::const_iterator - next = nextCommands.begin(); next != nextCommands.end(); ++next) - { - if (index.find(*next) == index.end() || - index[*next] <= index[*it]) - { - // You must reorder your calls to "ServerJob::AddCommand" - throw OrthancException(ErrorCode_BadJobOrdering); - } - } - } - } - - - size_t ServerJob::Submit(SharedMessageQueue& target, - ServerCommandInstance::IListener& listener) - { - if (submitted_) - { - // This job has already been submitted - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - CheckOrdering(); - - size_t size = filters_.size(); - - for (std::list::iterator - it = filters_.begin(); it != filters_.end(); ++it) - { - target.Enqueue(*it); - } - - filters_.clear(); - submitted_ = true; - - return size; - } - - - ServerJob::ServerJob() : - jobId_(Toolbox::GenerateUuid()), - submitted_(false), - description_("no description") - { - } - - - ServerJob::~ServerJob() - { - for (std::list::iterator - it = filters_.begin(); it != filters_.end(); ++it) - { - delete *it; - } - - for (std::list::iterator - it = payloads_.begin(); it != payloads_.end(); ++it) - { - delete *it; - } - } - - - ServerCommandInstance& ServerJob::AddCommand(IServerCommand* filter) - { - if (submitted_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - filters_.push_back(new ServerCommandInstance(filter, jobId_)); - - return *filters_.back(); - } - - - IDynamicObject& ServerJob::AddPayload(IDynamicObject* payload) - { - if (submitted_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - payloads_.push_back(payload); - - return *filters_.back(); - } - -} diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/Graveyard/OldScheduler/ServerJob.h --- a/Resources/Graveyard/OldScheduler/ServerJob.h Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -/** - * 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 "ServerCommandInstance.h" -#include "../../Core/MultiThreading/SharedMessageQueue.h" - -namespace Orthanc -{ - class ServerJob - { - friend class ServerScheduler; - - private: - std::list filters_; - std::list payloads_; - std::string jobId_; - bool submitted_; - std::string description_; - - void CheckOrdering(); - - size_t Submit(SharedMessageQueue& target, - ServerCommandInstance::IListener& listener); - - public: - ServerJob(); - - ~ServerJob(); - - const std::string& GetId() const - { - return jobId_; - } - - void SetDescription(const std::string& description) - { - description_ = description; - } - - const std::string& GetDescription() const - { - return description_; - } - - ServerCommandInstance& AddCommand(IServerCommand* filter); - - // Take the ownership of a payload to a job. This payload will be - // automatically freed when the job succeeds or fails. - IDynamicObject& AddPayload(IDynamicObject* payload); - }; -} diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/Graveyard/OldScheduler/ServerScheduler.cpp --- a/Resources/Graveyard/OldScheduler/ServerScheduler.cpp Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,359 +0,0 @@ -/** - * 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 "../PrecompiledHeadersServer.h" -#include "ServerScheduler.h" - -#include "../../Core/OrthancException.h" -#include "../../Core/Logging.h" - -namespace Orthanc -{ - namespace - { - // Anonymous namespace to avoid clashes between compilation modules - class Sink : public IServerCommand - { - private: - ListOfStrings& target_; - - public: - explicit Sink(ListOfStrings& target) : target_(target) - { - } - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - target_.push_back(*it); - } - - return true; - } - }; - } - - - ServerScheduler::JobInfo& ServerScheduler::GetJobInfo(const std::string& jobId) - { - Jobs::iterator info = jobs_.find(jobId); - - if (info == jobs_.end()) - { - throw OrthancException(ErrorCode_InternalError); - } - - return info->second; - } - - - void ServerScheduler::SignalSuccess(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - - JobInfo& info = GetJobInfo(jobId); - info.success_++; - - assert(info.failures_ == 0); - - if (info.success_ >= info.size_) - { - if (info.watched_) - { - watchedJobStatus_[jobId] = JobStatus_Success; - watchedJobFinished_.notify_all(); - } - - LOG(INFO) << "Job successfully finished (" << info.description_ << ")"; - jobs_.erase(jobId); - - availableJob_.Release(); - } - } - - - void ServerScheduler::SignalFailure(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - - JobInfo& info = GetJobInfo(jobId); - info.failures_++; - - if (info.success_ + info.failures_ >= info.size_) - { - if (info.watched_) - { - watchedJobStatus_[jobId] = JobStatus_Failure; - watchedJobFinished_.notify_all(); - } - - LOG(ERROR) << "Job has failed (" << info.description_ << ")"; - jobs_.erase(jobId); - - availableJob_.Release(); - } - } - - - void ServerScheduler::Worker(ServerScheduler* that) - { - static const int32_t TIMEOUT = 100; - - LOG(WARNING) << "The server scheduler has started"; - - while (!that->finish_) - { - std::auto_ptr object(that->queue_.Dequeue(TIMEOUT)); - if (object.get() != NULL) - { - ServerCommandInstance& filter = dynamic_cast(*object); - - // Skip the execution of this filter if its parent job has - // previously failed. - bool jobHasFailed; - { - boost::mutex::scoped_lock lock(that->mutex_); - JobInfo& info = that->GetJobInfo(filter.GetJobId()); - jobHasFailed = (info.failures_ > 0 || info.cancel_); - } - - if (jobHasFailed) - { - that->SignalFailure(filter.GetJobId()); - } - else - { - filter.Execute(*that); - } - } - } - } - - - void ServerScheduler::SubmitInternal(ServerJob& job, - bool watched) - { - availableJob_.Acquire(); - - boost::mutex::scoped_lock lock(mutex_); - - JobInfo info; - info.size_ = job.Submit(queue_, *this); - info.cancel_ = false; - info.success_ = 0; - info.failures_ = 0; - info.description_ = job.GetDescription(); - info.watched_ = watched; - - assert(info.size_ > 0); - - if (watched) - { - watchedJobStatus_[job.GetId()] = JobStatus_Running; - } - - jobs_[job.GetId()] = info; - - LOG(INFO) << "New job submitted (" << job.description_ << ")"; - } - - - ServerScheduler::ServerScheduler(unsigned int maxJobs) : availableJob_(maxJobs) - { - if (maxJobs == 0) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - finish_ = false; - worker_ = boost::thread(Worker, this); - } - - - ServerScheduler::~ServerScheduler() - { - if (!finish_) - { - LOG(ERROR) << "INTERNAL ERROR: ServerScheduler::Finalize() should be invoked manually to avoid mess in the destruction order!"; - Stop(); - } - } - - - void ServerScheduler::Stop() - { - if (!finish_) - { - finish_ = true; - - if (worker_.joinable()) - { - worker_.join(); - } - } - } - - - void ServerScheduler::Submit(ServerJob& job) - { - if (job.filters_.empty()) - { - return; - } - - SubmitInternal(job, false); - } - - - bool ServerScheduler::SubmitAndWait(ListOfStrings& outputs, - ServerJob& job) - { - std::string jobId = job.GetId(); - - outputs.clear(); - - if (job.filters_.empty()) - { - return true; - } - - // Add a sink filter to collect all the results of the filters - // that have no next filter. - ServerCommandInstance& sink = job.AddCommand(new Sink(outputs)); - - for (std::list::iterator - it = job.filters_.begin(); it != job.filters_.end(); ++it) - { - if ((*it) != &sink && - (*it)->IsConnectedToSink()) - { - (*it)->ConnectOutput(sink); - } - } - - // Submit the job - SubmitInternal(job, true); - - // Wait for the job to complete (either success or failure) - JobStatus status; - - { - boost::mutex::scoped_lock lock(mutex_); - - assert(watchedJobStatus_.find(jobId) != watchedJobStatus_.end()); - - while (watchedJobStatus_[jobId] == JobStatus_Running) - { - watchedJobFinished_.wait(lock); - } - - status = watchedJobStatus_[jobId]; - watchedJobStatus_.erase(jobId); - } - - return (status == JobStatus_Success); - } - - - bool ServerScheduler::SubmitAndWait(ServerJob& job) - { - ListOfStrings ignoredSink; - return SubmitAndWait(ignoredSink, job); - } - - - bool ServerScheduler::IsRunning(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - return jobs_.find(jobId) != jobs_.end(); - } - - - void ServerScheduler::Cancel(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - - Jobs::iterator job = jobs_.find(jobId); - - if (job != jobs_.end()) - { - job->second.cancel_ = true; - LOG(WARNING) << "Canceling a job (" << job->second.description_ << ")"; - } - } - - - float ServerScheduler::GetProgress(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - - Jobs::iterator job = jobs_.find(jobId); - - if (job == jobs_.end() || - job->second.size_ == 0 /* should never happen */) - { - // This job is not running - return 1; - } - - if (job->second.failures_ != 0) - { - return 1; - } - - if (job->second.size_ == 1) - { - return static_cast(job->second.success_); - } - - return (static_cast(job->second.success_) / - static_cast(job->second.size_ - 1)); - } - - - void ServerScheduler::GetListOfJobs(ListOfStrings& jobs) - { - boost::mutex::scoped_lock lock(mutex_); - - jobs.clear(); - - for (Jobs::const_iterator - it = jobs_.begin(); it != jobs_.end(); ++it) - { - jobs.push_back(it->first); - } - } -} diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/Graveyard/OldScheduler/ServerScheduler.h --- a/Resources/Graveyard/OldScheduler/ServerScheduler.h Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,123 +0,0 @@ -/** - * 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 "ServerJob.h" - -#include "../../Core/MultiThreading/Semaphore.h" - -namespace Orthanc -{ - class ServerScheduler : public ServerCommandInstance::IListener - { - private: - struct JobInfo - { - bool watched_; - bool cancel_; - size_t size_; - size_t success_; - size_t failures_; - std::string description_; - }; - - enum JobStatus - { - JobStatus_Running = 1, - JobStatus_Success = 2, - JobStatus_Failure = 3 - }; - - typedef IServerCommand::ListOfStrings ListOfStrings; - typedef std::map Jobs; - - boost::mutex mutex_; - boost::condition_variable watchedJobFinished_; - Jobs jobs_; - SharedMessageQueue queue_; - bool finish_; - boost::thread worker_; - std::map watchedJobStatus_; - Semaphore availableJob_; - - JobInfo& GetJobInfo(const std::string& jobId); - - virtual void SignalSuccess(const std::string& jobId); - - virtual void SignalFailure(const std::string& jobId); - - static void Worker(ServerScheduler* that); - - void SubmitInternal(ServerJob& job, - bool watched); - - public: - explicit ServerScheduler(unsigned int maxjobs); - - ~ServerScheduler(); - - void Stop(); - - void Submit(ServerJob& job); - - bool SubmitAndWait(ListOfStrings& outputs, - ServerJob& job); - - bool SubmitAndWait(ServerJob& job); - - bool IsRunning(const std::string& jobId); - - void Cancel(const std::string& jobId); - - // Returns a number between 0 and 1 - float GetProgress(const std::string& jobId); - - bool IsRunning(const ServerJob& job) - { - return IsRunning(job.GetId()); - } - - void Cancel(const ServerJob& job) - { - Cancel(job.GetId()); - } - - float GetProgress(const ServerJob& job) - { - return GetProgress(job.GetId()); - } - - void GetListOfJobs(ListOfStrings& jobs); - }; -} diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/Graveyard/OldScheduler/StorePeerCommand.cpp --- a/Resources/Graveyard/OldScheduler/StorePeerCommand.cpp Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,92 +0,0 @@ -/** - * 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 "../PrecompiledHeadersServer.h" -#include "StorePeerCommand.h" - -#include "../../Core/Logging.h" -#include "../../Core/HttpClient.h" - -namespace Orthanc -{ - StorePeerCommand::StorePeerCommand(ServerContext& context, - const WebServiceParameters& peer, - bool ignoreExceptions) : - context_(context), - peer_(peer), - ignoreExceptions_(ignoreExceptions) - { - } - - bool StorePeerCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - // Configure the HTTP client - HttpClient client(peer_, "instances"); - client.SetMethod(HttpMethod_Post); - - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Sending resource " << *it << " to peer \"" - << peer_.GetUrl() << "\""; - - try - { - context_.ReadDicom(client.GetBody(), *it); - - std::string answer; - if (!client.Apply(answer)) - { - LOG(ERROR) << "Unable to send resource " << *it << " to peer \"" << peer_.GetUrl() << "\""; - throw OrthancException(ErrorCode_NetworkProtocol); - } - - // Only chain with other commands if this command succeeds - outputs.push_back(*it); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Unable to forward to an Orthanc peer in (instance " - << *it << ", peer " << peer_.GetUrl() << "): " << e.What(); - - if (!ignoreExceptions_) - { - throw; - } - } - } - - return true; - } -} diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/Graveyard/OldScheduler/StorePeerCommand.h --- a/Resources/Graveyard/OldScheduler/StorePeerCommand.h Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -/** - * 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 "IServerCommand.h" -#include "../ServerContext.h" -#include "../OrthancInitialization.h" - -namespace Orthanc -{ - class StorePeerCommand : public IServerCommand - { - private: - ServerContext& context_; - WebServiceParameters peer_; - bool ignoreExceptions_; - - public: - StorePeerCommand(ServerContext& context, - const WebServiceParameters& peer, - bool ignoreExceptions); - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -} diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/Graveyard/OldScheduler/StoreScuCommand.cpp --- a/Resources/Graveyard/OldScheduler/StoreScuCommand.cpp Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +0,0 @@ -/** - * 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 "../PrecompiledHeadersServer.h" -#include "StoreScuCommand.h" - -#include "../../Core/Logging.h" - -namespace Orthanc -{ - StoreScuCommand::StoreScuCommand(ServerContext& context, - const std::string& localAet, - const RemoteModalityParameters& modality, - bool ignoreExceptions) : - context_(context), - modality_(modality), - ignoreExceptions_(ignoreExceptions), - localAet_(localAet), - moveOriginatorID_(0) - { - } - - - void StoreScuCommand::SetMoveOriginator(const std::string& aet, - uint16_t id) - { - moveOriginatorAET_ = aet; - moveOriginatorID_ = id; - } - - - bool StoreScuCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), localAet_, modality_); - - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Sending resource " << *it << " to modality \"" - << modality_.GetApplicationEntityTitle() << "\""; - - try - { - std::string dicom; - context_.ReadDicom(dicom, *it); - - locker.GetConnection().Store(dicom, moveOriginatorAET_, moveOriginatorID_); - - // Only chain with other commands if this command succeeds - outputs.push_back(*it); - } - catch (OrthancException& e) - { - // Ignore transmission errors (e.g. if the remote modality is - // powered off) - LOG(ERROR) << "Unable to forward to a modality in (instance " - << *it << "): " << e.What(); - - if (!ignoreExceptions_) - { - throw; - } - } - } - - return true; - } -} diff -r ff0718a4633a -r e1c2bc9a5cc1 Resources/Graveyard/OldScheduler/StoreScuCommand.h --- a/Resources/Graveyard/OldScheduler/StoreScuCommand.h Mon Apr 27 18:33:33 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -/** - * 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 "IServerCommand.h" -#include "../ServerContext.h" - -namespace Orthanc -{ - class StoreScuCommand : public IServerCommand - { - private: - ServerContext& context_; - RemoteModalityParameters modality_; - bool ignoreExceptions_; - std::string localAet_; - std::string moveOriginatorAET_; - uint16_t moveOriginatorID_; - - public: - StoreScuCommand(ServerContext& context, - const std::string& localAet, - const RemoteModalityParameters& modality, - bool ignoreExceptions); - - void SetMoveOriginator(const std::string& aet, - uint16_t id); - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -} diff -r ff0718a4633a -r e1c2bc9a5cc1 UnitTestsSources/FromDcmtkTests.cpp --- a/UnitTestsSources/FromDcmtkTests.cpp Mon Apr 27 18:33:33 2020 +0200 +++ b/UnitTestsSources/FromDcmtkTests.cpp Mon Apr 27 18:34:20 2020 +0200 @@ -1929,6 +1929,8 @@ #include #include #include +#include // for DJ_RPLossy +#include // for DJ_RPLossless namespace Orthanc @@ -1940,6 +1942,8 @@ { } + virtual DcmFileFormat& GetDicom() = 0; + virtual DicomTransferSyntax GetTransferSyntax() = 0; virtual std::string GetSopClassUid() = 0; @@ -1953,8 +1957,13 @@ virtual void GetCompressedFrame(std::string& target, unsigned int frame) = 0; - virtual IDicomTranscoder* Transcode(std::set syntaxes, - bool allowNewSopInstanceUid) = 0; + // 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; }; @@ -1966,9 +1975,30 @@ 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 || @@ -1999,20 +2029,14 @@ "Unsupported transfer syntax: " + boost::lexical_cast(xfer)); } - const char* a = NULL; - const char* b = NULL; - - if (!dataset.findAndGetString(DCM_SOPClassUID, a).good() || - !dataset.findAndGetString(DCM_SOPInstanceUID, b).good() || - a == NULL || - b == NULL) + if (!dataset.findAndGetUint16(DCM_BitsStored, bitsStored_).good()) { throw OrthancException(ErrorCode_BadFileFormat, - "Missing SOP class/instance UID in DICOM instance"); - } - - sopClassUid_.assign(a); - sopInstanceUid_.assign(b); + "Missing \"Bits Stored\" tag in DICOM instance"); + } + + sopClassUid_ = GetStringTag(dataset, DCM_SOPClassUID); + sopInstanceUid_ = GetStringTag(dataset, DCM_SOPInstanceUID); } public: @@ -2027,6 +2051,35 @@ 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_; @@ -2047,6 +2100,15 @@ 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); @@ -2056,48 +2118,69 @@ virtual void GetCompressedFrame(std::string& target, unsigned int frame) ORTHANC_OVERRIDE { -#if 1 index_->GetRawFrame(target, frame); - printf("%d: %d\n", frame, target.size()); -#endif - -#if 1 - assert(dicom_->getDataset() != NULL); - DcmDataset& dataset = *dicom_->getDataset(); + } + + virtual bool Transcode(std::string& target, + std::set syntaxes, + bool allowNewSopInstanceUid) ORTHANC_OVERRIDE + { + assert(dicom_ != NULL && + dicom_->getDataset() != NULL); - DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset); - - if (pixelSequence != NULL && - frame == 0 && - pixelSequence->card() != GetFramesCount() + 1) + if (syntaxes.find(GetTransferSyntax()) != syntaxes.end()) { - printf("COMPRESSED\n"); - - // Check out "djcodecd.cc" - - printf("%d fragments\n", pixelSequence->card()); + printf("NO TRANSCODING\n"); - // Skip the first fragment, that is the offset table - for (unsigned long i = 1; ;i++) - { - DcmPixelItem *fragment = NULL; - if (pixelSequence->getItem(fragment, i).good()) - { - printf("fragment %d %d\n", i, fragment->getLength()); - } - else - { - break; - } - } + // 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; } -#endif - } - - virtual IDicomTranscoder* Transcode(std::set syntaxes, - bool allowNewSopInstanceUid) ORTHANC_OVERRIDE - { - throw OrthancException(ErrorCode_NotImplemented); + 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; + } } }; } @@ -2174,14 +2257,16 @@ } } -#include "dcmtk/dcmjpeg/djrploss.h" /* for DJ_RPLossy */ -#include "dcmtk/dcmjpeg/djrplol.h" /* for DJ_RPLossless */ #include static void TestFile(const std::string& path) { + static unsigned int count = 0; + count++; + + printf("** %s\n", path.c_str()); std::string s; @@ -2189,9 +2274,19 @@ Orthanc::DcmtkTranscoder transcoder(s.c_str(), s.size()); - printf("[%s] [%s] [%s] %d\n", GetTransferSyntaxUid(transcoder.GetTransferSyntax()), + /*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.GetFramesCount(), transcoder.GetTransferSyntax()); for (size_t i = 0; i < transcoder.GetFramesCount(); i++) { @@ -2200,27 +2295,65 @@ if (i == 0) { - static unsigned int i = 0; char buf[1024]; - sprintf(buf, "/tmp/frame-%06d.dcm", i++); + 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); + + std::string t; + bool ok = transcoder.Transcode(t, syntaxes, 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::DcmtkTranscoder transcoder2(t.c_str(), t.size()); + printf(" => transcoded transfer syntax %d ; %lu bytes\n", transcoder2.GetTransferSyntax(), t.size()); + } + } + printf("\n"); } -TEST(Toto, Transcode) +TEST(Toto, DISABLED_Transcode) { + //OFLog::configure(OFLogger::DEBUG_LOG_LEVEL); + if (0) { - OFLog::configure(OFLogger::DEBUG_LOG_LEVEL); - 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/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())); @@ -2232,7 +2365,7 @@ #if 0 E_TransferSyntax target = EXS_LittleEndianExplicit; p = NULL; -#elif 1 +#elif 0 E_TransferSyntax target = EXS_JPEGProcess14SV1; DJ_RPLossless rp_lossless(6, 0); p = &rp_lossless; @@ -2264,10 +2397,137 @@ 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"); + } +} + + + +#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) +{ + uint8_t id; + + 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("**** TRANSCODING to %s => %d\n", + GetTransferSyntaxUid(uncompressed[i]), id); + 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.PrepareStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess1); + assoc.PrepareStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess2_4); + //assoc.PrepareStorageClass(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); } #endif diff -r ff0718a4633a -r e1c2bc9a5cc1 UnitTestsSources/MultiThreadingTests.cpp --- a/UnitTestsSources/MultiThreadingTests.cpp Mon Apr 27 18:33:33 2020 +0200 +++ b/UnitTestsSources/MultiThreadingTests.cpp Mon Apr 27 18:34:20 2020 +0200 @@ -1314,7 +1314,7 @@ DicomInstanceToStore toStore; toStore.SetParsedDicomFile(dicom); - return (context_->Store(id, toStore) == StoreStatus_Success); + return (context_->Store(id, toStore, StoreInstanceMode_Default) == StoreStatus_Success); } }; } diff -r ff0718a4633a -r e1c2bc9a5cc1 UnitTestsSources/ServerIndexTests.cpp --- a/UnitTestsSources/ServerIndexTests.cpp Mon Apr 27 18:33:33 2020 +0200 +++ b/UnitTestsSources/ServerIndexTests.cpp Mon Apr 27 18:34:20 2020 +0200 @@ -726,7 +726,8 @@ std::map instanceMetadata; DicomInstanceToStore toStore; toStore.SetSummary(instance); - ASSERT_EQ(StoreStatus_Success, index.Store(instanceMetadata, toStore, attachments)); + ASSERT_EQ(StoreStatus_Success, index.Store(instanceMetadata, toStore, attachments, + false /* don't overwrite */)); ASSERT_EQ(5u, instanceMetadata.size()); ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_RemoteAet) != instanceMetadata.end()); ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_ReceptionDate) != instanceMetadata.end()); @@ -803,7 +804,7 @@ DicomInstanceHasher hasher(instance); std::string id = hasher.HashInstance(); - context.GetIndex().SetOverwriteInstances(overwrite); + context.SetOverwriteInstances(overwrite); uint64_t diskSize, uncompressedSize, countPatients, countStudies, countSeries, countInstances; context.GetIndex().GetGlobalStatistics(diskSize, uncompressedSize, countPatients, @@ -819,7 +820,7 @@ ASSERT_EQ(id, toStore.GetHasher().HashInstance()); std::string id2; - ASSERT_EQ(StoreStatus_Success, context.Store(id2, toStore)); + ASSERT_EQ(StoreStatus_Success, context.Store(id2, toStore, StoreInstanceMode_Default)); ASSERT_EQ(id, id2); } @@ -854,7 +855,8 @@ toStore.SetOrigin(DicomInstanceOrigin::FromPlugins()); std::string id2; - ASSERT_EQ(overwrite ? StoreStatus_Success : StoreStatus_AlreadyStored, context.Store(id2, toStore)); + ASSERT_EQ(overwrite ? StoreStatus_Success : StoreStatus_AlreadyStored, + context.Store(id2, toStore, StoreInstanceMode_Default)); ASSERT_EQ(id, id2); }