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

#include <print_scu.h>

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

Public Member Functions

 print_scu (std::shared_ptr< di::ILogger > logger=nullptr)
 Construct Print SCU with default configuration.
 
 print_scu (const print_scu_config &config, std::shared_ptr< di::ILogger > logger=nullptr)
 Construct Print SCU with custom configuration.
 
 ~print_scu ()=default
 
 print_scu (const print_scu &)=delete
 
print_scuoperator= (const print_scu &)=delete
 
 print_scu (print_scu &&)=delete
 
print_scuoperator= (print_scu &&)=delete
 
network::Result< print_resultcreate_film_session (network::association &assoc, const print_session_data &data)
 Create a new Film Session (N-CREATE)
 
network::Result< print_resultdelete_film_session (network::association &assoc, std::string_view session_uid)
 Delete a Film Session (N-DELETE)
 
network::Result< print_resultcreate_film_box (network::association &assoc, const print_film_box_data &data)
 Create a new Film Box (N-CREATE)
 
network::Result< print_resultprint_film_box (network::association &assoc, std::string_view film_box_uid)
 Print a Film Box (N-ACTION)
 
network::Result< print_resultdelete_film_box (network::association &assoc, std::string_view film_box_uid)
 Delete a Film Box (N-DELETE)
 
network::Result< print_resultset_image_box (network::association &assoc, std::string_view image_box_uid, const print_image_data &data, bool use_color=false)
 Set Image Box pixel data (N-SET)
 
network::Result< print_resultquery_printer_status (network::association &assoc)
 Query printer status (N-GET)
 
size_t sessions_created () const noexcept
 
size_t film_boxes_created () const noexcept
 
size_t images_set () const noexcept
 
size_t prints_executed () const noexcept
 
size_t printer_queries () const noexcept
 
void reset_statistics () noexcept
 

Private Member Functions

std::optional< uint8_t > find_print_context (network::association &assoc, std::string_view sop_class_uid) const
 Find an accepted presentation context for a print SOP class.
 
std::string generate_uid () const
 Generate a unique SOP Instance UID.
 
uint16_t next_message_id () noexcept
 Get the next message ID for DIMSE operations.
 

Private Attributes

std::shared_ptr< di::ILoggerlogger_
 Logger instance.
 
print_scu_config config_
 Configuration.
 
std::atomic< uint16_t > message_id_counter_ {1}
 Message ID counter.
 
std::atomic< size_t > sessions_created_ {0}
 Statistics.
 
std::atomic< size_t > film_boxes_created_ {0}
 
std::atomic< size_t > images_set_ {0}
 
std::atomic< size_t > prints_executed_ {0}
 
std::atomic< size_t > printer_queries_ {0}
 

Detailed Description

Examples
print_scu/main.cpp.

Definition at line 231 of file print_scu.h.

Constructor & Destructor Documentation

◆ print_scu() [1/4]

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

Construct Print SCU with default configuration.

Parameters
loggerLogger instance for service logging (nullptr uses null_logger)

Definition at line 143 of file print_scu.cpp.

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

◆ print_scu() [2/4]

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

Construct Print SCU with custom configuration.

Parameters
configConfiguration options
loggerLogger instance for service logging (nullptr uses null_logger)

Definition at line 146 of file print_scu.cpp.

148 : logger_(logger ? std::move(logger) : di::null_logger()),
149 config_(config) {}
print_scu_config config_
Configuration.
Definition print_scu.h:412

◆ ~print_scu()

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

◆ print_scu() [3/4]

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

◆ print_scu() [4/4]

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

Member Function Documentation

◆ create_film_box()

network::Result< print_result > kcenon::pacs::services::print_scu::create_film_box ( network::association & assoc,
const print_film_box_data & data )
nodiscard

Create a new Film Box (N-CREATE)

The SCP will auto-create Image Boxes based on the image display format. Referenced Image Box UIDs are returned in the response dataset.

Parameters
assocThe established association to use
dataFilm box creation data
Returns
Result containing print_result (response_data has image box UIDs)
Examples
print_scu/main.cpp.

Definition at line 330 of file print_scu.cpp.

