PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
kcenon::pacs::services::ups_push_scu Class Reference

#include <ups_push_scu.h>

Collaboration diagram for kcenon::pacs::services::ups_push_scu:
Collaboration graph

Public Member Functions

 ups_push_scu (std::shared_ptr< di::ILogger > logger=nullptr)
 Construct UPS Push SCU with default configuration.
 
 ups_push_scu (const ups_push_scu_config &config, std::shared_ptr< di::ILogger > logger=nullptr)
 Construct UPS Push SCU with custom configuration.
 
 ~ups_push_scu ()=default
 
 ups_push_scu (const ups_push_scu &)=delete
 
ups_push_scuoperator= (const ups_push_scu &)=delete
 
 ups_push_scu (ups_push_scu &&)=delete
 
ups_push_scuoperator= (ups_push_scu &&)=delete
 
network::Result< ups_resultcreate (network::association &assoc, const ups_create_data &data)
 Create a new UPS workitem on the remote SCP (N-CREATE)
 
network::Result< ups_resultset (network::association &assoc, const ups_set_data &data)
 Modify an existing UPS workitem (N-SET)
 
network::Result< ups_resultget (network::association &assoc, const ups_get_data &data)
 Retrieve UPS workitem attributes from remote SCP (N-GET)
 
network::Result< ups_resultchange_state (network::association &assoc, const ups_change_state_data &data)
 Change UPS workitem state (N-ACTION Type 1)
 
network::Result< ups_resultrequest_cancel (network::association &assoc, const ups_request_cancel_data &data)
 Request cancellation of a UPS workitem (N-ACTION Type 3)
 
size_t creates_performed () const noexcept
 
size_t sets_performed () const noexcept
 
size_t gets_performed () const noexcept
 
size_t actions_performed () const noexcept
 
void reset_statistics () noexcept
 

Private Member Functions

core::dicom_dataset build_create_dataset (const ups_create_data &data) const
 
core::dicom_dataset build_change_state_dataset (const ups_change_state_data &data) const
 
core::dicom_dataset build_request_cancel_dataset (const ups_request_cancel_data &data) const
 
std::string generate_workitem_uid () const
 
uint16_t next_message_id () noexcept
 

Private Attributes

std::shared_ptr< di::ILoggerlogger_
 
ups_push_scu_config config_
 
std::atomic< uint16_t > message_id_counter_ {1}
 
std::atomic< size_t > creates_performed_ {0}
 
std::atomic< size_t > sets_performed_ {0}
 
std::atomic< size_t > gets_performed_ {0}
 
std::atomic< size_t > actions_performed_ {0}
 

Detailed Description

Definition at line 229 of file ups_push_scu.h.

Constructor & Destructor Documentation

◆ ups_push_scu() [1/4]

kcenon::pacs::services::ups_push_scu::ups_push_scu ( std::shared_ptr< di::ILogger > logger = nullptr)
explicit

Construct UPS Push SCU with default configuration.

Parameters
loggerLogger instance (nullptr uses null_logger)

Definition at line 78 of file ups_push_scu.cpp.

79 : logger_(logger ? std::move(logger) : di::null_logger()) {}
std::shared_ptr< di::ILogger > logger_
std::shared_ptr< ILogger > null_logger()
Get a shared null logger instance.
Definition ilogger.h:271

◆ ups_push_scu() [2/4]

kcenon::pacs::services::ups_push_scu::ups_push_scu ( const ups_push_scu_config & config,
std::shared_ptr< di::ILogger > logger = nullptr )
explicit

Construct UPS Push SCU with custom configuration.

Parameters
configConfiguration options
loggerLogger instance (nullptr uses null_logger)

Definition at line 81 of file ups_push_scu.cpp.

83 : logger_(logger ? std::move(logger) : di::null_logger()),
84 config_(config) {}

◆ ~ups_push_scu()

kcenon::pacs::services::ups_push_scu::~ups_push_scu ( )
default

◆ ups_push_scu() [3/4]

kcenon::pacs::services::ups_push_scu::ups_push_scu ( const ups_push_scu & )
delete

◆ ups_push_scu() [4/4]

kcenon::pacs::services::ups_push_scu::ups_push_scu ( ups_push_scu && )
delete

Member Function Documentation

◆ actions_performed()

size_t kcenon::pacs::services::ups_push_scu::actions_performed ( ) const
nodiscardnoexcept

Definition at line 579 of file ups_push_scu.cpp.

579 {
580 return actions_performed_.load(std::memory_order_relaxed);
581}
std::atomic< size_t > actions_performed_

References actions_performed_.

◆ build_change_state_dataset()

