18#include <kcenon/common/patterns/event_bus.h>
67 using namespace network::dimse;
71 case command_field::c_move_rq:
74 case command_field::c_get_rq:
80 "Expected C-MOVE-RQ or C-GET-RQ but received " +
86 return "Retrieve SCP";
120 using namespace network::dimse;
126 "No retrieve handler configured");
133 "No destination resolver configured for C-MOVE");
143 assoc, context_id, message_id,
144 sop_class_uid,
true, stats,
false);
149 if (dest_ae.empty()) {
153 dimse_message response{command_field::c_move_rsp, 0};
154 response.set_affected_sop_class_uid(sop_class_uid);
155 response.set_message_id_responded_to(message_id);
156 response.set_status(status_refused_move_destination_unknown);
158 return assoc.
send_dimse(context_id, response);
163 if (!dest_addr.has_value()) {
167 dimse_message response{command_field::c_move_rsp, 0};
168 response.set_affected_sop_class_uid(sop_class_uid);
169 response.set_message_id_responded_to(message_id);
170 response.set_status(status_refused_move_destination_unknown);
172 return assoc.
send_dimse(context_id, response);
179 const auto& query_keys = request.
dataset().value().get();
181 auto start_time = std::chrono::steady_clock::now();
187 kcenon::common::get_event_bus().publish(
193 static_cast<uint16_t
>(files.size())
199 stats.remaining =
static_cast<uint16_t
>(files.size());
200 bool was_cancelled =
false;
203 for (
const auto& file : files) {
206 was_cancelled =
true;
212 assoc, context_id, message_id,
213 sop_class_uid,
true, stats);
215 if (pending_result.is_err()) {
216 return pending_result;
223 status_code store_status = status_success;
231 assoc, context_id, file,
232 calling_ae, message_id);
252 auto end_time = std::chrono::steady_clock::now();
253 auto duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
254 end_time - start_time).count();
256 kcenon::common::get_event_bus().publish(
264 static_cast<uint64_t
>(duration_ms)
270 assoc, context_id, message_id,
271 sop_class_uid,
true, stats, was_cancelled);
283 using namespace network::dimse;
289 "No retrieve handler configured");
299 assoc, context_id, message_id,
300 sop_class_uid,
false, stats,
false);
307 const auto& query_keys = request.
dataset().value().get();
309 auto start_time = std::chrono::steady_clock::now();
315 kcenon::common::get_event_bus().publish(
321 static_cast<uint16_t
>(files.size())
327 stats.remaining =
static_cast<uint16_t
>(files.size());
328 bool was_cancelled =
false;
331 for (
const auto& file : files) {
334 was_cancelled =
true;
340 assoc, context_id, message_id,
341 sop_class_uid,
false, stats);
343 if (pending_result.is_err()) {
344 return pending_result;
349 status_code store_status = status_success;
354 assoc, context_id, file,
355 calling_ae, message_id);
359 auto file_sop_class = file.sop_class_uid();
360 auto file_sop_instance = file.sop_instance_uid();
363 dimse_message store_rq{command_field::c_store_rq, message_id};
364 store_rq.set_affected_sop_class_uid(file_sop_class);
365 store_rq.set_affected_sop_instance_uid(file_sop_instance);
366 store_rq.set_priority(priority_medium);
371 store_rq.command_set().set_string(
372 tag_move_originator_aet,
375 store_rq.command_set().set_numeric<uint16_t>(
376 tag_move_originator_message_id,
381 store_rq.set_dataset(file.dataset());
385 if (!store_context_id.has_value()) {
393 auto send_result = assoc.
send_dimse(store_context_id.value(), store_rq);
394 if (send_result.is_err()) {
402 if (recv_result.is_err()) {
408 auto& [recv_ctx, store_rsp] = recv_result.value();
409 store_status = store_rsp.status();
429 auto end_time = std::chrono::steady_clock::now();
430 auto duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
431 end_time - start_time).count();
433 kcenon::common::get_event_bus().publish(
441 static_cast<uint64_t
>(duration_ms)
447 assoc, context_id, message_id,
448 sop_class_uid,
false, stats, was_cancelled);
459 std::string_view sop_class_uid,
463 using namespace network::dimse;
466 auto cmd = is_move ? command_field::c_move_rsp : command_field::c_get_rsp;
467 dimse_message response{cmd, 0};
469 response.set_affected_sop_class_uid(sop_class_uid);
470 response.set_message_id_responded_to(message_id);
471 response.set_status(status_pending);
474 response.set_remaining_subops(stats.remaining);
475 response.set_completed_subops(stats.completed);
476 response.set_failed_subops(stats.failed);
477 response.set_warning_subops(stats.warning);
480 return assoc.
send_dimse(context_id, response);
487 std::string_view sop_class_uid,
490 bool was_cancelled) {
492 using namespace network::dimse;
495 auto cmd = is_move ? command_field::c_move_rsp : command_field::c_get_rsp;
496 dimse_message response{cmd, 0};
498 response.set_affected_sop_class_uid(sop_class_uid);
499 response.set_message_id_responded_to(message_id);
502 status_code final_status;
504 final_status = status_cancel;
505 }
else if (stats.failed > 0 && stats.completed == 0 && stats.warning == 0) {
507 final_status = status_refused_out_of_resources_subops;
508 }
else if (stats.failed > 0 || stats.warning > 0) {
510 final_status = status_warning_subops_complete_failures;
513 final_status = status_success;
516 response.set_status(final_status);
519 response.set_remaining_subops(stats.remaining);
520 response.set_completed_subops(stats.completed);
521 response.set_failed_subops(stats.failed);
522 response.set_warning_subops(stats.warning);
525 return assoc.
send_dimse(context_id, response);
Result< std::monostate > send_dimse(uint8_t context_id, const dimse::dimse_message &msg)
Send a DIMSE message.
std::string_view calling_ae() const noexcept
Get calling AE title.
Result< std::pair< uint8_t, dimse::dimse_message > > receive_dimse(duration timeout=default_timeout)
Receive a DIMSE message.
std::optional< uint8_t > accepted_context_id(std::string_view abstract_syntax) const
Get the presentation context ID for an abstract syntax.
auto message_id() const noexcept -> uint16_t
Get the message ID.
auto affected_sop_class_uid() const -> std::string
Get the Affected SOP Class UID.
auto command_set() noexcept -> core::dicom_dataset &
Get mutable reference to the command set.
auto has_dataset() const noexcept -> bool
Check if the message has an associated data set.
auto dataset() -> kcenon::pacs::Result< std::reference_wrapper< core::dicom_dataset > >
Get mutable reference to the data set.
auto command() const noexcept -> command_field
Get the command field.
std::string_view service_name() const noexcept override
Get the service name.
network::Result< std::monostate > handle_message(network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request) override
Handle an incoming DIMSE message (C-MOVE-RQ or C-GET-RQ)
std::string get_move_destination(const network::dimse::dimse_message &request) const
Get the Move Destination AE title from the request.
void set_destination_resolver(destination_resolver resolver)
Set the destination resolver function.
network::Result< std::monostate > send_pending_response(network::association &assoc, uint8_t context_id, uint16_t message_id, std::string_view sop_class_uid, bool is_move, const sub_operation_stats &stats)
Send a pending response with progress information.
std::atomic< size_t > get_operations_
size_t get_operations() const noexcept
Get total number of C-GET operations processed.
void reset_statistics() noexcept
Reset statistics counters.
size_t images_transferred() const noexcept
Get total number of images transferred.
void set_cancel_check(retrieve_cancel_check check)
Set the cancel check function.
retrieve_cancel_check cancel_check_
std::atomic< size_t > images_transferred_
std::vector< std::string > supported_sop_classes() const override
Get supported SOP Class UIDs.
store_sub_operation store_handler_
network::Result< std::monostate > handle_c_get(network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
Handle a C-GET request.
network::Result< std::monostate > send_final_response(network::association &assoc, uint8_t context_id, uint16_t message_id, std::string_view sop_class_uid, bool is_move, const sub_operation_stats &stats, bool was_cancelled)
Send a final response with completion status.
network::Result< std::monostate > handle_c_move(network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
Handle a C-MOVE request.
destination_resolver destination_resolver_
void set_retrieve_handler(retrieve_handler handler)
Set the retrieve handler function.
std::atomic< size_t > move_operations_
retrieve_handler retrieve_handler_
size_t move_operations() const noexcept
Get total number of C-MOVE operations processed.
void set_store_sub_operation(store_sub_operation handler)
Set the store sub-operation handler.
retrieve_scp(std::shared_ptr< di::ILogger > logger=nullptr)
Construct a Retrieve SCP with optional logger.
DIMSE command field enumeration.
Compile-time constants for commonly used DICOM tags.
DICOM event definitions for event-based communication.
@ US
Unsigned Short (2 bytes)
@ AE
Application Entity (16 chars max)
constexpr int retrieve_handler_not_set
constexpr int retrieve_missing_destination
constexpr int retrieve_unexpected_command
constexpr core::dicom_tag tag_move_destination
Move Destination (0000,0600) - AE.
constexpr bool is_success(storage_status status) noexcept
Check if the status indicates success.
std::function< std::optional< std::pair< std::string, uint16_t > >( const std::string &ae_title)> destination_resolver
Destination resolver function type.
std::function< network::dimse::status_code( network::association &assoc, uint8_t context_id, const core::dicom_file &file, const std::string &move_originator_ae, uint16_t move_originator_msg_id)> store_sub_operation
Store sub-operation function type.
std::function< std::vector< core::dicom_file >( const core::dicom_dataset &query_keys)> retrieve_handler
Retrieve handler function type.
std::function< bool()> retrieve_cancel_check
Cancel check function type.
constexpr bool is_warning(storage_status status) noexcept
Check if the status indicates a warning.
constexpr std::string_view study_root_move_sop_class_uid
Study Root Query/Retrieve Information Model - MOVE.
constexpr std::string_view study_root_get_sop_class_uid
Study Root Query/Retrieve Information Model - GET.
auto to_string(mpps_status status) -> std::string_view
Convert mpps_status to DICOM string representation.
constexpr std::string_view patient_root_move_sop_class_uid
Patient Root Query/Retrieve Information Model - MOVE.
constexpr std::string_view patient_root_get_sop_class_uid
Patient Root Query/Retrieve Information Model - GET.
VoidResult pacs_void_error(int code, const std::string &message, const std::string &details="")
Create a PACS void error result.
Result<T> type aliases and helpers for PACS system.
DICOM Retrieve SCP service (C-MOVE/C-GET handler)
Event published when a retrieve operation completes.
Event published when a retrieve operation (C-MOVE/C-GET) starts.
Statistics for C-MOVE/C-GET sub-operations.