332 {
333
334 using namespace network::dimse;
335 using namespace encoding;
336
337 auto start_time = std::chrono::steady_clock::now();
338
339 if (!assoc.is_established()) {
342 "Association not established");
343 }
344
345 auto context_id = find_print_context(assoc, basic_film_box_sop_class_uid);
346 if (!context_id) {
349 "No accepted presentation context for Film Box SOP Class");
350 }
351
352 // Build creation dataset
353 core::dicom_dataset ds;
354 if (!data.image_display_format.empty()) {
355 ds.set_string(print_tags::image_display_format, vr_type::ST,
356 data.image_display_format);
357 }
358 if (!data.film_orientation.empty()) {
359 ds.set_string(print_tags::film_orientation, vr_type::CS, data.film_orientation);
360 }
361 if (!data.film_size_id.empty()) {
362 ds.set_string(print_tags::film_size_id, vr_type::CS, data.film_size_id);
363 }
364 if (!data.magnification_type.empty()) {
365 ds.set_string(print_tags::magnification_type, vr_type::CS,
366 data.magnification_type);
367 }
368
369 // Add referenced film session sequence
370 if (!data.film_session_uid.empty()) {
371 auto& seq = ds.get_or_create_sequence(
373 core::dicom_dataset ref_item;
374 ref_item.set_string(core::tags::referenced_sop_class_uid, vr_type::UI,
376 ref_item.set_string(core::tags::referenced_sop_instance_uid, vr_type::UI,
377 data.film_session_uid);
378 seq.push_back(std::move(ref_item));
379 }
380
381 auto request = make_n_create_rq(
384 "", // SCP generates the UID
385 std::move(ds));
386
387 logger_->debug("Sending N-CREATE Film Box");
388
389 auto send_result = assoc.send_dimse(*context_id, request);
390 if (send_result.is_err()) {
391 logger_->error("Failed to send N-CREATE Film Box: " + send_result.error().message);
392 return send_result.error();
393 }
394
395 auto recv_result = assoc.receive_dimse(config_.timeout);
396 if (recv_result.is_err()) {
397 logger_->error("Failed to receive N-CREATE Film Box response: " +
398 recv_result.error().message);
399 return recv_result.error();
400 }
401
402 const auto& [recv_context_id, response] = recv_result.value();
403
404 if (response.command() != command_field::n_create_rsp) {
407 "Expected N-CREATE-RSP but received " +
408 std::string(to_string(response.command())));
409 }
410
411 auto end_time = std::chrono::steady_clock::now();
412
413 print_result result;
414 result.sop_instance_uid = std::string(response.affected_sop_instance_uid());
415 result.status = static_cast<uint16_t>(response.status());
416 result.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
417 end_time - start_time);
418
419 if (response.command_set().contains(tag_error_comment)) {
420 result.error_comment = response.command_set().get_string(tag_error_comment);
421 }
422
423 // Capture response dataset (contains Referenced Image Box Sequence)
424 if (response.has_dataset()) {
425 result.response_data = response.dataset().value().get();
426 }
427
428 film_boxes_created_.fetch_add(1, std::memory_order_relaxed);
429
430 if (result.is_success()) {
431 logger_->info("N-CREATE Film Box successful: " + result.sop_instance_uid);
432 } else {
433 logger_->warn("N-CREATE Film Box status 0x" +
434 std::to_string(result.status));
435 }
436
437 return result;
438}
std::optional< uint8_t > find_print_context(network::association &assoc, std::string_view sop_class_uid) const
Find an accepted presentation context for a print SOP class.
std::atomic< size_t > film_boxes_created_
Definition print_scu.h:419
uint16_t next_message_id() noexcept
Get the next message ID for DIMSE operations.
constexpr dicom_tag referenced_sop_class_uid
Referenced SOP Class UID (in Sequence)
constexpr dicom_tag referenced_sop_instance_uid
Referenced SOP Instance UID (in Sequence)
constexpr int print_invalid_sop_class
Definition result.h:199
constexpr int print_unexpected_command
Definition result.h:198
constexpr int association_not_established
Definition result.h:202
constexpr core::dicom_tag referenced_film_session_sequence
Referenced Film Session Sequence (2010,0500)
Definition print_scp.h:441
constexpr core::dicom_tag image_display_format
Image Display Format (2010,0010)
Definition print_scp.h:429
constexpr core::dicom_tag film_orientation
Film Orientation (2010,0040)
Definition print_scp.h:432
constexpr core::dicom_tag film_size_id
Film Size ID (2010,0050)
Definition print_scp.h:435
constexpr core::dicom_tag magnification_type
Magnification Type (2010,0060)
Definition print_scp.h:438
constexpr std::string_view basic_film_session_sop_class_uid
Basic Film Session SOP Class UID.
Definition print_scp.h:40
constexpr std::string_view basic_film_box_sop_class_uid
Basic Film Box SOP Class UID.
Definition print_scp.h:44
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
std::chrono::milliseconds timeout
Timeout for receiving DIMSE response.
Definition print_scu.h:138

References kcenon::pacs::error_codes::association_not_established, kcenon::pacs::services::basic_film_box_sop_class_uid, kcenon::pacs::services::basic_film_session_sop_class_uid, config_, kcenon::pacs::services::print_result::elapsed, kcenon::pacs::services::print_result::error_comment, film_boxes_created_, kcenon::pacs::services::print_film_box_data::film_orientation, kcenon::pacs::services::print_tags::film_orientation, kcenon::pacs::services::print_film_box_data::film_session_uid, kcenon::pacs::services::print_film_box_data::film_size_id, kcenon::pacs::services::print_tags::film_size_id, find_print_context(), kcenon::pacs::core::dicom_dataset::get(), kcenon::pacs::core::dicom_dataset::get_or_create_sequence(), kcenon::pacs::services::print_film_box_data::image_display_format, kcenon::pacs::services::print_tags::image_display_format, kcenon::pacs::network::association::is_established(), kcenon::pacs::services::print_result::is_success(), logger_, kcenon::pacs::services::print_film_box_data::magnification_type, kcenon::pacs::services::print_tags::magnification_type, next_message_id(), kcenon::pacs::pacs_error(), kcenon::pacs::error_codes::print_invalid_sop_class, kcenon::pacs::error_codes::print_unexpected_command, kcenon::pacs::network::association::receive_dimse(), kcenon::pacs::services::print_tags::referenced_film_session_sequence, kcenon::pacs::core::tags::referenced_sop_class_uid, kcenon::pacs::core::tags::referenced_sop_instance_uid, kcenon::pacs::services::print_result::response_data, kcenon::pacs::network::association::send_dimse(), kcenon::pacs::core::dicom_dataset::set_string(), kcenon::pacs::services::print_result::sop_instance_uid, kcenon::pacs::services::print_result::status, kcenon::pacs::services::print_scu_config::timeout, and kcenon::pacs::services::to_string().