core::dicom_dataset kcenon::pacs::services::ups_push_scu::build_change_state_dataset ( const ups_change_state_data & data) const
nodiscardprivate

Definition at line 647 of file ups_push_scu.cpp.

648 {
649
650 using namespace core;
651 using namespace encoding;
652
653 dicom_dataset ds;
654
655 // Requested state
656 ds.set_string(ups_tags::procedure_step_state, vr_type::CS,
657 data.requested_state);
658
659 // Transaction UID
660 ds.set_string(ups_tags::transaction_uid, vr_type::UI,
661 data.transaction_uid);
662
663 return ds;
664}
constexpr core::dicom_tag transaction_uid
Transaction UID (0008,1195)
constexpr core::dicom_tag procedure_step_state
Procedure Step State (0074,1000)

References kcenon::pacs::services::ups_tags::procedure_step_state, kcenon::pacs::services::ups_change_state_data::requested_state, kcenon::pacs::services::ups_change_state_data::transaction_uid, and kcenon::pacs::services::ups_tags::transaction_uid.

Referenced by change_state().

Here is the caller graph for this function:

◆ build_create_dataset()

core::dicom_dataset kcenon::pacs::services::ups_push_scu::build_create_dataset ( const ups_create_data & data) const
nodiscardprivate

Definition at line 594 of file ups_push_scu.cpp.

595 {
596
597 using namespace core;
598 using namespace encoding;
599
600 dicom_dataset ds;
601
602 // Procedure Step State - always SCHEDULED for N-CREATE
603 ds.set_string(ups_tags::procedure_step_state, vr_type::CS, "SCHEDULED");
604
605 // Procedure Step Label (required)
606 if (!data.procedure_step_label.empty()) {
607 ds.set_string(ups_tags::procedure_step_label, vr_type::LO,
608 data.procedure_step_label);
609 }
610
611 // Worklist Label
612 if (!data.worklist_label.empty()) {
613 ds.set_string(ups_tags::worklist_label, vr_type::LO,
614 data.worklist_label);
615 }
616
617 // Priority
618 if (!data.priority.empty()) {
619 ds.set_string(ups_tags::scheduled_procedure_step_priority, vr_type::CS,
620 data.priority);
621 }
622
623 // Scheduled start datetime
624 if (!data.scheduled_start_datetime.empty()) {
625 ds.set_string(
626 dicom_tag{0x0040, 0x4005}, vr_type::DT,
627 data.scheduled_start_datetime);
628 }
629
630 // Expected completion datetime
631 if (!data.expected_completion_datetime.empty()) {
632 ds.set_string(
633 dicom_tag{0x0040, 0x4011}, vr_type::DT,
634 data.expected_completion_datetime);
635 }
636
637 // Scheduled Station Name
638 if (!data.scheduled_station_name.empty()) {
639 ds.set_string(
640 dicom_tag{0x0040, 0x4001}, vr_type::CS,
641 data.scheduled_station_name);
642 }
643
644 return ds;
645}
constexpr core::dicom_tag procedure_step_label
Procedure Step Label (0074,1204)
constexpr core::dicom_tag worklist_label
Worklist Label (0074,1202)
constexpr core::dicom_tag scheduled_procedure_step_priority
Scheduled Procedure Step Priority (0074,1200)

References kcenon::pacs::services::ups_create_data::expected_completion_datetime, kcenon::pacs::services::ups_create_data::priority, kcenon::pacs::services::ups_create_data::procedure_step_label, kcenon::pacs::services::ups_tags::procedure_step_label, kcenon::pacs::services::ups_tags::procedure_step_state, kcenon::pacs::services::ups_tags::scheduled_procedure_step_priority, kcenon::pacs::services::ups_create_data::scheduled_start_datetime, kcenon::pacs::services::ups_create_data::scheduled_station_name, kcenon::pacs::services::ups_create_data::worklist_label, and kcenon::pacs::services::ups_tags::worklist_label.

Referenced by create().

Here is the caller graph for this function:

◆ build_request_cancel_dataset()

core::dicom_dataset kcenon::pacs::services::ups_push_scu::build_request_cancel_dataset ( const ups_request_cancel_data & data) const
nodiscardprivate

Definition at line 666 of file ups_push_scu.cpp.

667 {
668
669 using namespace core;
670 using namespace encoding;
671
672 dicom_dataset ds;
673
674 if (!data.reason.empty()) {
675 ds.set_string(ups_tags::reason_for_cancellation, vr_type::LT,
676 data.reason);
677 }
678
679 return ds;
680}
constexpr core::dicom_tag reason_for_cancellation
Reason for Cancellation (0074,1238)

