# HG changeset patch # User Sebastien Jodogne # Date 1452162499 -3600 # Node ID 5011a597b6ceef12e45860eee6716f7cfa01befd # Parent d7c1cb559431a31185abd7cc9f2050918e22cc4e Support of Move Originator Message ID (0000,1031) in C-Store responses driven by C-Move diff -r d7c1cb559431 -r 5011a597b6ce NEWS --- a/NEWS Tue Jan 05 17:45:27 2016 +0100 +++ b/NEWS Thu Jan 07 11:28:19 2016 +0100 @@ -3,6 +3,7 @@ * Support of optional tags for counting resources in C-Find: 0008-0061, 0008-0062, 0020-1200, 0020-1202, 0020-1204, 0020-1206, 0020-1208, 0020-1209 +* Support of Move Originator Message ID (0000,1031) in C-Store responses driven by C-Move * Huge speedup if decoding the family of JPEG transfer syntaxes * Refactoring leading to speedups with custom image decoders (including Web viewer plugin) * Support decoding of RLE Lossless transfer syntax diff -r d7c1cb559431 -r 5011a597b6ce OrthancServer/DicomProtocol/DicomUserConnection.cpp --- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp Tue Jan 05 17:45:27 2016 +0100 +++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp Thu Jan 07 11:28:19 2016 +0100 @@ -148,7 +148,9 @@ void CheckIsOpen() const; - void Store(DcmInputStream& is, DicomUserConnection& connection); + void Store(DcmInputStream& is, + DicomUserConnection& connection, + uint16_t moveMessageID); }; @@ -251,7 +253,9 @@ } - void DicomUserConnection::PImpl::Store(DcmInputStream& is, DicomUserConnection& connection) + void DicomUserConnection::PImpl::Store(DcmInputStream& is, + DicomUserConnection& connection, + uint16_t moveMessageID) { CheckIsOpen(); @@ -325,18 +329,28 @@ } // Prepare the transmission of data - T_DIMSE_C_StoreRQ req; - memset(&req, 0, sizeof(req)); - req.MessageID = assoc_->nextMsgID++; - strcpy(req.AffectedSOPClassUID, sopClass); - strcpy(req.AffectedSOPInstanceUID, sopInstance); - req.DataSetType = DIMSE_DATASET_PRESENT; - req.Priority = DIMSE_PRIORITY_MEDIUM; + 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); + + strncpy(request.MoveOriginatorApplicationEntityTitle, + connection.GetLocalApplicationEntityTitle().c_str(), DIC_AE_LEN); + request.opts = O_STORE_MOVEORIGINATORAETITLE; + + if (moveMessageID != 0) + { + request.MoveOriginatorID = moveMessageID; // The type DIC_US is an alias for uint16_t + request.opts |= O_STORE_MOVEORIGINATORID; + } // Finally conduct transmission of data T_DIMSE_C_StoreRSP rsp; DcmDataset* statusDetail = NULL; - Check(DIMSE_storeUser(assoc_, presID, &req, + Check(DIMSE_storeUser(assoc_, presID, &request, NULL, dcmff.getDataset(), /*progressCallback*/ NULL, NULL, /*opt_blockMode*/ DIMSE_BLOCKING, /*opt_dimse_timeout*/ dimseTimeout_, &rsp, &statusDetail, NULL)); @@ -511,9 +525,9 @@ T_DIMSE_C_FindRQ request; memset(&request, 0, sizeof(request)); request.MessageID = association->nextMsgID++; - strcpy(request.AffectedSOPClassUID, sopClass); + strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); + request.Priority = DIMSE_PRIORITY_MEDIUM; request.DataSetType = DIMSE_DATASET_PRESENT; - request.Priority = DIMSE_PRIORITY_MEDIUM; T_DIMSE_C_FindRSP response; DcmDataset* statusDetail = NULL; @@ -678,10 +692,10 @@ T_DIMSE_C_MoveRQ request; memset(&request, 0, sizeof(request)); request.MessageID = pimpl_->assoc_->nextMsgID++; - strcpy(request.AffectedSOPClassUID, sopClass); + strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); + request.Priority = DIMSE_PRIORITY_MEDIUM; request.DataSetType = DIMSE_DATASET_PRESENT; - request.Priority = DIMSE_PRIORITY_MEDIUM; - strncpy(request.MoveDestination, targetAet.c_str(), sizeof(DIC_AE) / sizeof(char)); + strncpy(request.MoveDestination, targetAet.c_str(), DIC_AE_LEN); T_DIMSE_C_MoveRSP response; DcmDataset* statusDetail = NULL; @@ -920,7 +934,9 @@ return pimpl_->IsOpen(); } - void DicomUserConnection::Store(const char* buffer, size_t size) + void DicomUserConnection::Store(const char* buffer, + size_t size, + uint16_t moveMessageID) { // Prepare an input stream for the memory buffer DcmInputBufferStream is; @@ -928,22 +944,24 @@ is.setBuffer(buffer, size); is.setEos(); - pimpl_->Store(is, *this); + pimpl_->Store(is, *this, moveMessageID); } - void DicomUserConnection::Store(const std::string& buffer) + void DicomUserConnection::Store(const std::string& buffer, + uint16_t moveMessageID) { if (buffer.size() > 0) - Store(reinterpret_cast(&buffer[0]), buffer.size()); + Store(reinterpret_cast(&buffer[0]), buffer.size(), moveMessageID); else - Store(NULL, 0); + Store(NULL, 0, moveMessageID); } - void DicomUserConnection::StoreFile(const std::string& path) + void DicomUserConnection::StoreFile(const std::string& path, + uint16_t moveMessageID) { // Prepare an input stream for the file DcmInputFileStream is(path.c_str()); - pimpl_->Store(is, *this); + pimpl_->Store(is, *this, moveMessageID); } bool DicomUserConnection::Echo() diff -r d7c1cb559431 -r 5011a597b6ce OrthancServer/DicomProtocol/DicomUserConnection.h --- a/OrthancServer/DicomProtocol/DicomUserConnection.h Tue Jan 05 17:45:27 2016 +0100 +++ b/OrthancServer/DicomProtocol/DicomUserConnection.h Thu Jan 07 11:28:19 2016 +0100 @@ -133,11 +133,15 @@ bool Echo(); - void Store(const char* buffer, size_t size); + void Store(const char* buffer, + size_t size, + uint16_t moveMessageID); - void Store(const std::string& buffer); + void Store(const std::string& buffer, + uint16_t moveMessageID); - void StoreFile(const std::string& path); + void StoreFile(const std::string& path, + uint16_t moveMessageID); void Find(DicomFindAnswers& result, ResourceType level, diff -r d7c1cb559431 -r 5011a597b6ce OrthancServer/LuaScripting.cpp --- a/OrthancServer/LuaScripting.cpp Tue Jan 05 17:45:27 2016 +0100 +++ b/OrthancServer/LuaScripting.cpp Thu Jan 07 11:28:19 2016 +0100 @@ -253,7 +253,8 @@ LOG(INFO) << "Lua script to send resource " << parameters["Resource"].asString() << " to modality " << modality << " using Store-SCU"; return new StoreScuCommand(context_, localAet, - Configuration::GetModalityUsingSymbolicName(modality), true); + Configuration::GetModalityUsingSymbolicName(modality), + true, 0 /* not a C-MOVE */); } if (operation == "store-peer") diff -r d7c1cb559431 -r 5011a597b6ce OrthancServer/OrthancMoveRequestHandler.cpp --- a/OrthancServer/OrthancMoveRequestHandler.cpp Tue Jan 05 17:45:27 2016 +0100 +++ b/OrthancServer/OrthancMoveRequestHandler.cpp Thu Jan 07 11:28:19 2016 +0100 @@ -52,14 +52,17 @@ std::vector instances_; size_t position_; RemoteModalityParameters remote_; + uint16_t moveRequestID_; public: OrthancMoveRequestIterator(ServerContext& context, const std::string& aet, - const std::string& publicId) : + const std::string& publicId, + uint16_t moveRequestID) : context_(context), localAet_(context.GetDefaultLocalApplicationEntityTitle()), - position_(0) + position_(0), + moveRequestID_(moveRequestID) { LOG(INFO) << "Sending resource " << publicId << " to modality \"" << aet << "\""; @@ -95,7 +98,7 @@ { ReusableDicomUserConnection::Locker locker (context_.GetReusableDicomUserConnection(), localAet_, remote_); - locker.GetConnection().Store(dicom); + locker.GetConnection().Store(dicom, moveRequestID_); } return Status_Success; @@ -184,6 +187,33 @@ } + /** + * Retrieve the Message ID (0000,0110) for this C-MOVE request, if + * any. If present, this Message ID will be stored in the Move + * Originator Message ID (0000,1031) field of the C-MOVE response. + * http://dicom.nema.org/medical/dicom/current/output/html/part07.html#sect_9.3.1 + **/ + + static const DicomTag MESSAGE_ID(0x0000, 0x0110); + const DicomValue* messageIdTmp = input.TestAndGetValue(MESSAGE_ID); + + uint16_t messageId = 0; + + if (messageIdTmp != NULL && + !messageIdTmp->IsNull() && + !messageIdTmp->IsBinary()) + { + try + { + messageId = boost::lexical_cast(messageIdTmp->GetContent()); + } + catch (boost::bad_lexical_cast&) + { + LOG(WARNING) << "Cannot convert the Message ID (\"" << messageIdTmp ->GetContent() + << "\") of an incoming C-MOVE request to an integer, assuming zero"; + } + } + /** * Retrieve the query level. @@ -208,7 +238,7 @@ LookupIdentifier(publicId, ResourceType_Study, input) || LookupIdentifier(publicId, ResourceType_Patient, input)) { - return new OrthancMoveRequestIterator(context_, targetAet, publicId); + return new OrthancMoveRequestIterator(context_, targetAet, publicId, messageId); } else { @@ -229,7 +259,7 @@ if (LookupIdentifier(publicId, level, input)) { - return new OrthancMoveRequestIterator(context_, targetAet, publicId); + return new OrthancMoveRequestIterator(context_, targetAet, publicId, messageId); } else { diff -r d7c1cb559431 -r 5011a597b6ce OrthancServer/OrthancRestApi/OrthancRestModalities.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Tue Jan 05 17:45:27 2016 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Thu Jan 07 11:28:19 2016 +0100 @@ -695,7 +695,8 @@ for (std::list::const_iterator it = instances.begin(); it != instances.end(); ++it) { - job.AddCommand(new StoreScuCommand(context, localAet, p, false)).AddInput(*it); + job.AddCommand(new StoreScuCommand(context, localAet, p, false, + 0 /* not a C-MOVE */)).AddInput(*it); } job.SetDescription("HTTP request: Store-SCU to peer \"" + remote + "\""); diff -r d7c1cb559431 -r 5011a597b6ce OrthancServer/Scheduler/StoreScuCommand.cpp --- a/OrthancServer/Scheduler/StoreScuCommand.cpp Tue Jan 05 17:45:27 2016 +0100 +++ b/OrthancServer/Scheduler/StoreScuCommand.cpp Thu Jan 07 11:28:19 2016 +0100 @@ -40,11 +40,13 @@ StoreScuCommand::StoreScuCommand(ServerContext& context, const std::string& localAet, const RemoteModalityParameters& modality, - bool ignoreExceptions) : + bool ignoreExceptions, + uint16_t moveMessageID) : context_(context), modality_(modality), ignoreExceptions_(ignoreExceptions), - localAet_(localAet) + localAet_(localAet), + moveMessageID_(moveMessageID) { } @@ -63,7 +65,8 @@ { std::string dicom; context_.ReadFile(dicom, *it, FileContentType_Dicom); - locker.GetConnection().Store(dicom); + + locker.GetConnection().Store(dicom, moveMessageID_); // Only chain with other commands if this command succeeds outputs.push_back(*it); diff -r d7c1cb559431 -r 5011a597b6ce OrthancServer/Scheduler/StoreScuCommand.h --- a/OrthancServer/Scheduler/StoreScuCommand.h Tue Jan 05 17:45:27 2016 +0100 +++ b/OrthancServer/Scheduler/StoreScuCommand.h Thu Jan 07 11:28:19 2016 +0100 @@ -44,12 +44,15 @@ RemoteModalityParameters modality_; bool ignoreExceptions_; std::string localAet_; + uint16_t moveMessageID_; public: StoreScuCommand(ServerContext& context, const std::string& localAet, const RemoteModalityParameters& modality, - bool ignoreExceptions); + bool ignoreExceptions, + uint16_t moveMessageID /* only makes sense if this + command results from a C-MOVE */); virtual bool Apply(ListOfStrings& outputs, const ListOfStrings& inputs); diff -r d7c1cb559431 -r 5011a597b6ce UnitTestsSources/MultiThreadingTests.cpp --- a/UnitTestsSources/MultiThreadingTests.cpp Tue Jan 05 17:45:27 2016 +0100 +++ b/UnitTestsSources/MultiThreadingTests.cpp Thu Jan 07 11:28:19 2016 +0100 @@ -138,7 +138,7 @@ { RemoteModalityParameters remote("STORESCP", "localhost", 2000, ModalityManufacturer_Generic); ReusableDicomUserConnection::Locker lock(c, "ORTHANC", remote); - lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676281"); + lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676281", 0); } printf("**\n"); fflush(stdout); @@ -148,7 +148,7 @@ { RemoteModalityParameters remote("STORESCP", "localhost", 2000, ModalityManufacturer_Generic); ReusableDicomUserConnection::Locker lock(c, "ORTHANC", remote); - lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676277"); + lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676277", 0); } Toolbox::ServerBarrier();