Here is the call graph for this function:

◆ create_film_session()

network::Result< print_result > kcenon::pacs::services::print_scu::create_film_session ( network::association & assoc,
const print_session_data & data )
nodiscard

Create a new Film Session (N-CREATE)

Parameters
assocThe established association to use
dataFilm session creation data
Returns
Result containing print_result on success
Examples
print_scu/main.cpp.

Definition at line 155 of file print_scu.cpp.

157 {
158
159 using namespace network::dimse;
160 using namespace encoding;
161
162 auto start_time = std::chrono::steady_clock::now();
163
164 if (!assoc.is_established()) {
167 "Association not established");
168 }
169
171 if (!context_id) {
174 "No accepted presentation context for Film Session SOP Class");
175 }
176
177 std::string session_uid = data.sop_instance_uid;
178 if (session_uid.empty() && config_.auto_generate_uid) {
179 session_uid = generate_uid();
180 }
181
182 // Build creation dataset
183 core::dicom_dataset ds;
184 ds.set_string(print_tags::number_of_copies, vr_type::IS,
185 std::to_string(data.number_of_copies));
186 if (!data.print_priority.empty()) {
187 ds.set_string(print_tags::print_priority, vr_type::CS, data.print_priority);
188 }
189 if (!data.medium_type.empty()) {
190 ds.set_string(print_tags::medium_type, vr_type::CS, data.medium_type);
191 }
192 if (!data.film_destination.empty()) {
193 ds.set_string(print_tags::film_destination, vr_type::CS, data.film_destination);
194 }
195 if (!data.film_session_label.empty()) {
196 ds.set_string(print_tags::film_session_label, vr_type::LO, data.film_session_label);
197 }
198
199 auto request = make_n_create_rq(
202 session_uid,
203 std::move(ds));
204
205 logger_->debug("Sending N-CREATE Film Session: " + session_uid);
206
207 auto send_result = assoc.send_dimse(*context_id, request);
208 if (send_result.is_err()) {
209 logger_->error("Failed to send N-CREATE Film Session: " + send_result.error().message);
210 return send_result.error();
211 }
212
213 auto recv_result = assoc.receive_dimse(config_.timeout);
214 if (recv_result.is_err()) {
215 logger_->error("Failed to receive N-CREATE Film Session response: " +
216 recv_result.error().message);
217 return recv_result.error();
218 }
219
220 const auto& [recv_context_id, response] = recv_result.value();
221
222 if (response.command() != command_field::n_create_rsp) {
225 "Expected N-CREATE-RSP but received " +
226 std::string(to_string(response.command())));
227 }
228
229 auto end_time = std::chrono::steady_clock::now();
230
231 print_result result;
232 result.sop_instance_uid = response.affected_sop_instance_uid().empty()
233 ? session_uid : std::string(response.affected_sop_instance_uid());
234 result.status = static_cast<uint16_t>(response.status());
235 result.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
236 end_time - start_time);
237
238 if (response.command_set().contains(tag_error_comment)) {
239 result.error_comment = response.command_set().get_string(tag_error_comment);
240 }
241
242 sessions_created_.fetch_add(1, std::memory_order_relaxed);
243
244 if (result.is_success()) {
245 logger_->info("N-CREATE Film Session successful: " + result.sop_instance_uid);
246 } else {
247 logger_->warn("N-CREATE Film Session status 0x" +
248 std::to_string(result.status) + ": " + result.sop_instance_uid);
249 }
250
251 return result;
252}
std::atomic< size_t > sessions_created_
Statistics.
Definition print_scu.h:418
std::string generate_uid() const
Generate a unique SOP Instance UID.
constexpr core::dicom_tag film_destination
Film Destination (2000,0040)
Definition print_scp.h:423
constexpr core::dicom_tag print_priority
Print Priority (2000,0020)
Definition print_scp.h:417
constexpr core::dicom_tag medium_type
Medium Type (2000,0030)
Definition print_scp.h:420
constexpr core::dicom_tag number_of_copies
Number of Copies (2000,0010)
Definition print_scp.h:414
constexpr core::dicom_tag film_session_label
Film Session Label (2000,0050)
Definition print_scp.h:426
bool auto_generate_uid
Auto-generate SOP Instance UIDs if not provided.
Definition print_scu.h:141