References kcenon::pacs::services::ups_request_cancel_data::reason, and kcenon::pacs::services::ups_tags::reason_for_cancellation.

Referenced by request_cancel().

Here is the caller graph for this function:

◆ change_state()

network::Result< ups_result > kcenon::pacs::services::ups_push_scu::change_state ( network::association & assoc,
const ups_change_state_data & data )
nodiscard

Change UPS workitem state (N-ACTION Type 1)

Requests a state transition for the specified workitem. Valid transitions: SCHEDULED→IN PROGRESS, IN PROGRESS→COMPLETED, IN PROGRESS→CANCELED, SCHEDULED→CANCELED.

Parameters
assocThe established association to use
dataThe state change data including Transaction UID
Returns
Result containing ups_result on success, or error

Definition at line 375 of file ups_push_scu.cpp.

377 {
378
379 using namespace network::dimse;
380
381 auto start_time = std::chrono::steady_clock::now();
382
383 if (!assoc.is_established()) {
386 "Association not established");
387 }
388
389 if (data.workitem_uid.empty()) {
392 "Workitem UID is required for state change");
393 }
394
395 if (data.transaction_uid.empty()) {
398 "Transaction UID is required for state change");
399 }
400
401 auto context_id = assoc.accepted_context_id(ups_push_sop_class_uid);
402 if (!context_id) {
405 "No accepted presentation context for UPS Push SOP Class");
406 }
407
408 // Build the N-ACTION request with action dataset
409 auto request = make_n_action_rq(
412 data.workitem_uid,
414
415 // Attach dataset with state and transaction UID
416 auto action_dataset = build_change_state_dataset(data);
417 request.set_dataset(std::move(action_dataset));
418
419 logger_->debug("Sending N-ACTION (Change State) for UPS workitem: " +
420 data.workitem_uid + " -> " + data.requested_state);
421
422 auto send_result = assoc.send_dimse(*context_id, request);
423 if (send_result.is_err()) {
424 logger_->error("Failed to send N-ACTION: " + send_result.error().message);
425 return send_result.error();
426 }
427
428 auto recv_result = assoc.receive_dimse(config_.timeout);
429 if (recv_result.is_err()) {
430 logger_->error("Failed to receive N-ACTION response: " +
431 recv_result.error().message);
432 return recv_result.error();
433 }
434
435 const auto& [recv_context_id, response] = recv_result.value();
436
437 if (response.command() != command_field::n_action_rsp) {
440 "Expected N-ACTION-RSP but received " +
441 std::string(to_string(response.command())));
442 }
443
444 auto end_time = std::chrono::steady_clock::now();
445 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
446 end_time - start_time);
447
448 ups_result result;
449 result.workitem_uid = data.workitem_uid;
450 result.status = static_cast<uint16_t>(response.status());
451 result.elapsed = elapsed;
452
453 if (response.command_set().contains(tag_error_comment)) {
454 result.error_comment = response.command_set().get_string(tag_error_comment);
455 }
456
457 actions_performed_.fetch_add(1, std::memory_order_relaxed);
458
459 if (result.is_success()) {
460 logger_->info("N-ACTION (Change State) successful for UPS workitem: " +
461 data.workitem_uid + " -> " + data.requested_state);
462 } else {
463 logger_->warn("N-ACTION returned status 0x" +
464 std::to_string(result.status) +
465 " for UPS workitem: " + data.workitem_uid);
466 }
467
468 return result;
469}
core::dicom_dataset build_change_state_dataset(const ups_change_state_data &data) const
constexpr int ups_unexpected_command
Definition result.h:210
constexpr int ups_context_not_accepted
Definition result.h:216
constexpr int ups_missing_transaction_uid
Definition result.h:215
constexpr int ups_missing_uid
Definition result.h:212
constexpr int association_not_established
Definition result.h:202
auto to_string(mpps_status status) -> std::string_view
Convert mpps_status to DICOM string representation.
Definition mpps_scp.h:60
constexpr std::string_view ups_push_sop_class_uid
UPS Push SOP Class UID (PS3.4 Table CC.2-1)
constexpr uint16_t ups_action_change_state
N-ACTION Type 1: Change UPS State (PS3.4 CC.2.4)
Result< T > pacs_error(int code, const std::string &message, const std::string &details="")
Create a PACS error result with module context.
Definition result.h:234
std::chrono::milliseconds timeout
Timeout for receiving DIMSE response.

