# HG changeset patch # User Alain Mazy # Date 1733387405 -3600 # Node ID 63ea301075ef1052ed30608953a551877698dd37 # Parent dfd5effec064b273b6404be0fd9d95f41c00e218 RetrievMethod diff -r dfd5effec064 -r 63ea301075ef NEWS --- a/NEWS Tue Dec 03 15:20:55 2024 +0100 +++ b/NEWS Thu Dec 05 09:30:05 2024 +0100 @@ -8,7 +8,10 @@ - Added support for C-GET SCU. - Added a configuration "AcceptedSopClasses" and "RejectedSopClasses" to limit the SOP classes accepted by Orthanc when acting as C-STORE SCP. - + - New config option "DicomDefaultRetrieveMethod" to define wheter Orthanc uses C-MOVE or C-GET + to retrieve a resource after a C-Find (when calling /queries/.../retrieve). + This configuration can be overriden for each modality in "DicomModalities->..->RetrieveMethod". + Default value: "C-MOVE" to keep the backward compatibility. REST API -------- @@ -22,7 +25,8 @@ standard https://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.2.3.html. This has no impact on StoneViewer and OHIF. https://discourse.orthanc-server.org/t/dicomwebplugin-does-not-return-series-metadata-properly/5195 - +* /queries/../retrieve now accepts a new field in the payload: "RetrieveMethod" to define wheter + Orthanc uses C-MOVE or C-GET to retrieve the resource. Maintenance ----------- diff -r dfd5effec064 -r 63ea301075ef OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.cpp --- a/OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.cpp Tue Dec 03 15:20:55 2024 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.cpp Thu Dec 05 09:30:05 2024 +0100 @@ -50,6 +50,7 @@ static const char* KEY_USE_DICOM_TLS = "UseDicomTls"; static const char* KEY_LOCAL_AET = "LocalAet"; static const char* KEY_TIMEOUT = "Timeout"; +static const char* KEY_RETRIEVE_METHOD = "RetrieveMethod"; namespace Orthanc @@ -72,6 +73,7 @@ useDicomTls_ = false; localAet_.clear(); timeout_ = 0; + retrieveMethod_ = RetrieveMethod_SystemDefault; } @@ -308,6 +310,17 @@ { timeout_ = SerializationToolbox::ReadUnsignedInteger(serialized, KEY_TIMEOUT); } + + if (serialized.isMember(KEY_RETRIEVE_METHOD)) + { + retrieveMethod_ = StringToRetrieveMethod + (SerializationToolbox::ReadString(serialized, KEY_RETRIEVE_METHOD)); + } + else + { + retrieveMethod_ = RetrieveMethod_SystemDefault; + } + } @@ -427,6 +440,7 @@ target[KEY_USE_DICOM_TLS] = useDicomTls_; target[KEY_LOCAL_AET] = localAet_; target[KEY_TIMEOUT] = timeout_; + target[KEY_RETRIEVE_METHOD] = EnumerationToString(retrieveMethod_); } else { @@ -521,4 +535,14 @@ { return timeout_ != 0; } + + RetrieveMethod RemoteModalityParameters::GetRetrieveMethod() const + { + return retrieveMethod_; + } + + void RemoteModalityParameters::SetRetrieveMethod(RetrieveMethod retrieveMethod) + { + retrieveMethod_ = retrieveMethod; + } } diff -r dfd5effec064 -r 63ea301075ef OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.h --- a/OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.h Tue Dec 03 15:20:55 2024 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.h Thu Dec 05 09:30:05 2024 +0100 @@ -51,6 +51,7 @@ bool useDicomTls_; std::string localAet_; uint32_t timeout_; + RetrieveMethod retrieveMethod_; // New in Orthanc 1.12.6 void Clear(); @@ -118,5 +119,10 @@ uint32_t GetTimeout() const; bool HasTimeout() const; + + RetrieveMethod GetRetrieveMethod() const; + + void SetRetrieveMethod(RetrieveMethod retrieveMethod); + }; } diff -r dfd5effec064 -r 63ea301075ef OrthancFramework/Sources/Enumerations.cpp --- a/OrthancFramework/Sources/Enumerations.cpp Tue Dec 03 15:20:55 2024 +0100 +++ b/OrthancFramework/Sources/Enumerations.cpp Thu Dec 05 09:30:05 2024 +0100 @@ -2493,6 +2493,44 @@ } } + RetrieveMethod StringToRetrieveMethod(const std::string& str) + { + if (str == "C-MOVE") + { + return RetrieveMethod_Move; + } + else if (str == "C-GET") + { + return RetrieveMethod_Get; + } + else if (str == "SystemDefault") + { + return RetrieveMethod_SystemDefault; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "RetrieveMethod can be \"C-MOVE\", \"C-GET\" or \"SystemDefault\": " + str); + } + } + + const char* EnumerationToString(RetrieveMethod method) + { + switch (method) + { + case RetrieveMethod_Get: + return "C-GET"; + + case RetrieveMethod_Move: + return "C-MOVE"; + + case RetrieveMethod_SystemDefault: + return "SystemDefault"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } } diff -r dfd5effec064 -r 63ea301075ef OrthancFramework/Sources/Enumerations.h --- a/OrthancFramework/Sources/Enumerations.h Tue Dec 03 15:20:55 2024 +0100 +++ b/OrthancFramework/Sources/Enumerations.h Thu Dec 05 09:30:05 2024 +0100 @@ -793,6 +793,14 @@ ResourceType_Instance = 4 }; + enum RetrieveMethod // new in Orthanc 1.12.6 + { + RetrieveMethod_Move = 1, + RetrieveMethod_Get = 2, + + RetrieveMethod_SystemDefault = 65535 + }; + ORTHANC_PUBLIC const char* EnumerationToString(ErrorCode code); @@ -849,6 +857,9 @@ const char* EnumerationToString(DicomToJsonFormat format); ORTHANC_PUBLIC + const char* EnumerationToString(RetrieveMethod method); + + ORTHANC_PUBLIC Encoding StringToEncoding(const char* encoding); ORTHANC_PUBLIC @@ -947,4 +958,7 @@ ORTHANC_PUBLIC void GetAllDicomTransferSyntaxes(std::set& target); + + ORTHANC_PUBLIC + RetrieveMethod StringToRetrieveMethod(const std::string& str); } diff -r dfd5effec064 -r 63ea301075ef OrthancServer/Resources/Configuration.json --- a/OrthancServer/Resources/Configuration.json Tue Dec 03 15:20:55 2024 +0100 +++ b/OrthancServer/Resources/Configuration.json Thu Dec 05 09:30:05 2024 +0100 @@ -485,6 +485,10 @@ * for Orthanc when initiating an SCU to this very specific * modality. Similarly, "Timeout" allows one to overwrite the * global value "DicomScuTimeout" on a per-modality basis. + * + * The "RetrieveMethod" option allows one to overwrite the global + * "DicomDefaultRetrieveMethod" configuration option for this + * specific modality. (Allowed values: "C-MOVE" or "C-GET"). **/ //"untrusted" : { // "AET" : "ORTHANC", @@ -501,7 +505,8 @@ // "AllowTranscoding" : true, // new in 1.7.0 // "UseDicomTls" : false, // new in 1.9.0 // "LocalAet" : "HELLO", // new in 1.9.0 - // "Timeout" : 60 // new in 1.9.1 + // "Timeout" : 60, // new in 1.9.1 + // "RetrieveMethod": "C-MOVE" // new in 1.12.6 //} }, @@ -515,6 +520,13 @@ // accept C-FIND requests from Orthanc (new in Orthanc 1.8.1). "DicomEchoChecksFind" : false, + // Wheter Orthanc uses C-MOVE or C-GET to retrieve a resource after + // a C-Find (when calling /queries/.../retrieve). + // This configuration can be overriden for each modality by providing + // "RetrieveMethod" in the "DicomModalities" entry. + // (new in Orthanc 1.12.6) + "DicomDefaultRetrieveMethod" : "C-MOVE", + // The timeout (in seconds) after which the DICOM associations are // considered as closed by the Orthanc SCU (client) if the remote // DICOM SCP (server) does not answer. diff -r dfd5effec064 -r 63ea301075ef OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp Tue Dec 03 15:20:55 2024 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp Thu Dec 05 09:30:05 2024 +0100 @@ -57,6 +57,7 @@ static const char* const KEY_CHECK_FIND = "CheckFind"; static const char* const SOP_CLASS_UID = "SOPClassUID"; static const char* const SOP_INSTANCE_UID = "SOPInstanceUID"; + static const char* const KEY_RETRIEVE_METHOD = "RetrieveMethod"; static RemoteModalityParameters MyGetModalityUsingSymbolicName(const std::string& name) { @@ -914,57 +915,57 @@ } - static void SubmitGetScuJob(RestApiPostCall& call, - bool allAnswers, - size_t index) - { - ServerContext& context = OrthancRestApi::GetContext(call); - - int timeout = -1; - Json::Value body; - - if (call.ParseJsonRequest(body)) - { - timeout = Toolbox::GetJsonIntegerField(body, KEY_TIMEOUT, -1); - } + // static void SubmitGetScuJob(RestApiPostCall& call, + // bool allAnswers, + // size_t index) + // { + // ServerContext& context = OrthancRestApi::GetContext(call); + + // int timeout = -1; + // Json::Value body; + + // if (call.ParseJsonRequest(body)) + // { + // timeout = Toolbox::GetJsonIntegerField(body, KEY_TIMEOUT, -1); + // } - std::unique_ptr job(new DicomGetScuJob(context)); - job->SetQueryFormat(OrthancRestApi::GetDicomFormat(body, DicomToJsonFormat_Short)); + // std::unique_ptr job(new DicomGetScuJob(context)); + // job->SetQueryFormat(OrthancRestApi::GetDicomFormat(body, DicomToJsonFormat_Short)); - { - QueryAccessor query(call); - job->SetRemoteModality(query.GetHandler().GetRemoteModality()); - - if (timeout >= 0) - { - // New in Orthanc 1.7.0 - job->SetTimeout(static_cast(timeout)); - } - else if (query.GetHandler().HasTimeout()) - { - // New in Orthanc 1.9.1 - job->SetTimeout(query.GetHandler().GetTimeout()); - } - - LOG(WARNING) << "Driving C-Get SCU on remote modality " - << query.GetHandler().GetRemoteModality().GetApplicationEntityTitle(); - - if (allAnswers) - { - for (size_t i = 0; i < query.GetHandler().GetAnswersCount(); i++) - { - job->AddFindAnswer(query.GetHandler(), i); - } - } - else - { - job->AddFindAnswer(query.GetHandler(), index); - } - } - - OrthancRestApi::GetApi(call).SubmitCommandsJob - (call, job.release(), true /* synchronous by default */, body); - } + // { + // QueryAccessor query(call); + // job->SetRemoteModality(query.GetHandler().GetRemoteModality()); + + // if (timeout >= 0) + // { + // // New in Orthanc 1.7.0 + // job->SetTimeout(static_cast(timeout)); + // } + // else if (query.GetHandler().HasTimeout()) + // { + // // New in Orthanc 1.9.1 + // job->SetTimeout(query.GetHandler().GetTimeout()); + // } + + // LOG(WARNING) << "Driving C-Get SCU on remote modality " + // << query.GetHandler().GetRemoteModality().GetApplicationEntityTitle(); + + // if (allAnswers) + // { + // for (size_t i = 0; i < query.GetHandler().GetAnswersCount(); i++) + // { + // job->AddFindAnswer(query.GetHandler(), i); + // } + // } + // else + // { + // job->AddFindAnswer(query.GetHandler(), index); + // } + // } + + // OrthancRestApi::GetApi(call).SubmitCommandsJob + // (call, job.release(), true /* synchronous by default */, body); + // } static void SubmitRetrieveJob(RestApiPostCall& call, @@ -975,12 +976,25 @@ std::string targetAet; int timeout = -1; - + + QueryAccessor query(call); + + RetrieveMethod retrieveMethod = query.GetHandler().GetRemoteModality().GetRetrieveMethod(); + Json::Value body; if (call.ParseJsonRequest(body)) { + OrthancConfiguration::ReaderLock lock; + targetAet = Toolbox::GetJsonStringField(body, KEY_TARGET_AET, context.GetDefaultLocalApplicationEntityTitle()); timeout = Toolbox::GetJsonIntegerField(body, KEY_TIMEOUT, -1); + + std::string strRetrieveMethod = SerializationToolbox::ReadString(body, KEY_RETRIEVE_METHOD, ""); + + if (!strRetrieveMethod.empty()) + { + retrieveMethod = StringToRetrieveMethod(strRetrieveMethod); + } } else { @@ -995,45 +1009,96 @@ } } - std::unique_ptr job(new DicomMoveScuJob(context)); - job->SetQueryFormat(OrthancRestApi::GetDicomFormat(body, DicomToJsonFormat_Short)); - + if (retrieveMethod == RetrieveMethod_SystemDefault) { - QueryAccessor query(call); - job->SetTargetAet(targetAet); - job->SetLocalAet(query.GetHandler().GetLocalAet()); - job->SetRemoteModality(query.GetHandler().GetRemoteModality()); - - if (timeout >= 0) - { - // New in Orthanc 1.7.0 - job->SetTimeout(static_cast(timeout)); - } - else if (query.GetHandler().HasTimeout()) + retrieveMethod = context.GetDefaultDicomRetrieveMethod(); + } + + switch (retrieveMethod) + { + case RetrieveMethod_Move: { - // New in Orthanc 1.9.1 - job->SetTimeout(query.GetHandler().GetTimeout()); - } - - LOG(WARNING) << "Driving C-Move SCU on remote modality " - << query.GetHandler().GetRemoteModality().GetApplicationEntityTitle() - << " to target modality " << targetAet; - - if (allAnswers) - { - for (size_t i = 0; i < query.GetHandler().GetAnswersCount(); i++) + std::unique_ptr job(new DicomMoveScuJob(context)); + job->SetQueryFormat(OrthancRestApi::GetDicomFormat(body, DicomToJsonFormat_Short)); + + job->SetTargetAet(targetAet); + job->SetLocalAet(query.GetHandler().GetLocalAet()); + job->SetRemoteModality(query.GetHandler().GetRemoteModality()); + + // TODO: refactor in a base class for DicomGetScuJob and DicomMoveScuJob + if (timeout >= 0) + { + // New in Orthanc 1.7.0 + job->SetTimeout(static_cast(timeout)); + } + else if (query.GetHandler().HasTimeout()) + { + // New in Orthanc 1.9.1 + job->SetTimeout(query.GetHandler().GetTimeout()); + } + + LOG(WARNING) << "Driving C-Move SCU on remote modality " + << query.GetHandler().GetRemoteModality().GetApplicationEntityTitle() + << " to target modality " << targetAet; + + // TODO: refactor in a base class for DicomGetScuJob and DicomMoveScuJob + if (allAnswers) + { + for (size_t i = 0; i < query.GetHandler().GetAnswersCount(); i++) + { + job->AddFindAnswer(query.GetHandler(), i); + } + } + else { - job->AddFindAnswer(query.GetHandler(), i); + job->AddFindAnswer(query.GetHandler(), index); } - } - else + + OrthancRestApi::GetApi(call).SubmitCommandsJob + (call, job.release(), true /* synchronous by default */, body); + + }; break; + case RetrieveMethod_Get: { - job->AddFindAnswer(query.GetHandler(), index); - } + std::unique_ptr job(new DicomGetScuJob(context)); + job->SetQueryFormat(OrthancRestApi::GetDicomFormat(body, DicomToJsonFormat_Short)); + job->SetRemoteModality(query.GetHandler().GetRemoteModality()); + + // TODO: refactor in a base class for DicomGetScuJob and DicomMoveScuJob + if (timeout >= 0) + { + // New in Orthanc 1.7.0 + job->SetTimeout(static_cast(timeout)); + } + else if (query.GetHandler().HasTimeout()) + { + // New in Orthanc 1.9.1 + job->SetTimeout(query.GetHandler().GetTimeout()); + } + + LOG(WARNING) << "Driving C-Get SCU on remote modality " + << query.GetHandler().GetRemoteModality().GetApplicationEntityTitle(); + + // TODO: refactor in a base class for DicomGetScuJob and DicomMoveScuJob + if (allAnswers) + { + for (size_t i = 0; i < query.GetHandler().GetAnswersCount(); i++) + { + job->AddFindAnswer(query.GetHandler(), i); + } + } + else + { + job->AddFindAnswer(query.GetHandler(), index); + } + + OrthancRestApi::GetApi(call).SubmitCommandsJob + (call, job.release(), true /* synchronous by default */, body); + + }; break; + default: + throw OrthancException(ErrorCode_NotImplemented); } - - OrthancRestApi::GetApi(call).SubmitCommandsJob - (call, job.release(), true /* synchronous by default */, body); } @@ -1050,6 +1115,10 @@ "`DicomAet` configuration option.", false) .SetRequestField(KEY_TIMEOUT, RestApiCallDocumentation::Type_Number, "Timeout for the C-MOVE command, in seconds", false) + .SetRequestField(KEY_RETRIEVE_METHOD, RestApiCallDocumentation::Type_String, + "Force usage of C-MOVE or C-GET to retrieve the resource. If note defined in the payload, " + "the retrieve method is defined in the DicomDefaultRetrieveMethod configuration or in " + "DicomModalities->..->RetrieveMethod", false) .AddRequestType(MimeType_PlainText, "AET of the target modality"); } @@ -1060,8 +1129,8 @@ { DocumentRetrieveShared(call); call.GetDocumentation() - .SetSummary("Retrieve one answer with a C-MOVE SCU") - .SetDescription("Start a C-MOVE SCU command as a job, in order to retrieve one answer associated with the " + .SetSummary("Retrieve one answer with a C-MOVE or a C-GET SCU") + .SetDescription("Start a C-MOVE or a C-GET SCU command as a job, in order to retrieve one answer associated with the " "query/retrieve operation whose identifiers are provided in the URL: " "https://orthanc.uclouvain.be/book/users/rest.html#performing-retrieve-c-move") .SetUriArgument("index", "Index of the answer"); @@ -1073,40 +1142,6 @@ } - static void RetrieveOneAnswerWithGet(RestApiPostCall& call) - { - if (call.IsDocumentation()) - { - DocumentRetrieveShared(call); - call.GetDocumentation() - .SetSummary("Retrieve one answer with a C-GET SCU") - .SetDescription("Start a C-GET SCU command as a job, in order to retrieve one answer associated with the " - "query/retrieve operation whose identifiers are provided in the URL: " - "https://orthanc.uclouvain.be/book/users/rest.html#performing-retrieve-c-get") // TODO-GET: write doc - .SetUriArgument("index", "Index of the answer"); - return; - } - - size_t index = boost::lexical_cast(call.GetUriComponent("index", "")); - SubmitRetrieveJob(call, false, index); - } - - - static void RetrieveAllAnswersWithGet(RestApiPostCall& call) - { - if (call.IsDocumentation()) - { - DocumentRetrieveShared(call); - call.GetDocumentation() - .SetSummary("Retrieve all answers with C-GET SCU") - .SetDescription("Start a C-GET SCU command as a job, in order to retrieve all the answers associated with the " - "query/retrieve operation whose identifier is provided in the URL: " - "https://orthanc.uclouvain.be/book/users/rest.html#performing-retrieve-c-get"); - return; - } - - SubmitGetScuJob(call, true, 0); - } static void RetrieveAllAnswers(RestApiPostCall& call) @@ -2727,7 +2762,6 @@ Register("/queries/{id}/answers/{index}", ListQueryAnswerOperations); Register("/queries/{id}/answers/{index}/content", GetQueryOneAnswer); Register("/queries/{id}/answers/{index}/retrieve", RetrieveOneAnswer); - Register("/queries/{id}/answers/{index}/get", RetrieveOneAnswerWithGet); Register("/queries/{id}/answers/{index}/query-instances", QueryAnswerChildren); Register("/queries/{id}/answers/{index}/query-series", @@ -2738,7 +2772,6 @@ Register("/queries/{id}/modality", GetQueryModality); Register("/queries/{id}/query", GetQueryArguments); Register("/queries/{id}/retrieve", RetrieveAllAnswers); - Register("/queries/{id}/get", RetrieveAllAnswersWithGet); Register("/peers", ListPeers); Register("/peers/{id}", ListPeerOperations); diff -r dfd5effec064 -r 63ea301075ef OrthancServer/Sources/ServerContext.cpp --- a/OrthancServer/Sources/ServerContext.cpp Tue Dec 03 15:20:55 2024 +0100 +++ b/OrthancServer/Sources/ServerContext.cpp Thu Dec 05 09:30:05 2024 +0100 @@ -489,11 +489,14 @@ isUnknownSopClassAccepted_ = lock.GetConfiguration().GetBooleanParameter("UnknownSopClassAccepted", false); + // New options in Orthanc 1.12.6 std::list acceptedSopClasses; std::set rejectedSopClasses; lock.GetConfiguration().GetListOfStringsParameter(acceptedSopClasses, "AcceptedSopClasses"); lock.GetConfiguration().GetSetOfStringsParameter(rejectedSopClasses, "RejectSopClasses"); SetAcceptedSopClasses(acceptedSopClasses, rejectedSopClasses); + + defaultDicomRetrieveMethod_ = StringToRetrieveMethod(lock.GetConfiguration().GetStringParameter("DicomDefaultRetrieveMethod", "C-MOVE")); } jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200); diff -r dfd5effec064 -r 63ea301075ef OrthancServer/Sources/ServerContext.h --- a/OrthancServer/Sources/ServerContext.h Tue Dec 03 15:20:55 2024 +0100 +++ b/OrthancServer/Sources/ServerContext.h Thu Dec 05 09:30:05 2024 +0100 @@ -250,6 +250,7 @@ std::unique_ptr queryRetrieveArchive_; std::string defaultLocalAet_; + RetrieveMethod defaultDicomRetrieveMethod_; OrthancHttpHandler httpHandler_; bool saveJobs_; FindStorageAccessMode findStorageAccessMode_; @@ -434,6 +435,11 @@ return defaultLocalAet_; } + RetrieveMethod GetDefaultDicomRetrieveMethod() const + { + return defaultDicomRetrieveMethod_; + } + LuaScripting& GetLuaScripting() { return mainLua_;