References kcenon::pacs::error_codes::association_not_established, kcenon::pacs::services::print_scu_config::auto_generate_uid, kcenon::pacs::services::basic_film_session_sop_class_uid, config_, kcenon::pacs::services::print_result::elapsed, kcenon::pacs::services::print_result::error_comment, kcenon::pacs::services::print_session_data::film_destination, kcenon::pacs::services::print_tags::film_destination, kcenon::pacs::services::print_session_data::film_session_label, kcenon::pacs::services::print_tags::film_session_label, find_print_context(), generate_uid(), kcenon::pacs::network::association::is_established(), kcenon::pacs::services::print_result::is_success(), logger_, kcenon::pacs::services::print_session_data::medium_type, kcenon::pacs::services::print_tags::medium_type, next_message_id(), kcenon::pacs::services::print_session_data::number_of_copies, kcenon::pacs::services::print_tags::number_of_copies, kcenon::pacs::pacs_error(), kcenon::pacs::error_codes::print_invalid_sop_class, kcenon::pacs::services::print_session_data::print_priority, kcenon::pacs::services::print_tags::print_priority, kcenon::pacs::error_codes::print_unexpected_command, kcenon::pacs::network::association::receive_dimse(), kcenon::pacs::network::association::send_dimse(), sessions_created_, kcenon::pacs::core::dicom_dataset::set_string(), kcenon::pacs::services::print_result::sop_instance_uid, kcenon::pacs::services::print_session_data::sop_instance_uid, kcenon::pacs::services::print_result::status, kcenon::pacs::services::print_scu_config::timeout, and kcenon::pacs::services::to_string().

Here is the call graph for this function:

◆ delete_film_box()

network::Result< print_result > kcenon::pacs::services::print_scu::delete_film_box ( network::association & assoc,
std::string_view film_box_uid )
nodiscard

Delete a Film Box (N-DELETE)

Parameters
assocThe established association to use
film_box_uidFilm Box SOP Instance UID
Returns
Result containing print_result on success

Definition at line 516 of file print_scu.cpp.

518 {
519
520 using namespace network::dimse;
521
522 auto start_time = std::chrono::steady_clock::now();
523
524 if (!assoc.is_established()) {
527 "Association not established");
528 }
529
530 auto context_id = find_print_context(assoc, basic_film_box_sop_class_uid);
531 if (!context_id) {
534 "No accepted presentation context for Film Box SOP Class");
535 }
536
537 auto request = make_n_delete_rq(
540 film_box_uid);
541
542 logger_->debug("Sending N-DELETE Film Box: " + std::string(film_box_uid));
543
544 auto send_result = assoc.send_dimse(*context_id, request);
545 if (send_result.is_err()) {
546 logger_->error("Failed to send N-DELETE Film Box: " + send_result.error().message);
547 return send_result.error();
548 }
549
550 auto recv_result = assoc.receive_dimse(config_.timeout);
551 if (recv_result.is_err()) {
552 logger_->error("Failed to receive N-DELETE Film Box response: " +
553 recv_result.error().message);
554 return recv_result.error();
555 }
556
557 const auto& [recv_context_id, response] = recv_result.value();
558
559 if (response.command() != command_field::n_delete_rsp) {
562 "Expected N-DELETE-RSP but received " +
563 std::string(to_string(response.command())));
564 }
565
566 auto end_time = std::chrono::steady_clock::now();
567
568 print_result result;
569 result.sop_instance_uid = std::string(film_box_uid);
570 result.status = static_cast<uint16_t>(response.status());
571 result.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
572 end_time - start_time);
573
574 if (response.command_set().contains(tag_error_comment)) {
575 result.error_comment = response.command_set().get_string(tag_error_comment);
576 }
577
578 if (result.is_success()) {
579 logger_->info("N-DELETE Film Box successful: " + std::string(film_box_uid));
580 } else {
581 logger_->warn("N-DELETE Film Box status 0x" +
582 std::to_string(result.status) + ": " + std::string(film_box_uid));
583 }
584
585 return result;
586}

References kcenon::pacs::error_codes::association_not_established, kcenon::pacs::services::basic_film_box_sop_class_uid, config_, kcenon::pacs::services::print_result::elapsed, kcenon::pacs::services::print_result::error_comment, find_print_context(), kcenon::pacs::network::association::is_established(), kcenon::pacs::services::print_result::is_success(), logger_, next_message_id(), kcenon::pacs::pacs_error(), kcenon::pacs::error_codes::print_invalid_sop_class, kcenon::pacs::error_codes::print_unexpected_command, kcenon::pacs::network::association::receive_dimse(), kcenon::pacs::network::association::send_dimse(), kcenon::pacs::services::print_result::sop_instance_uid, kcenon::pacs::services::print_result::status, kcenon::pacs::services::print_scu_config::timeout, and kcenon::pacs::services::to_string().

Here is the call graph for this function:

◆ delete_film_session()

network::Result< print_result > kcenon::pacs::services::print_scu::delete_film_session ( network::association & assoc,
std::string_view session_uid )
nodiscard

Delete a Film Session (N-DELETE)

Also deletes all associated Film Boxes and Image Boxes on the SCP side.

Parameters
assocThe established association to use
session_uidFilm Session SOP Instance UID
Returns
Result containing print_result on success
Examples
print_scu/main.cpp.

Definition at line 254 of file print_scu.cpp.