References kcenon::pacs::network::association::accepted_context_id(), actions_performed_, kcenon::pacs::error_codes::association_not_established, build_change_state_dataset(), config_, kcenon::pacs::services::ups_result::elapsed, kcenon::pacs::services::ups_result::error_comment, kcenon::pacs::network::association::is_established(), kcenon::pacs::services::ups_result::is_success(), logger_, next_message_id(), kcenon::pacs::pacs_error(), kcenon::pacs::network::association::receive_dimse(), kcenon::pacs::services::ups_change_state_data::requested_state, kcenon::pacs::network::association::send_dimse(), kcenon::pacs::services::ups_result::status, kcenon::pacs::services::ups_push_scu_config::timeout, kcenon::pacs::services::to_string(), kcenon::pacs::services::ups_change_state_data::transaction_uid, kcenon::pacs::services::ups_action_change_state, kcenon::pacs::error_codes::ups_context_not_accepted, kcenon::pacs::error_codes::ups_missing_transaction_uid, kcenon::pacs::error_codes::ups_missing_uid, kcenon::pacs::services::ups_push_sop_class_uid, kcenon::pacs::error_codes::ups_unexpected_command, kcenon::pacs::services::ups_change_state_data::workitem_uid, and kcenon::pacs::services::ups_result::workitem_uid.

Here is the call graph for this function:

◆ create()

network::Result< ups_result > kcenon::pacs::services::ups_push_scu::create ( network::association & assoc,
const ups_create_data & data )
nodiscard

Create a new UPS workitem on the remote SCP (N-CREATE)

Creates a new workitem with SCHEDULED state. If workitem_uid is empty and auto_generate_uid is true, a unique UID will be generated.

Parameters
assocThe established association to use
dataThe workitem creation data
Returns
Result containing ups_result on success, or error

Definition at line 90 of file ups_push_scu.cpp.

92 {
93
94 using namespace network::dimse;
95
96 auto start_time = std::chrono::steady_clock::now();
97
98 // Verify association is established
99 if (!assoc.is_established()) {
102 "Association not established");
103 }
104
105 // Get accepted presentation context for UPS Push
106 auto context_id = assoc.accepted_context_id(ups_push_sop_class_uid);
107 if (!context_id) {
110 "No accepted presentation context for UPS Push SOP Class");
111 }
112
113 // Generate or use provided workitem UID
114 std::string uid = data.workitem_uid;
115 if (uid.empty() && config_.auto_generate_uid) {
117 }
118
119 if (uid.empty()) {
122 "Workitem UID is required");
123 }
124
125 // Build the creation dataset
126 auto dataset = build_create_dataset(data);
127
128 // Create the N-CREATE request
129 auto request = make_ups_n_create_rq(next_message_id(), uid, std::move(dataset));
130
131 logger_->debug("Sending N-CREATE request for UPS workitem: " + uid);
132
133 // Send the request
134 auto send_result = assoc.send_dimse(*context_id, request);
135 if (send_result.is_err()) {
136 logger_->error("Failed to send N-CREATE: " + send_result.error().message);
137 return send_result.error();
138 }
139
140 // Receive the response
141 auto recv_result = assoc.receive_dimse(config_.timeout);
142 if (recv_result.is_err()) {
143 logger_->error("Failed to receive N-CREATE response: " +
144 recv_result.error().message);
145 return recv_result.error();
146 }
147
148 const auto& [recv_context_id, response] = recv_result.value();
149
150 // Verify it's an N-CREATE response
151 if (response.command() != command_field::n_create_rsp) {
154 "Expected N-CREATE-RSP but received " +
155 std::string(to_string(response.command())));
156 }
157
158 auto end_time = std::chrono::steady_clock::now();
159 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
160 end_time - start_time);
161
162 // Build result
163 ups_result result;
164 result.workitem_uid = uid;
165 result.status = static_cast<uint16_t>(response.status());
166 result.elapsed = elapsed;
167
168 if (response.command_set().contains(tag_error_comment)) {
169 result.error_comment = response.command_set().get_string(tag_error_comment);
170 }
171
172 creates_performed_.fetch_add(1, std::memory_order_relaxed);
173
174 if (result.is_success()) {
175 logger_->info("N-CREATE successful for UPS workitem: " + uid);
176 } else {
177 logger_->warn("N-CREATE returned status 0x" +
178 std::to_string(result.status) +
179 " for UPS workitem: " + uid);
180 }
181
182 return result;
183}
core::dicom_dataset build_create_dataset(const ups_create_data &data) const
std::atomic< size_t > creates_performed_
bool auto_generate_uid
Auto-generate workitem UID if not provided.
std::string_view uid

