Mercurial > hg > orthanc
changeset 5875:94e6a9a66109 get-scu
new Config AcceptedSopClasses + propose only the contexts that are going to be used in the connection
line wrap: on
line diff
--- a/NEWS Wed Nov 13 15:21:08 2024 +0100 +++ b/NEWS Wed Nov 20 09:58:12 2024 +0100 @@ -1,6 +1,14 @@ Pending changes in the mainline =============================== +General +------- + +* DICOM: + - Added support for C-GET SCU. + - Added a configuration "AcceptedSopClasses" to limit the SOP classes accepted by + Orthanc. + REST API ----------- @@ -20,6 +28,10 @@ * DICOM TLS: "DicomTlsTrustedCertificates" is not required anymore when issuing an outgoing SCU connexion when "DicomTlsRemoteCertificateRequired" is set to false. +* DICOM negotiation: + - When opening a DICOM SCU connection, Orthanc now only proposes the contexts that it is + going to use in the connection and not all contexts as in previous versions. E.g, when + performing a C-ECHO, Orthanc will not propose C-MOVE or C-FIND. * Introduced a new thread to update the statistics at regular interval for the DB plugins that are implementing the UpdateAndGetStatistics function (currently only PostgreSQL). This avoids very long update times in case you don't call /statistics
--- a/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp Wed Nov 13 15:21:08 2024 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp Wed Nov 20 09:58:12 2024 +0100 @@ -234,21 +234,58 @@ - void DicomControlUserConnection::SetupPresentationContexts() // TODO-GET, setup only the presentation contexts that are enabled for that modality + void DicomControlUserConnection::SetupPresentationContexts( + ScuOperationFlags scuOperation, + const std::set<std::string>& acceptedStorageSopClasses, + const std::set<DicomTransferSyntax>& acceptedTransferSyntaxes) { assert(association_.get() != NULL); - association_->ProposeGenericPresentationContext(UID_VerificationSOPClass); - association_->ProposeGenericPresentationContext(UID_FINDPatientRootQueryRetrieveInformationModel); - association_->ProposeGenericPresentationContext(UID_MOVEPatientRootQueryRetrieveInformationModel); - association_->ProposeGenericPresentationContext(UID_FINDStudyRootQueryRetrieveInformationModel); - association_->ProposeGenericPresentationContext(UID_MOVEStudyRootQueryRetrieveInformationModel); - association_->ProposeGenericPresentationContext(UID_FINDModalityWorklistInformationModel); - association_->ProposeGenericPresentationContext(UID_GETStudyRootQueryRetrieveInformationModel); - association_->ProposeGenericPresentationContext(UID_GETPatientRootQueryRetrieveInformationModel); - - // for C-GET SCU, in order to receive the C-Store message TODO-GET: we need to refine this list based on what we know we are going to retrieve - association_->ProposeGenericPresentationContext(UID_ComputedRadiographyImageStorage, DicomAssociationRole_Scp); - association_->ProposeGenericPresentationContext(UID_MRImageStorage, DicomAssociationRole_Scp); + + if ((scuOperation & ScuOperationFlags_Echo) != 0) + { + association_->ProposeGenericPresentationContext(UID_VerificationSOPClass); + } + + if ((scuOperation & ScuOperationFlags_FindPatient) != 0) + { + association_->ProposeGenericPresentationContext(UID_FINDPatientRootQueryRetrieveInformationModel); + } + + if ((scuOperation & ScuOperationFlags_FindStudy) != 0) + { + association_->ProposeGenericPresentationContext(UID_FINDStudyRootQueryRetrieveInformationModel); + } + + if ((scuOperation & ScuOperationFlags_FindWorklist) != 0) + { + association_->ProposeGenericPresentationContext(UID_FINDModalityWorklistInformationModel); + } + + if ((scuOperation & ScuOperationFlags_MovePatient) != 0) + { + association_->ProposeGenericPresentationContext(UID_MOVEPatientRootQueryRetrieveInformationModel); + } + + if ((scuOperation & ScuOperationFlags_MoveStudy) != 0) + { + association_->ProposeGenericPresentationContext(UID_MOVEStudyRootQueryRetrieveInformationModel); + } + + if ((scuOperation & ScuOperationFlags_Get) != 0) + { + association_->ProposeGenericPresentationContext(UID_GETStudyRootQueryRetrieveInformationModel); + association_->ProposeGenericPresentationContext(UID_GETPatientRootQueryRetrieveInformationModel); + + if (acceptedStorageSopClasses.size() == 0) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); // the acceptedStorageSopClassUids should always be defined for a C-Get + } + + for (std::set<std::string>::const_iterator it = acceptedStorageSopClasses.begin(); it != acceptedStorageSopClasses.end(); ++it) + { + association_->ProposePresentationContext(*it, acceptedTransferSyntaxes, DicomAssociationRole_Scp); + } + } } @@ -665,11 +702,25 @@ } - DicomControlUserConnection::DicomControlUserConnection(const DicomAssociationParameters& params) : + DicomControlUserConnection::DicomControlUserConnection(const DicomAssociationParameters& params, ScuOperationFlags scuOperation) : parameters_(params), association_(new DicomAssociation) { - SetupPresentationContexts(); + assert((scuOperation & ScuOperationFlags_Get) == 0); // you must provide acceptedStorageSopClassUids for Get SCU + std::set<std::string> emptyStorageSopClasses; + std::set<DicomTransferSyntax> emptyStorageTransferSyntaxes; + + SetupPresentationContexts(scuOperation, emptyStorageSopClasses, emptyStorageTransferSyntaxes); + } + + DicomControlUserConnection::DicomControlUserConnection(const DicomAssociationParameters& params, + ScuOperationFlags scuOperation, + const std::set<std::string>& acceptedStorageSopClasses, + const std::set<DicomTransferSyntax>& acceptedTransferSyntaxes) : + parameters_(params), + association_(new DicomAssociation) + { + SetupPresentationContexts(scuOperation, acceptedStorageSopClasses, acceptedTransferSyntaxes); }
--- a/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.h Wed Nov 13 15:21:08 2024 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.h Wed Nov 20 09:58:12 2024 +0100 @@ -45,13 +45,31 @@ ); + enum ScuOperationFlags + { + ScuOperationFlags_Echo = 1 << 0, + ScuOperationFlags_FindPatient = 1 << 1, + ScuOperationFlags_FindStudy = 1 << 2, + ScuOperationFlags_FindWorklist = 1 << 3, + ScuOperationFlags_MoveStudy = 1 << 4, + ScuOperationFlags_MovePatient = 1 << 5, + // C-Store is not using DicomControlUserConnection but DicomStoreUserConnection + ScuOperationFlags_Get = 1 << 6, + + ScuOperationFlags_Find = ScuOperationFlags_FindPatient | ScuOperationFlags_FindStudy | ScuOperationFlags_FindWorklist, + ScuOperationFlags_Move = ScuOperationFlags_MoveStudy | ScuOperationFlags_MovePatient, + ScuOperationFlags_All = ScuOperationFlags_Echo | ScuOperationFlags_Find | ScuOperationFlags_Move | ScuOperationFlags_Get + }; + class DicomControlUserConnection : public boost::noncopyable { private: DicomAssociationParameters parameters_; boost::shared_ptr<DicomAssociation> association_; - void SetupPresentationContexts(); + void SetupPresentationContexts(ScuOperationFlags scuOperation, + const std::set<std::string>& acceptedStorageSopClasses, + const std::set<DicomTransferSyntax>& acceptedTransferSyntaxes); // TODO-GET: in order of preference ? void FindInternal(DicomFindAnswers& answers, DcmDataset* dataset, @@ -64,8 +82,14 @@ const DicomMap& fields); public: - explicit DicomControlUserConnection(const DicomAssociationParameters& params); - + explicit DicomControlUserConnection(const DicomAssociationParameters& params, ScuOperationFlags scuOperation); + + // specific constructor for CGet SCU + explicit DicomControlUserConnection(const DicomAssociationParameters& params, + ScuOperationFlags scuOperation, + const std::set<std::string>& acceptedStorageSopClasses, + const std::set<DicomTransferSyntax>& acceptedTransferSyntaxes); // TODO-GET: in order of preference ? + const DicomAssociationParameters& GetParameters() const { return parameters_;
--- a/OrthancFramework/Sources/DicomNetworking/IApplicationEntityFilter.h Wed Nov 13 15:21:08 2024 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/IApplicationEntityFilter.h Wed Nov 20 09:58:12 2024 +0100 @@ -55,5 +55,8 @@ virtual bool IsUnknownSopClassAccepted(const std::string& remoteIp, const std::string& remoteAet, const std::string& calledAet) = 0; + + virtual void GetAcceptedSopClasses(std::set<std::string>& sopClasses, + size_t maxCount) = 0; }; }
--- a/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp Wed Nov 13 15:21:08 2024 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp Wed Nov 20 09:58:12 2024 +0100 @@ -502,13 +502,21 @@ } else // see dcmqrsrv.cc lines 839 - 876 { + std::set<std::string> acceptedStorageClasses; + + if (server.HasApplicationEntityFilter()) + { + server.GetApplicationEntityFilter().GetAcceptedSopClasses(acceptedStorageClasses, 0); + } + /* accept storage syntaxes with proposed role */ int npc = ASC_countPresentationContexts(assoc->params); for (int i = 0; i < npc; i++) { T_ASC_PresentationContext pc; ASC_getPresentationContext(assoc->params, i, &pc); - if (dcmIsaStorageSOPClassUID(pc.abstractSyntax)) + if (acceptedStorageClasses.find(pc.abstractSyntax) != acceptedStorageClasses.end() + || (!server.HasApplicationEntityFilter() && dcmIsaStorageSOPClassUID(pc.abstractSyntax))) // previous behavior kept for compatibility in case the server does not have an ApplicationEntityFilter { /** * We are prepared to accept whatever role the caller
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/DicomFilter.cpp Wed Nov 13 15:21:08 2024 +0100 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/DicomFilter.cpp Wed Nov 20 09:58:12 2024 +0100 @@ -29,7 +29,7 @@ #include "../../../../OrthancFramework/Sources/OrthancException.h" #include "../Common/OrthancPluginCppWrapper.h" - +#include <dcmtk/dcmdata/dcuid.h> /* for variable dcmAllStorageSOPClassUIDs */ DicomFilter::DicomFilter() : hasAcceptedTransferSyntaxes_(false) @@ -41,6 +41,7 @@ alwaysAllowMove_ = config.GetBooleanValue("DicomAlwaysAllowMove", false); alwaysAllowStore_ = config.GetBooleanValue("DicomAlwaysAllowStore", true); unknownSopClassAccepted_ = config.GetBooleanValue("UnknownSopClassAccepted", false); + config.LookupSetOfStrings(acceptedStorageClasses_, "AcceptedSopClasses", false); isStrict_ = config.GetBooleanValue("StrictAetComparison", false); checkModalityHost_ = config.GetBooleanValue("DicomCheckModalityHost", false); } @@ -207,3 +208,44 @@ boost::shared_lock<boost::shared_mutex> lock(mutex_); return unknownSopClassAccepted_; } + + +void DicomFilter::GetAcceptedSopClasses(std::set<std::string>& sopClasses, size_t maxCount) +{ + boost::shared_lock<boost::shared_mutex> lock(mutex_); + + if (acceptedStorageClasses_.size() >= 0) + { + size_t count = 0; + std::set<std::string>::const_iterator it = acceptedStorageClasses_.begin(); + + while (it != acceptedStorageClasses_.end() && (maxCount == 0 || count < maxCount)) + { + sopClasses.insert(*it); + count++; + } + } + else + { + if (maxCount != 0) + { + size_t count = 0; + // we actually take a list of default 120 most common storage SOP classes defined in DCMTK + while (dcmLongSCUStorageSOPClassUIDs[count] != NULL && count < maxCount) + { + sopClasses.insert(dcmAllStorageSOPClassUIDs[count]); + count++; + } + } + else + { + size_t count = 0; + // we actually take all known storage SOP classes defined in DCMTK + while (dcmAllStorageSOPClassUIDs[count] != NULL) + { + sopClasses.insert(dcmAllStorageSOPClassUIDs[count]); + count++; + } + } + } +} \ No newline at end of file
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/DicomFilter.h Wed Nov 13 15:21:08 2024 +0100 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/DicomFilter.h Wed Nov 20 09:58:12 2024 +0100 @@ -44,6 +44,7 @@ bool hasAcceptedTransferSyntaxes_; std::set<Orthanc::DicomTransferSyntax> acceptedTransferSyntaxes_; + std::set<std::string> acceptedStorageClasses_; public: DicomFilter(); @@ -65,4 +66,6 @@ virtual bool IsUnknownSopClassAccepted(const std::string& remoteIp, const std::string& remoteAet, const std::string& calledAet) ORTHANC_OVERRIDE; + + virtual void GetAcceptedSopClasses(std::set<std::string>& sopClasses, size_t maxCount) ORTHANC_OVERRIDE; };
--- a/OrthancServer/Resources/Configuration.json Wed Nov 13 15:21:08 2024 +0100 +++ b/OrthancServer/Resources/Configuration.json Wed Nov 20 09:58:12 2024 +0100 @@ -202,6 +202,18 @@ // SOP classes (aka. "promiscuous mode") "UnknownSopClassAccepted" : false, + // The list of accepted Storage SOP classes. + // If empty or not defined, this list defaults + // to all DCMTK storage classes in case of C-STORE SCP + // and to a reduced list of 120 most standard storage + // classes in case of C-GET SCU. + // (new in Orthanc 1.12.6) + // "AcceptedSopClasses" : [ + // "1.2.840.10008.5.1.4.1.1.2", + // "1.2.840.10008.5.1.4.1.1.4" + // ] + + // Set the timeout (in seconds) after which the DICOM associations // are closed by the Orthanc SCP (server) if no further DIMSE // command is received from the SCU (client).
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp Wed Nov 13 15:21:08 2024 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp Wed Nov 20 09:58:12 2024 +0100 @@ -156,24 +156,31 @@ const DicomAssociationParameters& parameters, const Json::Value& body) { - DicomControlUserConnection connection(parameters); + bool checkFind = false; + + if (body.type() == Json::objectValue && + body.isMember(KEY_CHECK_FIND)) + { + checkFind = SerializationToolbox::ReadBoolean(body, KEY_CHECK_FIND); + } + else + { + OrthancConfiguration::ReaderLock lock; + checkFind = lock.GetConfiguration().GetBooleanParameter("DicomEchoChecksFind", false); + } + ScuOperationFlags operations = ScuOperationFlags_Echo; + + if (checkFind) + { + operations = static_cast<ScuOperationFlags>(operations | ScuOperationFlags_Find); + } + + DicomControlUserConnection connection(parameters, operations); if (connection.Echo()) { - bool find = false; - - if (body.type() == Json::objectValue && - body.isMember(KEY_CHECK_FIND)) - { - find = SerializationToolbox::ReadBoolean(body, KEY_CHECK_FIND); - } - else - { - OrthancConfiguration::ReaderLock lock; - find = lock.GetConfiguration().GetBooleanParameter("DicomEchoChecksFind", false); - } - if (find) + if (checkFind) { // Issue a C-FIND request at the study level about a random Study Instance UID const std::string studyInstanceUid = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study); @@ -386,7 +393,7 @@ DicomFindAnswers answers(false); { - DicomControlUserConnection connection(GetAssociationParameters(call)); + DicomControlUserConnection connection(GetAssociationParameters(call), ScuOperationFlags_FindPatient); FindPatient(answers, connection, fields); } @@ -429,7 +436,7 @@ DicomFindAnswers answers(false); { - DicomControlUserConnection connection(GetAssociationParameters(call)); + DicomControlUserConnection connection(GetAssociationParameters(call), ScuOperationFlags_FindStudy); FindStudy(answers, connection, fields); } @@ -473,7 +480,7 @@ DicomFindAnswers answers(false); { - DicomControlUserConnection connection(GetAssociationParameters(call)); + DicomControlUserConnection connection(GetAssociationParameters(call), ScuOperationFlags_FindStudy); FindSeries(answers, connection, fields); } @@ -518,7 +525,7 @@ DicomFindAnswers answers(false); { - DicomControlUserConnection connection(GetAssociationParameters(call)); + DicomControlUserConnection connection(GetAssociationParameters(call), ScuOperationFlags_FindStudy); FindInstance(answers, connection, fields); } @@ -567,7 +574,7 @@ return; } - DicomControlUserConnection connection(GetAssociationParameters(call)); + DicomControlUserConnection connection(GetAssociationParameters(call), ScuOperationFlags_Find); DicomFindAnswers patients(false); FindPatient(patients, connection, m); @@ -2288,7 +2295,7 @@ DicomFindAnswers answers(true); { - DicomControlUserConnection connection(GetAssociationParameters(call, json)); + DicomControlUserConnection connection(GetAssociationParameters(call, json), ScuOperationFlags_FindWorklist); connection.FindWorklist(answers, *query); }
--- a/OrthancServer/Sources/QueryRetrieveHandler.cpp Wed Nov 13 15:21:08 2024 +0100 +++ b/OrthancServer/Sources/QueryRetrieveHandler.cpp Wed Nov 20 09:58:12 2024 +0100 @@ -79,7 +79,13 @@ params.SetTimeout(timeout_); } - DicomControlUserConnection connection(params); + std::set<std::string> acceptedStorageClasses; + std::set<DicomTransferSyntax> acceptedTransferSyntaxes; + + context_.GetAcceptedSopClasses(acceptedStorageClasses, 120); // limit to 120 SOP Classes since there are 128 presentation contexts available. + context_.GetAcceptedTransferSyntaxes(acceptedTransferSyntaxes); + + DicomControlUserConnection connection(params, static_cast<ScuOperationFlags>(ScuOperationFlags_Find | ScuOperationFlags_Move | ScuOperationFlags_Get), acceptedStorageClasses, acceptedTransferSyntaxes); connection.Find(answers_, level_, fixed, findNormalized_); }
--- a/OrthancServer/Sources/ServerContext.cpp Wed Nov 13 15:21:08 2024 +0100 +++ b/OrthancServer/Sources/ServerContext.cpp Wed Nov 20 09:58:12 2024 +0100 @@ -49,6 +49,7 @@ #include <dcmtk/dcmdata/dcfilefo.h> #include <dcmtk/dcmnet/dimse.h> +#include <dcmtk/dcmdata/dcuid.h> /* for variable dcmAllStorageSOPClassUIDs */ #if HAVE_MALLOC_TRIM == 1 # include <malloc.h> @@ -485,6 +486,8 @@ lock.GetConfiguration().GetAcceptedTransferSyntaxes(acceptedTransferSyntaxes_); isUnknownSopClassAccepted_ = lock.GetConfiguration().GetBooleanParameter("UnknownSopClassAccepted", false); + + lock.GetConfiguration().GetSetOfStringsParameter(acceptedSopClasses_, "AcceptedSopClasses"); } jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200); @@ -2107,8 +2110,53 @@ } } - - void ServerContext::GetAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& syntaxes) + void ServerContext::SetAcceptedSopClasses(const std::set<std::string>& sopClasses) + { + boost::mutex::scoped_lock lock(dynamicOptionsMutex_); + acceptedSopClasses_ = sopClasses; + } + + void ServerContext::GetAcceptedSopClasses(std::set<std::string>& sopClasses, size_t maxCount) const + { + boost::mutex::scoped_lock lock(dynamicOptionsMutex_); + + if (acceptedSopClasses_.size() > 0) + { + size_t count = 0; + std::set<std::string>::const_iterator it = acceptedSopClasses_.begin(); + + while (it != acceptedSopClasses_.end() && (maxCount == 0 || count < maxCount)) + { + sopClasses.insert(*it); + count++; + } + } + else + { + if (maxCount != 0) + { + size_t count = 0; + // we actually take a list of default 120 most common storage SOP classes defined in DCMTK + while (dcmLongSCUStorageSOPClassUIDs[count] != NULL && count < maxCount) + { + sopClasses.insert(dcmAllStorageSOPClassUIDs[count]); + count++; + } + } + else + { + size_t count = 0; + // we actually take all known storage SOP classes defined in DCMTK + while (dcmAllStorageSOPClassUIDs[count] != NULL) + { + sopClasses.insert(dcmAllStorageSOPClassUIDs[count]); + count++; + } + } + } + } + + void ServerContext::GetAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& syntaxes) const { boost::mutex::scoped_lock lock(dynamicOptionsMutex_); syntaxes = acceptedTransferSyntaxes_; @@ -2122,7 +2170,7 @@ } - bool ServerContext::IsUnknownSopClassAccepted() + bool ServerContext::IsUnknownSopClassAccepted() const { boost::mutex::scoped_lock lock(dynamicOptionsMutex_); return isUnknownSopClassAccepted_;
--- a/OrthancServer/Sources/ServerContext.h Wed Nov 13 15:21:08 2024 +0100 +++ b/OrthancServer/Sources/ServerContext.h Wed Nov 20 09:58:12 2024 +0100 @@ -274,9 +274,10 @@ // New in Orthanc 1.9.0 DicomTransferSyntax preferredTransferSyntax_; - boost::mutex dynamicOptionsMutex_; + mutable boost::mutex dynamicOptionsMutex_; bool isUnknownSopClassAccepted_; std::set<DicomTransferSyntax> acceptedTransferSyntaxes_; + std::set<std::string> acceptedSopClasses_; StoreResult StoreAfterTranscoding(std::string& resultPublicId, DicomInstanceToStore& dicom, @@ -592,11 +593,15 @@ const std::string& GetDeidentifiedContent(const DicomElement& element) const; - void GetAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& syntaxes); + void GetAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& syntaxes) const; void SetAcceptedTransferSyntaxes(const std::set<DicomTransferSyntax>& syntaxes); - bool IsUnknownSopClassAccepted(); + void SetAcceptedSopClasses(const std::set<std::string>& sopClasses); + + void GetAcceptedSopClasses(std::set<std::string>& sopClasses, size_t maxCount) const; + + bool IsUnknownSopClassAccepted() const; void SetUnknownSopClassAccepted(bool accepted);
--- a/OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp Wed Nov 13 15:21:08 2024 +0100 +++ b/OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp Wed Nov 20 09:58:12 2024 +0100 @@ -112,7 +112,27 @@ { if (connection_.get() == NULL) { - connection_.reset(new DicomControlUserConnection(parameters_)); + std::set<std::string> storageSopClassUids; + std::set<DicomTransferSyntax> storageAcceptedTransferSyntaxes; + + if (findAnswer.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY)) + { + throw OrthancException(ErrorCode_NotImplemented); + // TODO-GET + } + else + { + // when we don't know what SOP Classes to use, we must limit to 120 SOP Classes because + // there are only 128 presentation contexts available + context_.GetAcceptedSopClasses(storageSopClassUids, 120); + } + + context_.GetAcceptedTransferSyntaxes(storageAcceptedTransferSyntaxes); + + connection_.reset(new DicomControlUserConnection(parameters_, + ScuOperationFlags_Get, + storageSopClassUids, + storageAcceptedTransferSyntaxes)); } connection_->Get(findAnswer, InstanceReceivedHandler, &context_);
--- a/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp Wed Nov 13 15:21:08 2024 +0100 +++ b/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp Wed Nov 20 09:58:12 2024 +0100 @@ -88,7 +88,7 @@ { if (connection_.get() == NULL) { - connection_.reset(new DicomControlUserConnection(parameters_)); + connection_.reset(new DicomControlUserConnection(parameters_, ScuOperationFlags_Move)); } connection_->Move(targetAet_, findAnswer);
--- a/OrthancServer/Sources/main.cpp Wed Nov 13 15:21:08 2024 +0100 +++ b/OrthancServer/Sources/main.cpp Wed Nov 20 09:58:12 2024 +0100 @@ -501,6 +501,12 @@ { return context_.IsUnknownSopClassAccepted(); } + + virtual void GetAcceptedSopClasses(std::set<std::string>& sopClasses, + size_t maxCount) ORTHANC_OVERRIDE + { + context_.GetAcceptedSopClasses(sopClasses, maxCount); + } };