256 {
257
258 using namespace network::dimse;
259
260 auto start_time = std::chrono::steady_clock::now();
261
262 if (!assoc.is_established()) {
265 "Association not established");
266 }
267
269 if (!context_id) {
272 "No accepted presentation context for Film Session SOP Class");
273 }
274
275 auto request = make_n_delete_rq(
278 session_uid);
279
280 logger_->debug("Sending N-DELETE Film Session: " + std::string(session_uid));
281
282 auto send_result = assoc.send_dimse(*context_id, request);
283 if (send_result.is_err()) {
284 logger_->error("Failed to send N-DELETE Film Session: " + send_result.error().message);
285 return send_result.error();
286 }
287
288 auto recv_result = assoc.receive_dimse(config_.timeout);
289 if (recv_result.is_err()) {
290 logger_->error("Failed to receive N-DELETE Film Session response: " +
291 recv_result.error().message);
292 return recv_result.error();
293 }
294
295 const auto& [recv_context_id, response] = recv_result.value();
296
297 if (response.command() != command_field::n_delete_rsp) {
300 "Expected N-DELETE-RSP but received " +
301 std::string(to_string(response.command())));
302 }
303
304 auto end_time = std::chrono::steady_clock::now();
305
306 print_result result;
307 result.sop_instance_uid = std::string(session_uid);
308 result.status = static_cast<uint16_t>(response.status());
309 result.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
310 end_time - start_time);
311
312 if (response.command_set().contains(tag_error_comment)) {
313 result.error_comment = response.command_set().get_string(tag_error_comment);
314 }
315
316 if (result.is_success()) {
317 logger_->info("N-DELETE Film Session successful: " + std::string(session_uid));
318 } else {
319 logger_->warn("N-DELETE Film Session status 0x" +
320 std::to_string(result.status) + ": " + std::string(session_uid));
321 }
322
323 return result;
324}

References kcenon::pacs::error_codes::association_not_established, kcenon::pacs::services::basic_film_session_sop_class_uid, config_, kcenon::pacs::services::print_result::elapsed, kcenon::pacs::services::print_result::error_comment, find_print_context(), kcenon::pacs::network::association::is_established(), kcenon::pacs::services::print_result::is_success(), logger_, next_message_id(), kcenon::pacs::pacs_error(), kcenon::pacs::error_codes::print_invalid_sop_class, kcenon::pacs::error_codes::print_unexpected_command, kcenon::pacs::network::association::receive_dimse(), kcenon::pacs::network::association::send_dimse(), kcenon::pacs::services::print_result::sop_instance_uid, kcenon::pacs::services::print_result::status, kcenon::pacs::services::print_scu_config::timeout, and kcenon::pacs::services::to_string().

Here is the call graph for this function:

◆ film_boxes_created()

size_t kcenon::pacs::services::print_scu::film_boxes_created ( ) const
nodiscardnoexcept

Definition at line 775 of file print_scu.cpp.

775 {
776 return film_boxes_created_.load(std::memory_order_relaxed);
777}

References film_boxes_created_.

◆ find_print_context()

std::optional< uint8_t > kcenon::pacs::services::print_scu::find_print_context ( network::association & assoc,
std::string_view sop_class_uid ) const
nodiscardprivate

Find an accepted presentation context for a print SOP class.

Tries the specific SOP class first, then falls back to Meta SOP classes.

Definition at line 803 of file print_scu.cpp.

805 {
806
807 // Try the specific SOP class first
808 auto context = assoc.accepted_context_id(sop_class_uid);
809 if (context) {
810 return context;
811 }
812
813 // Fall back to Meta SOP Classes which bundle multiple print SOP classes
814 context = assoc.accepted_context_id(basic_grayscale_print_meta_sop_class_uid);
815 if (context) {
816 return context;
817 }
818
819 context = assoc.accepted_context_id(basic_color_print_meta_sop_class_uid);
820 return context;
821}
constexpr std::string_view basic_grayscale_print_meta_sop_class_uid
Basic Grayscale Print Management Meta SOP Class UID.
Definition print_scp.h:60
constexpr std::string_view basic_color_print_meta_sop_class_uid
Basic Color Print Management Meta SOP Class UID.
Definition print_scp.h:64

References kcenon::pacs::network::association::accepted_context_id(), kcenon::pacs::services::basic_color_print_meta_sop_class_uid, and kcenon::pacs::services::basic_grayscale_print_meta_sop_class_uid.

Referenced by create_film_box(), create_film_session(), delete_film_box(), delete_film_session(), print_film_box(), query_printer_status(), and set_image_box().

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

◆ generate_uid()

std::string kcenon::pacs::services::print_scu::generate_uid ( ) const
nodiscardprivate

Generate a unique SOP Instance UID.

Definition at line 823 of file print_scu.cpp.

823 {
824 static std::mt19937_64 gen{std::random_device{}()};
825 static std::uniform_int_distribution<uint64_t> dist;
826
827 auto now = std::chrono::system_clock::now();
828 auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
829 now.time_since_epoch()).count();
830
831 return std::string(uid_root) + "." + std::to_string(timestamp) +
832 "." + std::to_string(dist(gen) % 100000);
833}

Referenced by create_film_session().

Here is the caller graph for this function:

◆ images_set()

size_t kcenon::pacs::services::print_scu::images_set ( ) const
nodiscardnoexcept

Definition at line 779 of file print_scu.cpp.

779 {
780 return images_set_.load(std::memory_order_relaxed);
781}
std::atomic< size_t > images_set_
Definition print_scu.h:420

References images_set_.