References kcenon::pacs::network::association::accepted_context_id(), kcenon::pacs::error_codes::association_not_established, kcenon::pacs::services::ups_push_scu_config::auto_generate_uid, build_create_dataset(), config_, creates_performed_, kcenon::pacs::services::ups_result::elapsed, kcenon::pacs::services::ups_result::error_comment, generate_workitem_uid(), kcenon::pacs::network::association::is_established(), kcenon::pacs::services::ups_result::is_success(), logger_, next_message_id(), kcenon::pacs::pacs_error(), kcenon::pacs::network::association::receive_dimse(), kcenon::pacs::network::association::send_dimse(), kcenon::pacs::services::ups_result::status, kcenon::pacs::services::ups_push_scu_config::timeout, kcenon::pacs::services::to_string(), uid, kcenon::pacs::error_codes::ups_context_not_accepted, kcenon::pacs::error_codes::ups_missing_uid, kcenon::pacs::services::ups_push_sop_class_uid, kcenon::pacs::error_codes::ups_unexpected_command, kcenon::pacs::services::ups_create_data::workitem_uid, and kcenon::pacs::services::ups_result::workitem_uid.

Referenced by operator=().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ creates_performed()

size_t kcenon::pacs::services::ups_push_scu::creates_performed ( ) const
nodiscardnoexcept

Definition at line 567 of file ups_push_scu.cpp.

567 {
568 return creates_performed_.load(std::memory_order_relaxed);
569}

References creates_performed_.

◆ generate_workitem_uid()

std::string kcenon::pacs::services::ups_push_scu::generate_workitem_uid ( ) const
nodiscardprivate

Definition at line 686 of file ups_push_scu.cpp.

686 {
687 static std::mt19937_64 gen{std::random_device{}()};
688 static std::uniform_int_distribution<uint64_t> dist;
689
690 auto now = std::chrono::system_clock::now();
691 auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
692 now.time_since_epoch()).count();
693
694 return std::string(uid_root) + "." + std::to_string(timestamp) +
695 "." + std::to_string(dist(gen) % 100000);
696}

Referenced by create().

Here is the caller graph for this function:

◆ get()

network::Result< ups_result > kcenon::pacs::services::ups_push_scu::get ( network::association & assoc,
const ups_get_data & data )
nodiscard

Retrieve UPS workitem attributes from remote SCP (N-GET)

Parameters
assocThe established association to use
dataThe query data including workitem UID and optional tags
Returns
Result containing ups_result with attributes on success

Definition at line 276 of file ups_push_scu.cpp.

278 {
279
280 using namespace network::dimse;
281
282 auto start_time = std::chrono::steady_clock::now();
283
284 if (!assoc.is_established()) {
287 "Association not established");
288 }
289
290 if (data.workitem_uid.empty()) {
293 "Workitem UID is required for N-GET");
294 }
295
296 auto context_id = assoc.accepted_context_id(ups_push_sop_class_uid);
297 if (!context_id) {
300 "No accepted presentation context for UPS Push SOP Class");
301 }
302
303 // Use the global factory function for N-GET
304 auto request = make_n_get_rq(
307 data.workitem_uid,
308 data.attribute_tags);
309
310 logger_->debug("Sending N-GET request for UPS workitem: " + data.workitem_uid +
311 " (attributes: " +
312 (data.attribute_tags.empty() ? "all" :
313 std::to_string(data.attribute_tags.size())) + ")");
314
315 auto send_result = assoc.send_dimse(*context_id, request);
316 if (send_result.is_err()) {
317 logger_->error("Failed to send N-GET: " + send_result.error().message);
318 return send_result.error();
319 }
320
321 auto recv_result = assoc.receive_dimse(config_.timeout);
322 if (recv_result.is_err()) {
323 logger_->error("Failed to receive N-GET response: " +
324 recv_result.error().message);
325 return recv_result.error();
326 }
327
328 const auto& [recv_context_id, response] = recv_result.value();
329
330 if (response.command() != command_field::n_get_rsp) {
333 "Expected N-GET-RSP but received " +
334 std::string(to_string(response.command())));
335 }
336
337 auto end_time = std::chrono::steady_clock::now();
338 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
339 end_time - start_time);
340
341 ups_result result;
342 result.workitem_uid = data.workitem_uid;
343 result.status = static_cast<uint16_t>(response.status());
344 result.elapsed = elapsed;
345
346 if (response.command_set().contains(tag_error_comment)) {
347 result.error_comment = response.command_set().get_string(tag_error_comment);
348 }
349
350 // Extract returned attributes from response dataset
351 if (response.has_dataset()) {
352 auto dataset_result = response.dataset();
353 if (dataset_result.is_ok()) {
354 result.attributes = dataset_result.value().get();
355 }
356 }
357
358 gets_performed_.fetch_add(1, std::memory_order_relaxed);
359
360 if (result.is_success()) {
361 logger_->info("N-GET successful for UPS workitem: " + data.workitem_uid);
362 } else {
363 logger_->warn("N-GET returned status 0x" +
364 std::to_string(result.status) +
365 " for UPS workitem: " + data.workitem_uid);
366 }
367
368 return result;
369}
std::atomic< size_t > gets_performed_

