PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
n_get_scu.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
14
18
19namespace kcenon::pacs::services {
20
21// =============================================================================
22// Construction
23// =============================================================================
24
25n_get_scu::n_get_scu(std::shared_ptr<di::ILogger> logger)
26 : logger_(logger ? std::move(logger) : di::null_logger()) {}
27
29 std::shared_ptr<di::ILogger> logger)
30 : logger_(logger ? std::move(logger) : di::null_logger()),
31 config_(config) {}
32
33// =============================================================================
34// N-GET Operation
35// =============================================================================
36
39 std::string_view sop_class_uid,
40 std::string_view sop_instance_uid,
41 const std::vector<core::dicom_tag>& attribute_tags) {
42
43 using namespace network::dimse;
44
45 auto start_time = std::chrono::steady_clock::now();
46
47 // Verify association is established
48 if (!assoc.is_established()) {
51 "Association not established");
52 }
53
54 // Validate UIDs
55 if (sop_class_uid.empty()) {
58 "SOP Class UID is required for N-GET");
59 }
60
61 if (sop_instance_uid.empty()) {
64 "SOP Instance UID is required for N-GET");
65 }
66
67 // Get accepted presentation context
68 auto context_id = assoc.accepted_context_id(sop_class_uid);
69 if (!context_id) {
72 "No accepted presentation context for SOP Class: " +
73 std::string(sop_class_uid));
74 }
75
76 // Build N-GET request using factory function
77 auto request = make_n_get_rq(
78 next_message_id(), sop_class_uid, sop_instance_uid, attribute_tags);
79
80 logger_->debug("Sending N-GET request for instance: " +
81 std::string(sop_instance_uid) +
82 " (attributes: " +
83 (attribute_tags.empty() ? "all" :
84 std::to_string(attribute_tags.size())) + ")");
85
86 // Send the request
87 auto send_result = assoc.send_dimse(*context_id, request);
88 if (send_result.is_err()) {
89 logger_->error("Failed to send N-GET: " + send_result.error().message);
90 return send_result.error();
91 }
92
93 // Receive the response
94 auto recv_result = assoc.receive_dimse(config_.timeout);
95 if (recv_result.is_err()) {
96 logger_->error("Failed to receive N-GET response: " +
97 recv_result.error().message);
98 return recv_result.error();
99 }
100
101 const auto& [recv_context_id, response] = recv_result.value();
102
103 // Verify it's an N-GET response
104 if (response.command() != command_field::n_get_rsp) {
107 "Expected N-GET-RSP but received " +
108 std::string(to_string(response.command())));
109 }
110
111 auto end_time = std::chrono::steady_clock::now();
112 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
113 end_time - start_time);
114
115 // Build result
116 n_get_result result;
117 result.status = static_cast<uint16_t>(response.status());
118 result.elapsed = elapsed;
119
120 // Extract error comment if present
121 if (response.command_set().contains(tag_error_comment)) {
122 result.error_comment = response.command_set().get_string(
123 tag_error_comment);
124 }
125
126 // Extract dataset (returned attributes) if present
127 if (response.has_dataset()) {
128 auto dataset_result = response.dataset();
129 if (dataset_result.is_ok()) {
130 result.attributes = dataset_result.value().get();
131 }
132 }
133
134 // Update statistics
135 gets_performed_.fetch_add(1, std::memory_order_relaxed);
136
137 if (result.is_success()) {
138 logger_->info("N-GET successful for instance: " +
139 std::string(sop_instance_uid));
140 } else {
141 logger_->warn("N-GET returned status 0x" +
142 std::to_string(result.status) +
143 " for instance: " + std::string(sop_instance_uid));
144 }
145
146 return result;
147}
148
149// =============================================================================
150// Statistics
151// =============================================================================
152
153size_t n_get_scu::gets_performed() const noexcept {
154 return gets_performed_.load(std::memory_order_relaxed);
155}
156
158 gets_performed_.store(0, std::memory_order_relaxed);
159}
160
161// =============================================================================
162// Private Implementation
163// =============================================================================
164
165uint16_t n_get_scu::next_message_id() noexcept {
166 uint16_t id = message_id_counter_.fetch_add(1, std::memory_order_relaxed);
167 if (id == 0) {
168 id = message_id_counter_.fetch_add(1, std::memory_order_relaxed);
169 }
170 return id;
171}
172
173} // namespace kcenon::pacs::services
auto get(dicom_tag tag) noexcept -> dicom_element *
Get a pointer to the element with the given tag.
bool is_established() const noexcept
Check if association is established and ready for DIMSE.
Result< std::monostate > send_dimse(uint8_t context_id, const dimse::dimse_message &msg)
Send a DIMSE message.
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.
std::atomic< uint16_t > message_id_counter_
Definition n_get_scu.h:207
void reset_statistics() noexcept
Reset statistics counters to zero.
uint16_t next_message_id() noexcept
Get the next message ID for DIMSE operations.
size_t gets_performed() const noexcept
Get the number of N-GET operations performed.
network::Result< n_get_result > get(network::association &assoc, std::string_view sop_class_uid, std::string_view sop_instance_uid, const std::vector< core::dicom_tag > &attribute_tags={})
Retrieve attributes from a managed SOP Instance.
Definition n_get_scu.cpp:37
n_get_scu(std::shared_ptr< di::ILogger > logger=nullptr)
Construct N-GET SCU with default configuration.
Definition n_get_scu.cpp:25
std::atomic< size_t > gets_performed_
Definition n_get_scu.h:208
std::shared_ptr< di::ILogger > logger_
Definition n_get_scu.h:205
DIMSE command field enumeration.
DICOM N-GET SCU service for attribute retrieval.
constexpr int n_get_missing_uid
Definition result.h:194
constexpr int n_get_unexpected_command
Definition result.h:191
constexpr int n_get_context_not_accepted
Definition result.h:193
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
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
Result<T> type aliases and helpers for PACS system.
DIMSE status codes.
Result of an N-GET operation.
Definition n_get_scu.h:45
std::chrono::milliseconds elapsed
Time taken for the operation.
Definition n_get_scu.h:56
std::string error_comment
Error comment from the SCP (if any)
Definition n_get_scu.h:53
core::dicom_dataset attributes
Retrieved attributes from the SOP Instance.
Definition n_get_scu.h:50
uint16_t status
DIMSE status code (0x0000 = success)
Definition n_get_scu.h:47
bool is_success() const noexcept
Check if the operation was successful.
Definition n_get_scu.h:59
Configuration for N-GET SCU service.
Definition n_get_scu.h:77
std::chrono::milliseconds timeout
Timeout for receiving DIMSE response.
Definition n_get_scu.h:79