◆ next_message_id()

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

Get the next message ID for DIMSE operations.

Definition at line 835 of file print_scu.cpp.

835 {
836 uint16_t id = message_id_counter_.fetch_add(1, std::memory_order_relaxed);
837 if (id == 0) {
838 id = message_id_counter_.fetch_add(1, std::memory_order_relaxed);
839 }
840 return id;
841}
std::atomic< uint16_t > message_id_counter_
Message ID counter.
Definition print_scu.h:415
@ id
Implant Displaced (alternate code)

References message_id_counter_.

Referenced by create_film_box(), create_film_session(), delete_film_box(), delete_film_session(), print_film_box(), query_printer_status(), and set_image_box().

Here is the caller graph for this function:

◆ operator=() [1/2]

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

◆ operator=() [2/2]

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

◆ print_film_box()

network::Result< print_result > kcenon::pacs::services::print_scu::print_film_box ( network::association & assoc,
std::string_view film_box_uid )
nodiscard

Print a Film Box (N-ACTION)

Sends N-ACTION to the SCP to initiate printing of the film box.

Parameters
assocThe established association to use
film_box_uidFilm Box SOP Instance UID
Returns
Result containing print_result on success
Examples
print_scu/main.cpp.

Definition at line 440 of file print_scu.cpp.

442 {
443
444 using namespace network::dimse;
445
446 auto start_time = std::chrono::steady_clock::now();
447
448 if (!assoc.is_established()) {
451 "Association not established");
452 }
453
454 auto context_id = find_print_context(assoc, basic_film_box_sop_class_uid);
455 if (!context_id) {
458 "No accepted presentation context for Film Box SOP Class");
459 }
460
461 // Action Type ID 1 = Print
462 auto request = make_n_action_rq(
465 film_box_uid,
466 1);
467
468 logger_->debug("Sending N-ACTION Print Film Box: " + std::string(film_box_uid));
469
470 auto send_result = assoc.send_dimse(*context_id, request);
471 if (send_result.is_err()) {
472 logger_->error("Failed to send N-ACTION Print: " + send_result.error().message);
473 return send_result.error();
474 }
475
476 auto recv_result = assoc.receive_dimse(config_.timeout);
477 if (recv_result.is_err()) {
478 logger_->error("Failed to receive N-ACTION Print response: " +
479 recv_result.error().message);
480 return recv_result.error();
481 }
482
483 const auto& [recv_context_id, response] = recv_result.value();
484
485 if (response.command() != command_field::n_action_rsp) {
488 "Expected N-ACTION-RSP but received " +
489 std::string(to_string(response.command())));
490 }
491
492 auto end_time = std::chrono::steady_clock::now();
493
494 print_result result;
495 result.sop_instance_uid = std::string(film_box_uid);
496 result.status = static_cast<uint16_t>(response.status());
497 result.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
498 end_time - start_time);
499
500 if (response.command_set().contains(tag_error_comment)) {
501 result.error_comment = response.command_set().get_string(tag_error_comment);
502 }
503
504 prints_executed_.fetch_add(1, std::memory_order_relaxed);
505
506 if (result.is_success()) {
507 logger_->info("N-ACTION Print successful: " + std::string(film_box_uid));
508 } else {
509 logger_->warn("N-ACTION Print status 0x" +
510 std::to_string(result.status) + ": " + std::string(film_box_uid));
511 }
512
513 return result;
514}
std::atomic< size_t > prints_executed_
Definition print_scu.h:421

References kcenon::pacs::error_codes::association_not_established, kcenon::pacs::services::basic_film_box_sop_class_uid, config_, kcenon::pacs::services::print_result::elapsed, kcenon::pacs::services::print_result::error_comment, find_print_context(), kcenon::pacs::network::association::is_established(), kcenon::pacs::services::print_result::is_success(), logger_, next_message_id(), kcenon::pacs::pacs_error(), kcenon::pacs::error_codes::print_invalid_sop_class, kcenon::pacs::error_codes::print_unexpected_command, prints_executed_, kcenon::pacs::network::association::receive_dimse(), kcenon::pacs::network::association::send_dimse(), kcenon::pacs::services::print_result::sop_instance_uid, kcenon::pacs::services::print_result::status, kcenon::pacs::services::print_scu_config::timeout, and kcenon::pacs::services::to_string().

Here is the call graph for this function:

◆ printer_queries()

size_t kcenon::pacs::services::print_scu::printer_queries ( ) const
nodiscardnoexcept

Definition at line 787 of file print_scu.cpp.

787 {
788 return printer_queries_.load(std::memory_order_relaxed);
789}
std::atomic< size_t > printer_queries_
Definition print_scu.h:422

References printer_queries_.

◆ prints_executed()

size_t kcenon::pacs::services::print_scu::prints_executed ( ) const
nodiscardnoexcept

Definition at line 783 of file print_scu.cpp.

783 {
784 return prints_executed_.load(std::memory_order_relaxed);
785}

References prints_executed_.

◆ query_printer_status()

network::Result< print_result > kcenon::pacs::services::print_scu::query_printer_status ( network::association & assoc)
nodiscard

Query printer status (N-GET)

Sends N-GET to the Printer SOP Class to retrieve printer status. The response_data in print_result contains the printer status dataset.