References kcenon::pacs::network::association::accepted_context_id(), kcenon::pacs::error_codes::association_not_established, kcenon::pacs::services::ups_get_data::attribute_tags, kcenon::pacs::services::ups_result::attributes, config_, kcenon::pacs::services::ups_result::elapsed, kcenon::pacs::services::ups_result::error_comment, kcenon::pacs::core::dicom_dataset::get(), gets_performed_, kcenon::pacs::network::association::is_established(), kcenon::pacs::services::ups_result::is_success(), logger_, next_message_id(), kcenon::pacs::pacs_error(), kcenon::pacs::network::association::receive_dimse(), kcenon::pacs::network::association::send_dimse(), kcenon::pacs::services::ups_result::status, kcenon::pacs::services::ups_push_scu_config::timeout, kcenon::pacs::services::to_string(), kcenon::pacs::error_codes::ups_context_not_accepted, kcenon::pacs::error_codes::ups_missing_uid, kcenon::pacs::services::ups_push_sop_class_uid, kcenon::pacs::error_codes::ups_unexpected_command, kcenon::pacs::services::ups_get_data::workitem_uid, and kcenon::pacs::services::ups_result::workitem_uid.

Here is the call graph for this function:

◆ gets_performed()

size_t kcenon::pacs::services::ups_push_scu::gets_performed ( ) const
nodiscardnoexcept

Definition at line 575 of file ups_push_scu.cpp.

575 {
576 return gets_performed_.load(std::memory_order_relaxed);
577}

References gets_performed_.

◆ next_message_id()

uint16_t kcenon::pacs::services::ups_push_scu::next_message_id ( )
nodiscardprivatenoexcept

Definition at line 698 of file ups_push_scu.cpp.

698 {
699 uint16_t id = message_id_counter_.fetch_add(1, std::memory_order_relaxed);
700 if (id == 0) {
701 id = message_id_counter_.fetch_add(1, std::memory_order_relaxed);
702 }
703 return id;
704}
std::atomic< uint16_t > message_id_counter_
@ id
Implant Displaced (alternate code)

References message_id_counter_.

Referenced by change_state(), create(), get(), request_cancel(), and set().

Here is the caller graph for this function:

◆ operator=() [1/2]

ups_push_scu & kcenon::pacs::services::ups_push_scu::operator= ( const ups_push_scu & )
delete

◆ operator=() [2/2]

ups_push_scu & kcenon::pacs::services::ups_push_scu::operator= ( ups_push_scu && )
delete

References create(), and set().

Here is the call graph for this function:

◆ request_cancel()

network::Result< ups_result > kcenon::pacs::services::ups_push_scu::request_cancel ( network::association & assoc,
const ups_request_cancel_data & data )
nodiscard

Request cancellation of a UPS workitem (N-ACTION Type 3)

Parameters
assocThe established association to use
dataThe cancellation request data
Returns
Result containing ups_result on success, or error

Definition at line 471 of file ups_push_scu.cpp.

