# HG changeset patch # User Sebastien Jodogne # Date 1586534172 -7200 # Node ID 83ea6939293d349122e19a933525be262d1e0306 # Parent 447880856ce8ad9eff5c05205ee72c7bb3d90d87 starting DicomStoreUserConnection::Store() diff -r 447880856ce8 -r 83ea6939293d Core/DicomNetworking/DicomStoreUserConnection.cpp --- a/Core/DicomNetworking/DicomStoreUserConnection.cpp Fri Apr 10 17:29:11 2020 +0200 +++ b/Core/DicomNetworking/DicomStoreUserConnection.cpp Fri Apr 10 17:56:12 2020 +0200 @@ -34,10 +34,14 @@ #include "../PrecompiledHeaders.h" #include "DicomStoreUserConnection.h" +#include "../DicomParsing/FromDcmtkBridge.h" +#include "../DicomParsing/ParsedDicomFile.h" +#include "../Logging.h" +#include "../OrthancException.h" #include "DicomAssociation.h" -#include "../Logging.h" -#include "../OrthancException.h" +#include + namespace Orthanc { @@ -237,4 +241,119 @@ association_->Open(parameters_); return LookupPresentationContext(presentationContextId, sopClassUid, transferSyntax); } + + + void DicomStoreUserConnection::Store(std::string& sopClassUid, + std::string& sopInstanceUid, + DcmDataset& dataset, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID) + { + 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()); + + 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"); + } + + // Prepare the transmission of data + T_DIMSE_C_StoreRQ request; + memset(&request, 0, sizeof(request)); + request.MessageID = association_->GetDcmtkAssociation().nextMsgID++; + strncpy(request.AffectedSOPClassUID, sopClassUid.c_str(), DIC_UI_LEN); + request.Priority = DIMSE_PRIORITY_MEDIUM; + request.DataSetType = DIMSE_DATASET_PRESENT; + strncpy(request.AffectedSOPInstanceUID, sopInstanceUid.c_str(), 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; + DicomAssociation::CheckCondition( + DIMSE_storeUser(&association_->GetDcmtkAssociation(), presID, &request, + NULL, &dataset, /*progressCallback*/ NULL, NULL, + /*opt_blockMode*/ (GetParameters().HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), + /*opt_dimse_timeout*/ GetParameters().GetTimeout(), + &response, &statusDetail, NULL), + GetParameters(), "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 \"" + + GetParameters().GetRemoteApplicationEntityTitle() + + "\" has failed with DIMSE status 0x" + buf); + } + } + + + void DicomStoreUserConnection::Store(std::string& sopClassUid, + std::string& sopInstanceUid, + ParsedDicomFile& parsed, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID) + { + Store(sopClassUid, sopInstanceUid, *parsed.GetDcmtkObject().getDataset(), + moveOriginatorAET, moveOriginatorID); + } + + + void DicomStoreUserConnection::Store(std::string& sopClassUid, + std::string& sopInstanceUid, + const void* buffer, + size_t size, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID) + { + std::unique_ptr dicom( + FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size)); + + Store(sopClassUid, sopInstanceUid, *dicom->getDataset(), + moveOriginatorAET, moveOriginatorID); + } } diff -r 447880856ce8 -r 83ea6939293d Core/DicomNetworking/DicomStoreUserConnection.h --- a/Core/DicomNetworking/DicomStoreUserConnection.h Fri Apr 10 17:29:11 2020 +0200 +++ b/Core/DicomNetworking/DicomStoreUserConnection.h Fri Apr 10 17:56:12 2020 +0200 @@ -41,6 +41,8 @@ #include // For uint8_t +class DcmDataset; + namespace Orthanc { /** @@ -62,6 +64,7 @@ **/ class DicomAssociation; // Forward declaration for PImpl design pattern + class ParsedDicomFile; class DicomStoreUserConnection : public boost::noncopyable { @@ -78,11 +81,12 @@ // 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 DicomAssociationParameters& params); @@ -124,8 +128,28 @@ void PrepareStorageClass(const std::string& sopClassUid, DicomTransferSyntax syntax); + // TODO => to private bool NegotiatePresentationContext(uint8_t& presentationContextId, const std::string& sopClassUid, DicomTransferSyntax transferSyntax); + + void Store(std::string& sopClassUid, + std::string& sopInstanceUid, + DcmDataset& dataset, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID); + + void Store(std::string& sopClassUid, + std::string& sopInstanceUid, + ParsedDicomFile& parsed, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID); + + void Store(std::string& sopClassUid, + std::string& sopInstanceUid, + const void* buffer, + size_t size, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID); }; }