Parameters
assocThe established association to use
Returns
Result containing print_result with printer status in response_data
Examples
print_scu/main.cpp.

Definition at line 686 of file print_scu.cpp.

687 {
688
689 using namespace network::dimse;
690
691 auto start_time = std::chrono::steady_clock::now();
692
693 if (!assoc.is_established()) {
696 "Association not established");
697 }
698
699 auto context_id = find_print_context(assoc, printer_sop_class_uid);
700 if (!context_id) {
703 "No accepted presentation context for Printer SOP Class");
704 }
705
706 // Well-Known Printer SOP Instance UID (PS3.4 H.4.17)
707 constexpr std::string_view printer_instance_uid =
708 "1.2.840.10008.5.1.1.17";
709
710 auto request = make_n_get_rq(
713 printer_instance_uid);
714
715 logger_->debug("Sending N-GET Printer Status");
716
717 auto send_result = assoc.send_dimse(*context_id, request);
718 if (send_result.is_err()) {
719 logger_->error("Failed to send N-GET Printer Status: " + send_result.error().message);
720 return send_result.error();
721 }
722
723 auto recv_result = assoc.receive_dimse(config_.timeout);
724 if (recv_result.is_err()) {
725 logger_->error("Failed to receive N-GET Printer Status response: " +
726 recv_result.error().message);
727 return recv_result.error();
728 }
729
730 const auto& [recv_context_id, response] = recv_result.value();
731
732 if (response.command() != command_field::n_get_rsp) {
735 "Expected N-GET-RSP but received " +
736 std::string(to_string(response.command())));
737 }
738
739 auto end_time = std::chrono::steady_clock::now();
740
741 print_result result;
742 result.sop_instance_uid = std::string(printer_instance_uid);
743 result.status = static_cast<uint16_t>(response.status());
744 result.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
745 end_time - start_time);
746
747 if (response.command_set().contains(tag_error_comment)) {
748 result.error_comment = response.command_set().get_string(tag_error_comment);
749 }
750
751 if (response.has_dataset()) {
752 result.response_data = response.dataset().value().get();
753 }
754
755 printer_queries_.fetch_add(1, std::memory_order_relaxed);
756
757 if (result.is_success()) {
758 logger_->info("N-GET Printer Status successful");
759 } else {
760 logger_->warn("N-GET Printer Status returned 0x" +
761 std::to_string(result.status));
762 }
763
764 return result;
765}
constexpr std::string_view printer_sop_class_uid
Printer SOP Class UID.
Definition print_scp.h:56

References kcenon::pacs::error_codes::association_not_established, config_, kcenon::pacs::services::print_result::elapsed, kcenon::pacs::services::print_result::error_comment, find_print_context(), kcenon::pacs::core::dicom_dataset::get(), kcenon::pacs::network::association::is_established(), kcenon::pacs::services::print_result::is_success(), logger_, next_message_id(), kcenon::pacs::pacs_error(), kcenon::pacs::error_codes::print_invalid_sop_class, kcenon::pacs::error_codes::print_unexpected_command, printer_queries_, kcenon::pacs::services::printer_sop_class_uid, kcenon::pacs::network::association::receive_dimse(), kcenon::pacs::services::print_result::response_data, kcenon::pacs::network::association::send_dimse(), kcenon::pacs::services::print_result::sop_instance_uid, kcenon::pacs::services::print_result::status, kcenon::pacs::services::print_scu_config::timeout, and kcenon::pacs::services::to_string().

Here is the call graph for this function:

◆ reset_statistics()

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

Definition at line 791 of file print_scu.cpp.

791 {
792 sessions_created_.store(0, std::memory_order_relaxed);
793 film_boxes_created_.store(0, std::memory_order_relaxed);
794 images_set_.store(0, std::memory_order_relaxed);
795 prints_executed_.store(0, std::memory_order_relaxed);
796 printer_queries_.store(0, std::memory_order_relaxed);
797}

References film_boxes_created_, images_set_, printer_queries_, prints_executed_, and sessions_created_.

◆ sessions_created()

size_t kcenon::pacs::services::print_scu::sessions_created ( ) const
nodiscardnoexcept

Definition at line 771 of file print_scu.cpp.

771 {
772 return sessions_created_.load(std::memory_order_relaxed);
773}

References sessions_created_.

◆ set_image_box()

network::Result< print_result > kcenon::pacs::services::print_scu::set_image_box ( network::association & assoc,
std::string_view image_box_uid,
const print_image_data & data,
bool use_color = false )
nodiscard

Set Image Box pixel data (N-SET)

Sets the pixel data for an Image Box in the film box. Use Basic Grayscale Image Box SOP Class by default.

Parameters
assocThe established association to use
image_box_uidImage Box SOP Instance UID
dataImage data to set
use_colorUse Basic Color Image Box instead of Grayscale
Returns
Result containing print_result on success

Definition at line 592 of file print_scu.cpp.