473 {
474
475 using namespace network::dimse;
476
477 auto start_time = std::chrono::steady_clock::now();
478
479 if (!assoc.is_established()) {
482 "Association not established");
483 }
484
485 if (data.workitem_uid.empty()) {
488 "Workitem UID is required for cancel request");
489 }
490
491 auto context_id = assoc.accepted_context_id(ups_push_sop_class_uid);
492 if (!context_id) {
495 "No accepted presentation context for UPS Push SOP Class");
496 }
497
498 // Build the N-ACTION request for cancel
499 auto request = make_n_action_rq(
502 data.workitem_uid,
504
505 // Attach dataset with cancellation reason if provided
506 if (!data.reason.empty()) {
507 auto cancel_dataset = build_request_cancel_dataset(data);
508 request.set_dataset(std::move(cancel_dataset));
509 }
510
511 logger_->debug("Sending N-ACTION (Request Cancel) for UPS workitem: " +
512 data.workitem_uid);
513
514 auto send_result = assoc.send_dimse(*context_id, request);
515 if (send_result.is_err()) {
516 logger_->error("Failed to send N-ACTION: " + send_result.error().message);
517 return send_result.error();
518 }
519
520 auto recv_result = assoc.receive_dimse(config_.timeout);
521 if (recv_result.is_err()) {
522 logger_->error("Failed to receive N-ACTION response: " +
523 recv_result.error().message);
524 return recv_result.error();
525 }
526
527 const auto& [recv_context_id, response] = recv_result.value();
528
529 if (response.command() != command_field::n_action_rsp) {
532 "Expected N-ACTION-RSP but received " +
533 std::string(to_string(response.command())));
534 }
535
536 auto end_time = std::chrono::steady_clock::now();
537 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
538 end_time - start_time);
539
540 ups_result result;
541 result.workitem_uid = data.workitem_uid;
542 result.status = static_cast<uint16_t>(response.status());
543 result.elapsed = elapsed;
544
545 if (response.command_set().contains(tag_error_comment)) {
546 result.error_comment = response.command_set().get_string(tag_error_comment);
547 }
548
549 actions_performed_.fetch_add(1, std::memory_order_relaxed);
550
551 if (result.is_success()) {
552 logger_->info("N-ACTION (Request Cancel) successful for UPS workitem: " +
553 data.workitem_uid);
554 } else {
555 logger_->warn("N-ACTION (Request Cancel) returned status 0x" +
556 std::to_string(result.status) +
557 " for UPS workitem: " + data.workitem_uid);
558 }
559
560 return result;
561}
core::dicom_dataset build_request_cancel_dataset(const ups_request_cancel_data &data) const
constexpr uint16_t ups_action_request_cancel
N-ACTION Type 3: Request Cancellation (PS3.4 CC.2.5)

References kcenon::pacs::network::association::accepted_context_id(), actions_performed_, kcenon::pacs::error_codes::association_not_established, build_request_cancel_dataset(), config_, kcenon::pacs::services::ups_result::elapsed, kcenon::pacs::services::ups_result::error_comment, kcenon::pacs::network::association::is_established(), kcenon::pacs::services::ups_result::is_success(), logger_, next_message_id(), kcenon::pacs::pacs_error(), kcenon::pacs::services::ups_request_cancel_data::reason, kcenon::pacs::network::association::receive_dimse(), kcenon::pacs::network::association::send_dimse(), kcenon::pacs::services::ups_result::status, kcenon::pacs::services::ups_push_scu_config::timeout, kcenon::pacs::services::to_string(), kcenon::pacs::services::ups_action_request_cancel, kcenon::pacs::error_codes::ups_context_not_accepted, kcenon::pacs::error_codes::ups_missing_uid, kcenon::pacs::services::ups_push_sop_class_uid, kcenon::pacs::error_codes::ups_unexpected_command, kcenon::pacs::services::ups_request_cancel_data::workitem_uid, and kcenon::pacs::services::ups_result::workitem_uid.

Here is the call graph for this function:

◆ reset_statistics()

void kcenon::pacs::services::ups_push_scu::reset_statistics ( )
noexcept

Definition at line 583 of file ups_push_scu.cpp.

583 {
584 creates_performed_.store(0, std::memory_order_relaxed);
585 sets_performed_.store(0, std::memory_order_relaxed);
586 gets_performed_.store(0, std::memory_order_relaxed);
587 actions_performed_.store(0, std::memory_order_relaxed);
588}
std::atomic< size_t > sets_performed_

References actions_performed_, creates_performed_, gets_performed_, and sets_performed_.

◆ set()

network::Result< ups_result > kcenon::pacs::services::ups_push_scu::set ( network::association & assoc,
const ups_set_data & data )
nodiscard

Modify an existing UPS workitem (N-SET)

Parameters
assocThe established association to use
dataThe modification data including workitem UID and dataset
Returns
Result containing ups_result on success, or error

Definition at line 189 of file ups_push_scu.cpp.