596 {
597
598 using namespace network::dimse;
599 using namespace encoding;
600
601 auto start_time = std::chrono::steady_clock::now();
602
603 if (!assoc.is_established()) {
606 "Association not established");
607 }
608
609 auto sop_class_uid = use_color
612
613 auto context_id = find_print_context(assoc, sop_class_uid);
614 if (!context_id) {
617 "No accepted presentation context for Image Box SOP Class");
618 }
619
620 // Build modification dataset with pixel data in the appropriate sequence
621 core::dicom_dataset ds = data.pixel_data;
622
623 if (data.image_position > 0) {
624 ds.set_string(print_tags::image_position, vr_type::US,
625 std::to_string(data.image_position));
626 }
627
628 auto request = make_n_set_rq(
630 sop_class_uid,
631 image_box_uid,
632 std::move(ds));
633
634 logger_->debug("Sending N-SET Image Box: " + std::string(image_box_uid));
635
636 auto send_result = assoc.send_dimse(*context_id, request);
637 if (send_result.is_err()) {
638 logger_->error("Failed to send N-SET Image Box: " + send_result.error().message);
639 return send_result.error();
640 }
641
642 auto recv_result = assoc.receive_dimse(config_.timeout);
643 if (recv_result.is_err()) {
644 logger_->error("Failed to receive N-SET Image Box response: " +
645 recv_result.error().message);
646 return recv_result.error();
647 }
648
649 const auto& [recv_context_id, response] = recv_result.value();
650
651 if (response.command() != command_field::n_set_rsp) {
654 "Expected N-SET-RSP but received " +
655 std::string(to_string(response.command())));
656 }
657
658 auto end_time = std::chrono::steady_clock::now();
659
660 print_result result;
661 result.sop_instance_uid = std::string(image_box_uid);
662 result.status = static_cast<uint16_t>(response.status());
663 result.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
664 end_time - start_time);
665
666 if (response.command_set().contains(tag_error_comment)) {
667 result.error_comment = response.command_set().get_string(tag_error_comment);
668 }
669
670 images_set_.fetch_add(1, std::memory_order_relaxed);
671
672 if (result.is_success()) {
673 logger_->info("N-SET Image Box successful: " + std::string(image_box_uid));
674 } else {
675 logger_->warn("N-SET Image Box status 0x" +
676 std::to_string(result.status) + ": " + std::string(image_box_uid));
677 }
678
679 return result;
680}
constexpr dicom_tag sop_class_uid
SOP Class UID.
constexpr core::dicom_tag image_position
Image Position (2020,0010)
Definition print_scp.h:447
constexpr std::string_view basic_color_image_box_sop_class_uid
Basic Color Image Box SOP Class UID.
Definition print_scp.h:52
constexpr std::string_view basic_grayscale_image_box_sop_class_uid
Basic Grayscale Image Box SOP Class UID.
Definition print_scp.h:48

References kcenon::pacs::error_codes::association_not_established, kcenon::pacs::services::basic_color_image_box_sop_class_uid, kcenon::pacs::services::basic_grayscale_image_box_sop_class_uid, config_, kcenon::pacs::services::print_result::elapsed, kcenon::pacs::services::print_result::error_comment, find_print_context(), kcenon::pacs::services::print_image_data::image_position, kcenon::pacs::services::print_tags::image_position, images_set_, kcenon::pacs::network::association::is_established(), kcenon::pacs::services::print_result::is_success(), logger_, next_message_id(), kcenon::pacs::pacs_error(), kcenon::pacs::services::print_image_data::pixel_data, kcenon::pacs::error_codes::print_invalid_sop_class, kcenon::pacs::error_codes::print_unexpected_command, kcenon::pacs::network::association::receive_dimse(), kcenon::pacs::network::association::send_dimse(), kcenon::pacs::core::dicom_dataset::set_string(), kcenon::pacs::services::print_result::sop_instance_uid, kcenon::pacs::services::print_result::status, kcenon::pacs::services::print_scu_config::timeout, and kcenon::pacs::services::to_string().

Here is the call graph for this function:

Member Data Documentation

◆ config_

print_scu_config kcenon::pacs::services::print_scu::config_
private

◆ film_boxes_created_

std::atomic<size_t> kcenon::pacs::services::print_scu::film_boxes_created_ {0}
private

Definition at line 419 of file print_scu.h.

419{0};

Referenced by create_film_box(), film_boxes_created(), and reset_statistics().

◆ images_set_

std::atomic<size_t> kcenon::pacs::services::print_scu::images_set_ {0}
private

Definition at line 420 of file print_scu.h.

420{0};

Referenced by images_set(), reset_statistics(), and set_image_box().

◆ logger_

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

◆ message_id_counter_

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

Message ID counter.

Definition at line 415 of file print_scu.h.

415{1};

Referenced by next_message_id().

◆ printer_queries_

std::atomic<size_t> kcenon::pacs::services::print_scu::printer_queries_ {0}
private

Definition at line 422 of file print_scu.h.

422{0};

Referenced by printer_queries(), query_printer_status(), and reset_statistics().

◆ prints_executed_

std::atomic<size_t> kcenon::pacs::services::print_scu::prints_executed_ {0}
private

Definition at line 421 of file print_scu.h.

421{0};

Referenced by print_film_box(), prints_executed(), and reset_statistics().

◆ sessions_created_

std::atomic<size_t> kcenon::pacs::services::print_scu::sessions_created_ {0}
private

Statistics.

Definition at line 418 of file print_scu.h.

418{0};

Referenced by create_film_session(), reset_statistics(), and sessions_created().


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