191 {
192
193 using namespace network::dimse;
194
195 auto start_time = std::chrono::steady_clock::now();
196
197 if (!assoc.is_established()) {
200 "Association not established");
201 }
202
203 if (data.workitem_uid.empty()) {
206 "Workitem UID is required for N-SET");
207 }
208
209 auto context_id = assoc.accepted_context_id(ups_push_sop_class_uid);
210 if (!context_id) {
213 "No accepted presentation context for UPS Push SOP Class");
214 }
215
216 // Create the N-SET request with modification dataset
217 auto request = make_ups_n_set_rq(
219 data.workitem_uid,
220 core::dicom_dataset(data.modifications));
221
222 logger_->debug("Sending N-SET request for UPS workitem: " + data.workitem_uid);
223
224 auto send_result = assoc.send_dimse(*context_id, request);
225 if (send_result.is_err()) {
226 logger_->error("Failed to send N-SET: " + send_result.error().message);
227 return send_result.error();
228 }
229
230 auto recv_result = assoc.receive_dimse(config_.timeout);
231 if (recv_result.is_err()) {
232 logger_->error("Failed to receive N-SET response: " +
233 recv_result.error().message);
234 return recv_result.error();
235 }
236
237 const auto& [recv_context_id, response] = recv_result.value();
238
239 if (response.command() != command_field::n_set_rsp) {
242 "Expected N-SET-RSP but received " +
243 std::string(to_string(response.command())));
244 }
245
246 auto end_time = std::chrono::steady_clock::now();
247 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
248 end_time - start_time);
249
250 ups_result result;
251 result.workitem_uid = data.workitem_uid;
252 result.status = static_cast<uint16_t>(response.status());
253 result.elapsed = elapsed;
254
255 if (response.command_set().contains(tag_error_comment)) {
256 result.error_comment = response.command_set().get_string(tag_error_comment);
257 }
258
259 sets_performed_.fetch_add(1, std::memory_order_relaxed);
260
261 if (result.is_success()) {
262 logger_->info("N-SET successful for UPS workitem: " + data.workitem_uid);
263 } else {
264 logger_->warn("N-SET returned status 0x" +
265 std::to_string(result.status) +
266 " for UPS workitem: " + data.workitem_uid);
267 }
268
269 return result;
270}

References kcenon::pacs::network::association::accepted_context_id(), kcenon::pacs::error_codes::association_not_established, config_, kcenon::pacs::services::ups_result::elapsed, kcenon::pacs::services::ups_result::error_comment, kcenon::pacs::network::association::is_established(), kcenon::pacs::services::ups_result::is_success(), logger_, kcenon::pacs::services::ups_set_data::modifications, next_message_id(), kcenon::pacs::pacs_error(), kcenon::pacs::network::association::receive_dimse(), kcenon::pacs::network::association::send_dimse(), sets_performed_, kcenon::pacs::services::ups_result::status, kcenon::pacs::services::ups_push_scu_config::timeout, kcenon::pacs::services::to_string(), kcenon::pacs::error_codes::ups_context_not_accepted, kcenon::pacs::error_codes::ups_missing_uid, kcenon::pacs::services::ups_push_sop_class_uid, kcenon::pacs::error_codes::ups_unexpected_command, kcenon::pacs::services::ups_result::workitem_uid, and kcenon::pacs::services::ups_set_data::workitem_uid.

Referenced by operator=().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ sets_performed()

size_t kcenon::pacs::services::ups_push_scu::sets_performed ( ) const
nodiscardnoexcept

Definition at line 571 of file ups_push_scu.cpp.

571 {
572 return sets_performed_.load(std::memory_order_relaxed);
573}

References sets_performed_.

Member Data Documentation

◆ actions_performed_

std::atomic<size_t> kcenon::pacs::services::ups_push_scu::actions_performed_ {0}
private

Definition at line 376 of file ups_push_scu.h.

376{0};

Referenced by actions_performed(), change_state(), request_cancel(), and reset_statistics().

◆ config_

ups_push_scu_config kcenon::pacs::services::ups_push_scu::config_
private

Definition at line 371 of file ups_push_scu.h.

Referenced by change_state(), create(), get(), request_cancel(), and set().

◆ creates_performed_

std::atomic<size_t> kcenon::pacs::services::ups_push_scu::creates_performed_ {0}
private

Definition at line 373 of file ups_push_scu.h.

373{0};

Referenced by create(), creates_performed(), and reset_statistics().

◆ gets_performed_

std::atomic<size_t> kcenon::pacs::services::ups_push_scu::gets_performed_ {0}
private

Definition at line 375 of file ups_push_scu.h.

375{0};

Referenced by get(), gets_performed(), and reset_statistics().

◆ logger_

std::shared_ptr<di::ILogger> kcenon::pacs::services::ups_push_scu::logger_
private

Definition at line 370 of file ups_push_scu.h.

Referenced by change_state(), create(), get(), request_cancel(), and set().

◆ message_id_counter_

std::atomic<uint16_t> kcenon::pacs::services::ups_push_scu::message_id_counter_ {1}
private

Definition at line 372 of file ups_push_scu.h.

372{1};

Referenced by next_message_id().

◆ sets_performed_

std::atomic<size_t> kcenon::pacs::services::ups_push_scu::sets_performed_ {0}
private

Definition at line 374 of file ups_push_scu.h.

374{0};

Referenced by reset_statistics(), set(), and sets_performed().


The documentation for this class was generated from the following files: