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

Print Management SCP service. More...

#include <print_scp.h>

Inheritance diagram for kcenon::pacs::services::print_scp:
Inheritance graph
Collaboration diagram for kcenon::pacs::services::print_scp:
Collaboration graph

Public Member Functions

 print_scp (std::shared_ptr< di::ILogger > logger=nullptr)
 Construct Print SCP with optional logger.
 
 ~print_scp () override=default
 
void set_session_handler (print_session_handler handler)
 Set handler for film session creation.
 
void set_print_handler (print_action_handler handler)
 Set handler for print action (film box print)
 
void set_printer_status_handler (printer_status_handler handler)
 Set handler for printer status query.
 
std::vector< std::string > supported_sop_classes () const override
 Get supported SOP Class UIDs.
 
network::Result< std::monostate > handle_message (network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request) override
 Handle an incoming DIMSE-N message.
 
std::string_view service_name () const noexcept override
 Get the service name.
 
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
 
- Public Member Functions inherited from kcenon::pacs::services::scp_service
 scp_service (std::shared_ptr< di::ILogger > logger=nullptr)
 Construct SCP service with optional logger.
 
virtual ~scp_service ()=default
 
 scp_service (const scp_service &)=delete
 
scp_serviceoperator= (const scp_service &)=delete
 
 scp_service (scp_service &&)=default
 
scp_serviceoperator= (scp_service &&)=default
 
void set_logger (std::shared_ptr< di::ILogger > logger)
 Set the logger instance.
 
const std::shared_ptr< di::ILogger > & logger () const noexcept
 Get the current logger instance.
 
bool supports_sop_class (std::string_view sop_class_uid) const
 Check if this service supports a specific SOP Class.
 

Private Member Functions

network::Result< std::monostate > handle_n_create (network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
 
network::Result< std::monostate > handle_n_set (network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
 
network::Result< std::monostate > handle_n_get (network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
 
network::Result< std::monostate > handle_n_action (network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
 
network::Result< std::monostate > handle_n_delete (network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
 
network::Result< std::monostate > create_film_session (network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
 
network::Result< std::monostate > create_film_box (network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
 
network::Result< std::monostate > send_response (network::association &assoc, uint8_t context_id, network::dimse::command_field response_type, uint16_t message_id, std::string_view sop_class_uid, const std::string &sop_instance_uid, network::dimse::status_code status)
 
auto generate_uid () -> std::string
 

Private Attributes

print_session_handler session_handler_
 
print_action_handler print_handler_
 
printer_status_handler printer_status_handler_
 
std::unordered_map< std::string, film_sessionsessions_
 Active film sessions indexed by SOP Instance UID.
 
std::unordered_map< std::string, film_boxfilm_boxes_
 Active film boxes indexed by SOP Instance UID.
 
std::unordered_map< std::string, image_boximage_boxes_
 Active image boxes indexed by SOP Instance UID.
 
std::mutex mutex_
 Mutex for state management.
 
std::atomic< uint32_t > uid_counter_ {0}
 UID generation 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}
 

Additional Inherited Members

- Protected Attributes inherited from kcenon::pacs::services::scp_service
std::shared_ptr< di::ILoggerlogger_
 Logger instance for service logging.
 

Detailed Description

Print Management SCP service.

Handles DICOM print requests for Film Session, Film Box, Image Box, and Printer SOP Classes.

Print Workflow

SCU (Workstation) SCP (Print Server)
| |
| N-CREATE Film Session |
|--------------------------------------->|
| |
| N-CREATE Film Box |
|--------------------------------------->|
| |
| N-SET Image Box (pixel data) |
|--------------------------------------->|
| |
| N-ACTION Film Box (Print) |
|--------------------------------------->|
| |
| N-DELETE Film Session |
|--------------------------------------->|

Definition at line 239 of file print_scp.h.

Constructor & Destructor Documentation

◆ print_scp()

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

Construct Print SCP with optional logger.

Parameters
loggerLogger instance for service logging

Definition at line 27 of file print_scp.cpp.

28 : scp_service(std::move(logger)) {}
const std::shared_ptr< di::ILogger > & logger() const noexcept
Get the current logger instance.
Definition scp_service.h:93
scp_service(std::shared_ptr< di::ILogger > logger=nullptr)
Construct SCP service with optional logger.
Definition scp_service.h:64

◆ ~print_scp()

kcenon::pacs::services::print_scp::~print_scp ( )
overridedefault

Member Function Documentation

◆ create_film_box()

network::Result< std::monostate > kcenon::pacs::services::print_scp::create_film_box ( network::association & assoc,
uint8_t context_id,
const network::dimse::dimse_message & request )
nodiscardprivate

Definition at line 580 of file print_scp.cpp.

583 {
584
585 using namespace network::dimse;
586
587 auto sop_instance_uid = request.affected_sop_instance_uid();
588 if (sop_instance_uid.empty()) {
590 }
591
592 film_box box;
593 box.sop_instance_uid = sop_instance_uid;
594
595 if (request.has_dataset()) {
596 const auto& ds = request.dataset().value().get();
597 box.data = ds;
598
599 if (ds.contains(print_tags::image_display_format)) {
600 box.image_display_format = ds.get_string(print_tags::image_display_format);
601 }
602 if (ds.contains(print_tags::film_orientation)) {
603 box.film_orientation = ds.get_string(print_tags::film_orientation);
604 }
605 if (ds.contains(print_tags::film_size_id)) {
606 box.film_size_id = ds.get_string(print_tags::film_size_id);
607 }
608 }
609
610 // Parse image display format to determine image box count
611 // Format: "STANDARD\C,R" where C=columns, R=rows
612 uint16_t num_image_boxes = 1;
613 auto format = box.image_display_format;
614 auto backslash_pos = format.find('\\');
615 if (backslash_pos != std::string::npos) {
616 auto dims = format.substr(backslash_pos + 1);
617 auto comma_pos = dims.find(',');
618 if (comma_pos != std::string::npos) {
619 auto cols = static_cast<uint16_t>(
620 std::stoul(dims.substr(0, comma_pos)));
621 auto rows = static_cast<uint16_t>(
622 std::stoul(dims.substr(comma_pos + 1)));
623 num_image_boxes = static_cast<uint16_t>(cols * rows);
624 }
625 }
626
627 // Create image boxes for this film box
628 std::lock_guard<std::mutex> lock(mutex_);
629
630 // Link to parent film session if found
631 for (auto& [_, session] : sessions_) {
632 session.film_box_uids.push_back(sop_instance_uid);
633 box.film_session_uid = session.sop_instance_uid;
634 break; // Link to first (most recent) session
635 }
636
637 for (uint16_t i = 1; i <= num_image_boxes; ++i) {
638 image_box ib;
639 ib.sop_instance_uid = generate_uid();
640 ib.film_box_uid = sop_instance_uid;
641 ib.image_position = i;
642 box.image_box_uids.push_back(ib.sop_instance_uid);
643 image_boxes_.emplace(ib.sop_instance_uid, std::move(ib));
644 }
645
646 film_boxes_.emplace(sop_instance_uid, std::move(box));
648
649 // Build response with Referenced Image Box Sequence
650 dimse_message response{command_field::n_create_rsp, 0};
651 response.set_message_id_responded_to(request.message_id());
652 response.set_affected_sop_class_uid(basic_film_box_sop_class_uid);
653 response.set_affected_sop_instance_uid(sop_instance_uid);
654 response.set_status(status_success);
655
656 // Add referenced image box UIDs in response dataset
657 using namespace encoding;
658 core::dicom_dataset response_ds;
659 auto& fb = film_boxes_.at(sop_instance_uid);
660 auto& seq = response_ds.get_or_create_sequence(
662 for (size_t i = 0; i < fb.image_box_uids.size(); ++i) {
663 core::dicom_dataset ref_item;
664 ref_item.set_string(core::tags::referenced_sop_class_uid, vr_type::UI,
666 ref_item.set_string(core::tags::referenced_sop_instance_uid, vr_type::UI,
667 fb.image_box_uids[i]);
668 seq.push_back(std::move(ref_item));
669 }
670 response.set_dataset(std::move(response_ds));
671
672 return assoc.send_dimse(context_id, response);
673}
std::unordered_map< std::string, film_box > film_boxes_
Active film boxes indexed by SOP Instance UID.
Definition print_scp.h:388
std::unordered_map< std::string, film_session > sessions_
Active film sessions indexed by SOP Instance UID.
Definition print_scp.h:385
auto generate_uid() -> std::string
std::atomic< size_t > film_boxes_created_
Definition print_scp.h:401
std::unordered_map< std::string, image_box > image_boxes_
Active image boxes indexed by SOP Instance UID.
Definition print_scp.h:391
std::mutex mutex_
Mutex for state management.
Definition print_scp.h:394
constexpr dicom_tag rows
Rows.
constexpr dicom_tag referenced_sop_class_uid
Referenced SOP Class UID (in Sequence)
constexpr dicom_tag sop_instance_uid
SOP Instance UID.
constexpr dicom_tag referenced_sop_instance_uid
Referenced SOP Instance UID (in Sequence)
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 referenced_image_box_sequence
Referenced Image Box Sequence (2010,0510)
Definition print_scp.h:444
@ fb
From Below - inferior to superior view.
constexpr std::string_view basic_film_box_sop_class_uid
Basic Film Box SOP Class UID.
Definition print_scp.h:44
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::network::dimse::dimse_message::affected_sop_instance_uid(), kcenon::pacs::services::basic_film_box_sop_class_uid, kcenon::pacs::services::basic_grayscale_image_box_sop_class_uid, kcenon::pacs::services::film_box::data, kcenon::pacs::network::dimse::dimse_message::dataset(), kcenon::pacs::services::image_box::film_box_uid, film_boxes_, film_boxes_created_, kcenon::pacs::services::film_box::film_orientation, kcenon::pacs::services::print_tags::film_orientation, kcenon::pacs::services::film_box::film_session_uid, kcenon::pacs::services::film_box::film_size_id, kcenon::pacs::services::print_tags::film_size_id, generate_uid(), kcenon::pacs::core::dicom_dataset::get_or_create_sequence(), kcenon::pacs::network::dimse::dimse_message::has_dataset(), kcenon::pacs::services::film_box::image_box_uids, image_boxes_, kcenon::pacs::services::film_box::image_display_format, kcenon::pacs::services::print_tags::image_display_format, kcenon::pacs::services::image_box::image_position, kcenon::pacs::network::dimse::dimse_message::message_id(), mutex_, kcenon::pacs::services::print_tags::referenced_image_box_sequence, kcenon::pacs::core::tags::referenced_sop_class_uid, kcenon::pacs::core::tags::referenced_sop_instance_uid, kcenon::pacs::network::association::send_dimse(), sessions_, kcenon::pacs::core::dicom_dataset::set_string(), kcenon::pacs::services::film_box::sop_instance_uid, and kcenon::pacs::services::image_box::sop_instance_uid.

Referenced by handle_n_create().

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

◆ create_film_session()

network::Result< std::monostate > kcenon::pacs::services::print_scp::create_film_session ( network::association & assoc,
uint8_t context_id,
const network::dimse::dimse_message & request )
nodiscardprivate

Definition at line 515 of file print_scp.cpp.

518 {
519
520 using namespace network::dimse;
521
522 auto sop_instance_uid = request.affected_sop_instance_uid();
523 if (sop_instance_uid.empty()) {
525 }
526
527 film_session session;
528 session.sop_instance_uid = sop_instance_uid;
529
530 if (request.has_dataset()) {
531 const auto& ds = request.dataset().value().get();
532 session.data = ds;
533
534 if (ds.contains(print_tags::number_of_copies)) {
535 auto copies_str = ds.get_string(print_tags::number_of_copies);
536 if (!copies_str.empty()) {
537 session.number_of_copies =
538 static_cast<uint32_t>(std::stoul(copies_str));
539 }
540 }
541 if (ds.contains(print_tags::print_priority)) {
542 session.print_priority = ds.get_string(print_tags::print_priority);
543 }
544 if (ds.contains(print_tags::medium_type)) {
545 session.medium_type = ds.get_string(print_tags::medium_type);
546 }
547 if (ds.contains(print_tags::film_destination)) {
548 session.film_destination = ds.get_string(print_tags::film_destination);
549 }
550 }
551
552 // Call session handler
553 if (session_handler_) {
554 auto result = session_handler_(session);
555 if (result.is_err()) {
556 return send_response(
557 assoc, context_id, command_field::n_create_rsp,
558 request.message_id(), basic_film_session_sop_class_uid,
559 sop_instance_uid, status_error_unable_to_process);
560 }
561 }
562
563 {
564 std::lock_guard<std::mutex> lock(mutex_);
565 sessions_.emplace(sop_instance_uid, std::move(session));
566 }
567
569
570 return send_response(
571 assoc, context_id, command_field::n_create_rsp,
572 request.message_id(), basic_film_session_sop_class_uid,
573 sop_instance_uid, status_success);
574}
print_session_handler session_handler_
Definition print_scp.h:380
std::atomic< size_t > sessions_created_
Statistics.
Definition print_scp.h:400
network::Result< std::monostate > send_response(network::association &assoc, uint8_t context_id, network::dimse::command_field response_type, uint16_t message_id, std::string_view sop_class_uid, const std::string &sop_instance_uid, network::dimse::status_code status)
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 std::string_view basic_film_session_sop_class_uid
Basic Film Session SOP Class UID.
Definition print_scp.h:40

References kcenon::pacs::network::dimse::dimse_message::affected_sop_instance_uid(), kcenon::pacs::services::basic_film_session_sop_class_uid, kcenon::pacs::services::film_session::data, kcenon::pacs::network::dimse::dimse_message::dataset(), kcenon::pacs::services::film_session::film_destination, kcenon::pacs::services::print_tags::film_destination, generate_uid(), kcenon::pacs::network::dimse::dimse_message::has_dataset(), kcenon::pacs::services::film_session::medium_type, kcenon::pacs::services::print_tags::medium_type, kcenon::pacs::network::dimse::dimse_message::message_id(), mutex_, kcenon::pacs::services::film_session::number_of_copies, kcenon::pacs::services::print_tags::number_of_copies, kcenon::pacs::services::film_session::print_priority, kcenon::pacs::services::print_tags::print_priority, send_response(), session_handler_, sessions_, sessions_created_, and kcenon::pacs::services::film_session::sop_instance_uid.

Referenced by handle_n_create().

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

◆ film_boxes_created()

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

Definition at line 100 of file print_scp.cpp.

100 {
101 return film_boxes_created_.load();
102}

References film_boxes_created_.

◆ generate_uid()

auto kcenon::pacs::services::print_scp::generate_uid ( ) -> std::string
nodiscardprivate

Definition at line 706 of file print_scp.cpp.

706 {
707 auto counter = uid_counter_.fetch_add(1);
708 auto now = std::chrono::steady_clock::now().time_since_epoch();
709 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
710
711 std::ostringstream oss;
712 oss << "2.25." << ms << "." << counter;
713 return oss.str();
714}
std::atomic< uint32_t > uid_counter_
UID generation counter.
Definition print_scp.h:397
@ counter
Monotonic increasing value.

Referenced by create_film_box(), and create_film_session().

Here is the caller graph for this function:

◆ handle_message()

network::Result< std::monostate > kcenon::pacs::services::print_scp::handle_message ( network::association & assoc,
uint8_t context_id,
const network::dimse::dimse_message & request )
nodiscardoverridevirtual

Handle an incoming DIMSE-N message.

Routes N-CREATE, N-SET, N-GET, N-ACTION, N-DELETE to appropriate handlers based on the affected SOP class.

Implements kcenon::pacs::services::scp_service.

Definition at line 62 of file print_scp.cpp.

65 {
66
67 using namespace network::dimse;
68
69 switch (request.command()) {
70 case command_field::n_create_rq:
71 return handle_n_create(assoc, context_id, request);
72 case command_field::n_set_rq:
73 return handle_n_set(assoc, context_id, request);
74 case command_field::n_get_rq:
75 return handle_n_get(assoc, context_id, request);
76 case command_field::n_action_rq:
77 return handle_n_action(assoc, context_id, request);
78 case command_field::n_delete_rq:
79 return handle_n_delete(assoc, context_id, request);
80 default:
83 "Unexpected command for Print SCP: " +
84 std::string(to_string(request.command())));
85 }
86}
network::Result< std::monostate > handle_n_set(network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
network::Result< std::monostate > handle_n_delete(network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
network::Result< std::monostate > handle_n_action(network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
network::Result< std::monostate > handle_n_create(network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
network::Result< std::monostate > handle_n_get(network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
constexpr int print_unexpected_command
Definition result.h:198
auto to_string(mpps_status status) -> std::string_view
Convert mpps_status to DICOM string representation.
Definition mpps_scp.h:60
VoidResult pacs_void_error(int code, const std::string &message, const std::string &details="")
Create a PACS void error result.
Definition result.h:249

References kcenon::pacs::network::dimse::dimse_message::command(), handle_n_action(), handle_n_create(), handle_n_delete(), handle_n_get(), handle_n_set(), kcenon::pacs::pacs_void_error(), kcenon::pacs::error_codes::print_unexpected_command, and kcenon::pacs::services::to_string().

Here is the call graph for this function:

◆ handle_n_action()

network::Result< std::monostate > kcenon::pacs::services::print_scp::handle_n_action ( network::association & assoc,
uint8_t context_id,
const network::dimse::dimse_message & request )
nodiscardprivate

Definition at line 356 of file print_scp.cpp.

359 {
360
361 using namespace network::dimse;
362
363 auto sop_class_uid = request.affected_sop_class_uid();
364
365 // N-ACTION is used on Film Session or Film Box to initiate printing
366 if (sop_class_uid != basic_film_session_sop_class_uid &&
367 sop_class_uid != basic_film_box_sop_class_uid) {
368 return send_response(
369 assoc, context_id, command_field::n_action_rsp,
370 request.message_id(), sop_class_uid,
371 "", status_refused_sop_class_not_supported);
372 }
373
374 auto sop_instance_uid = request.affected_sop_instance_uid();
375 if (sop_instance_uid.empty()) {
376 return send_response(
377 assoc, context_id, command_field::n_action_rsp,
378 request.message_id(), sop_class_uid,
379 "", status_error_missing_attribute);
380 }
381
382 // Verify the object exists
383 {
384 std::lock_guard<std::mutex> lock(mutex_);
385
386 if (sop_class_uid == basic_film_box_sop_class_uid) {
387 if (film_boxes_.find(sop_instance_uid) == film_boxes_.end()) {
388 return send_response(
389 assoc, context_id, command_field::n_action_rsp,
390 request.message_id(), sop_class_uid,
391 sop_instance_uid, status_error_invalid_object_instance);
392 }
393 } else {
394 if (sessions_.find(sop_instance_uid) == sessions_.end()) {
395 return send_response(
396 assoc, context_id, command_field::n_action_rsp,
397 request.message_id(), sop_class_uid,
398 sop_instance_uid, status_error_invalid_object_instance);
399 }
400 }
401 }
402
403 // Call print handler
404 if (print_handler_) {
405 auto result = print_handler_(sop_instance_uid);
406 if (result.is_err()) {
407 return send_response(
408 assoc, context_id, command_field::n_action_rsp,
409 request.message_id(), sop_class_uid,
410 sop_instance_uid, status_error_unable_to_process);
411 }
412 }
413
415
416 // Build action response with action type ID
417 dimse_message response{command_field::n_action_rsp, 0};
418 response.set_message_id_responded_to(request.message_id());
419 response.set_affected_sop_class_uid(sop_class_uid);
420 response.set_affected_sop_instance_uid(sop_instance_uid);
421 response.set_status(status_success);
422
423 auto action_type = request.action_type_id();
424 if (action_type.has_value()) {
425 response.set_action_type_id(action_type.value());
426 }
427
428 return assoc.send_dimse(context_id, response);
429}
print_action_handler print_handler_
Definition print_scp.h:381
std::atomic< size_t > prints_executed_
Definition print_scp.h:403
constexpr dicom_tag sop_class_uid
SOP Class UID.

References kcenon::pacs::network::dimse::dimse_message::action_type_id(), kcenon::pacs::network::dimse::dimse_message::affected_sop_class_uid(), kcenon::pacs::network::dimse::dimse_message::affected_sop_instance_uid(), kcenon::pacs::services::basic_film_box_sop_class_uid, kcenon::pacs::services::basic_film_session_sop_class_uid, film_boxes_, kcenon::pacs::network::dimse::dimse_message::message_id(), mutex_, print_handler_, prints_executed_, kcenon::pacs::network::association::send_dimse(), send_response(), and sessions_.

Referenced by handle_message().

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

◆ handle_n_create()

network::Result< std::monostate > kcenon::pacs::services::print_scp::handle_n_create ( network::association & assoc,
uint8_t context_id,
const network::dimse::dimse_message & request )
nodiscardprivate

Definition at line 128 of file print_scp.cpp.

131 {
132
133 using namespace network::dimse;
134
135 auto sop_class_uid = request.affected_sop_class_uid();
136
137 if (sop_class_uid == basic_film_session_sop_class_uid) {
138 return create_film_session(assoc, context_id, request);
139 }
140 if (sop_class_uid == basic_film_box_sop_class_uid) {
141 return create_film_box(assoc, context_id, request);
142 }
143
144 return send_response(
145 assoc, context_id, command_field::n_create_rsp,
146 request.message_id(), sop_class_uid,
147 "", status_refused_sop_class_not_supported);
148}
network::Result< std::monostate > create_film_box(network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
network::Result< std::monostate > create_film_session(network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)

References kcenon::pacs::network::dimse::dimse_message::affected_sop_class_uid(), kcenon::pacs::services::basic_film_box_sop_class_uid, kcenon::pacs::services::basic_film_session_sop_class_uid, create_film_box(), create_film_session(), kcenon::pacs::network::dimse::dimse_message::message_id(), and send_response().

Referenced by handle_message().

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

◆ handle_n_delete()

network::Result< std::monostate > kcenon::pacs::services::print_scp::handle_n_delete ( network::association & assoc,
uint8_t context_id,
const network::dimse::dimse_message & request )
nodiscardprivate

Definition at line 435 of file print_scp.cpp.

438 {
439
440 using namespace network::dimse;
441
442 auto sop_class_uid = request.affected_sop_class_uid();
443 auto sop_instance_uid = request.affected_sop_instance_uid();
444
445 if (sop_instance_uid.empty()) {
446 return send_response(
447 assoc, context_id, command_field::n_delete_rsp,
448 request.message_id(), sop_class_uid,
449 "", status_error_missing_attribute);
450 }
451
452 std::lock_guard<std::mutex> lock(mutex_);
453
454 if (sop_class_uid == basic_film_session_sop_class_uid) {
455 auto it = sessions_.find(sop_instance_uid);
456 if (it == sessions_.end()) {
457 return send_response(
458 assoc, context_id, command_field::n_delete_rsp,
459 request.message_id(), sop_class_uid,
460 sop_instance_uid, status_error_invalid_object_instance);
461 }
462
463 // Delete associated film boxes and their image boxes
464 for (const auto& fb_uid : it->second.film_box_uids) {
465 auto fb_it = film_boxes_.find(fb_uid);
466 if (fb_it != film_boxes_.end()) {
467 for (const auto& ib_uid : fb_it->second.image_box_uids) {
468 image_boxes_.erase(ib_uid);
469 }
470 film_boxes_.erase(fb_it);
471 }
472 }
473 sessions_.erase(it);
474
475 } else if (sop_class_uid == basic_film_box_sop_class_uid) {
476 auto it = film_boxes_.find(sop_instance_uid);
477 if (it == film_boxes_.end()) {
478 return send_response(
479 assoc, context_id, command_field::n_delete_rsp,
480 request.message_id(), sop_class_uid,
481 sop_instance_uid, status_error_invalid_object_instance);
482 }
483
484 // Delete associated image boxes
485 for (const auto& ib_uid : it->second.image_box_uids) {
486 image_boxes_.erase(ib_uid);
487 }
488
489 // Remove from parent session's film box list
490 for (auto& [_, session] : sessions_) {
491 auto& fb_uids = session.film_box_uids;
492 fb_uids.erase(
493 std::remove(fb_uids.begin(), fb_uids.end(), sop_instance_uid),
494 fb_uids.end());
495 }
496 film_boxes_.erase(it);
497
498 } else {
499 return send_response(
500 assoc, context_id, command_field::n_delete_rsp,
501 request.message_id(), sop_class_uid,
502 sop_instance_uid, status_refused_sop_class_not_supported);
503 }
504
505 return send_response(
506 assoc, context_id, command_field::n_delete_rsp,
507 request.message_id(), sop_class_uid,
508 sop_instance_uid, status_success);
509}

References kcenon::pacs::network::dimse::dimse_message::affected_sop_class_uid(), kcenon::pacs::network::dimse::dimse_message::affected_sop_instance_uid(), kcenon::pacs::services::basic_film_box_sop_class_uid, kcenon::pacs::services::basic_film_session_sop_class_uid, film_boxes_, image_boxes_, kcenon::pacs::network::dimse::dimse_message::message_id(), mutex_, send_response(), and sessions_.

Referenced by handle_message().

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

◆ handle_n_get()

network::Result< std::monostate > kcenon::pacs::services::print_scp::handle_n_get ( network::association & assoc,
uint8_t context_id,
const network::dimse::dimse_message & request )
nodiscardprivate

Definition at line 288 of file print_scp.cpp.

291 {
292
293 using namespace network::dimse;
294
295 auto sop_class_uid = request.affected_sop_class_uid();
296 if (sop_class_uid.empty()) {
297 sop_class_uid = request.command_set().get_string(
298 tag_requested_sop_class_uid);
299 }
300
301 if (sop_class_uid != printer_sop_class_uid) {
302 return send_response(
303 assoc, context_id, command_field::n_get_rsp,
304 request.message_id(), sop_class_uid,
305 "", status_refused_sop_class_not_supported);
306 }
307
309
310 // Build printer status response
311 core::dicom_dataset response_ds;
312
313 using namespace encoding;
314
316 auto result = printer_status_handler_();
317 if (result.is_ok()) {
318 auto& [status, info_ds] = result.value();
319 response_ds = std::move(info_ds);
320 response_ds.set_string(print_tags::printer_status_tag, vr_type::CS,
321 std::string(to_string(status)));
322 } else {
323 response_ds.set_string(print_tags::printer_status_tag, vr_type::CS, "NORMAL");
324 response_ds.set_string(print_tags::printer_status_info, vr_type::ST, "");
325 }
326 } else {
327 // Default: report printer as normal
328 response_ds.set_string(print_tags::printer_status_tag, vr_type::CS, "NORMAL");
329 response_ds.set_string(print_tags::printer_status_info, vr_type::ST, "");
330 response_ds.set_string(print_tags::printer_name, vr_type::LO, "PACS_PRINTER");
331 }
332
333 // Build response message
334 dimse_message response{command_field::n_get_rsp, 0};
335 response.set_message_id_responded_to(request.message_id());
336 response.set_affected_sop_class_uid(printer_sop_class_uid);
337 response.set_status(status_success);
338
339 auto sop_instance_uid = request.command_set().get_string(
340 tag_requested_sop_instance_uid);
341 if (sop_instance_uid.empty()) {
342 sop_instance_uid = request.affected_sop_instance_uid();
343 }
344 if (!sop_instance_uid.empty()) {
345 response.set_affected_sop_instance_uid(sop_instance_uid);
346 }
347
348 response.set_dataset(std::move(response_ds));
349 return assoc.send_dimse(context_id, response);
350}
printer_status_handler printer_status_handler_
Definition print_scp.h:382
std::atomic< size_t > printer_queries_
Definition print_scp.h:404
constexpr dicom_tag status
Status.
constexpr core::dicom_tag printer_status_tag
Printer Status (2110,0010)
Definition print_scp.h:456
constexpr core::dicom_tag printer_name
Printer Name (2110,0030)
Definition print_scp.h:462
constexpr core::dicom_tag printer_status_info
Printer Status Info (2110,0020)
Definition print_scp.h:459
constexpr std::string_view printer_sop_class_uid
Printer SOP Class UID.
Definition print_scp.h:56

References kcenon::pacs::network::dimse::dimse_message::affected_sop_class_uid(), kcenon::pacs::network::dimse::dimse_message::affected_sop_instance_uid(), kcenon::pacs::network::dimse::dimse_message::command_set(), kcenon::pacs::network::dimse::dimse_message::message_id(), kcenon::pacs::services::print_tags::printer_name, printer_queries_, kcenon::pacs::services::printer_sop_class_uid, printer_status_handler_, kcenon::pacs::services::print_tags::printer_status_info, kcenon::pacs::services::print_tags::printer_status_tag, kcenon::pacs::network::association::send_dimse(), send_response(), kcenon::pacs::core::dicom_dataset::set_string(), and kcenon::pacs::services::to_string().

Referenced by handle_message().

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

◆ handle_n_set()

network::Result< std::monostate > kcenon::pacs::services::print_scp::handle_n_set ( network::association & assoc,
uint8_t context_id,
const network::dimse::dimse_message & request )
nodiscardprivate

Definition at line 154 of file print_scp.cpp.

157 {
158
159 using namespace network::dimse;
160
161 auto sop_class_uid = request.affected_sop_class_uid();
162 if (sop_class_uid.empty()) {
163 sop_class_uid = request.command_set().get_string(
164 tag_requested_sop_class_uid);
165 }
166
167 auto sop_instance_uid = request.command_set().get_string(
168 tag_requested_sop_instance_uid);
169 if (sop_instance_uid.empty()) {
170 sop_instance_uid = request.affected_sop_instance_uid();
171 }
172
173 if (sop_instance_uid.empty()) {
174 return send_response(
175 assoc, context_id, command_field::n_set_rsp,
176 request.message_id(), sop_class_uid,
177 "", status_error_missing_attribute);
178 }
179
180 // Handle Image Box N-SET (grayscale or color)
181 if (sop_class_uid == basic_grayscale_image_box_sop_class_uid ||
182 sop_class_uid == basic_color_image_box_sop_class_uid) {
183
184 std::lock_guard<std::mutex> lock(mutex_);
185
186 auto it = image_boxes_.find(sop_instance_uid);
187 if (it == image_boxes_.end()) {
188 return send_response(
189 assoc, context_id, command_field::n_set_rsp,
190 request.message_id(), sop_class_uid,
191 sop_instance_uid, status_error_invalid_object_instance);
192 }
193
194 if (request.has_dataset()) {
195 it->second.data = request.dataset().value().get();
196 it->second.has_pixel_data = true;
197 }
198
199 ++images_set_;
200
201 return send_response(
202 assoc, context_id, command_field::n_set_rsp,
203 request.message_id(), sop_class_uid,
204 sop_instance_uid, status_success);
205 }
206
207 // Handle Film Session N-SET
208 if (sop_class_uid == basic_film_session_sop_class_uid) {
209 std::lock_guard<std::mutex> lock(mutex_);
210
211 auto it = sessions_.find(sop_instance_uid);
212 if (it == sessions_.end()) {
213 return send_response(
214 assoc, context_id, command_field::n_set_rsp,
215 request.message_id(), sop_class_uid,
216 sop_instance_uid, status_error_invalid_object_instance);
217 }
218
219 if (request.has_dataset()) {
220 const auto& ds = request.dataset().value().get();
221 it->second.data = ds;
222
223 if (ds.contains(print_tags::number_of_copies)) {
224 auto copies_str = ds.get_string(print_tags::number_of_copies);
225 if (!copies_str.empty()) {
226 it->second.number_of_copies =
227 static_cast<uint32_t>(std::stoul(copies_str));
228 }
229 }
230 if (ds.contains(print_tags::print_priority)) {
231 it->second.print_priority = ds.get_string(print_tags::print_priority);
232 }
233 if (ds.contains(print_tags::medium_type)) {
234 it->second.medium_type = ds.get_string(print_tags::medium_type);
235 }
236 }
237
238 return send_response(
239 assoc, context_id, command_field::n_set_rsp,
240 request.message_id(), sop_class_uid,
241 sop_instance_uid, status_success);
242 }
243
244 // Handle Film Box N-SET
245 if (sop_class_uid == basic_film_box_sop_class_uid) {
246 std::lock_guard<std::mutex> lock(mutex_);
247
248 auto it = film_boxes_.find(sop_instance_uid);
249 if (it == film_boxes_.end()) {
250 return send_response(
251 assoc, context_id, command_field::n_set_rsp,
252 request.message_id(), sop_class_uid,
253 sop_instance_uid, status_error_invalid_object_instance);
254 }
255
256 if (request.has_dataset()) {
257 const auto& ds = request.dataset().value().get();
258 it->second.data = ds;
259
260 if (ds.contains(print_tags::image_display_format)) {
261 it->second.image_display_format =
263 }
264 if (ds.contains(print_tags::film_orientation)) {
265 it->second.film_orientation = ds.get_string(print_tags::film_orientation);
266 }
267 if (ds.contains(print_tags::film_size_id)) {
268 it->second.film_size_id = ds.get_string(print_tags::film_size_id);
269 }
270 }
271
272 return send_response(
273 assoc, context_id, command_field::n_set_rsp,
274 request.message_id(), sop_class_uid,
275 sop_instance_uid, status_success);
276 }
277
278 return send_response(
279 assoc, context_id, command_field::n_set_rsp,
280 request.message_id(), sop_class_uid,
281 sop_instance_uid, status_refused_sop_class_not_supported);
282}
std::atomic< size_t > images_set_
Definition print_scp.h:402
constexpr std::string_view basic_color_image_box_sop_class_uid
Basic Color Image Box SOP Class UID.
Definition print_scp.h:52

References kcenon::pacs::network::dimse::dimse_message::affected_sop_class_uid(), kcenon::pacs::network::dimse::dimse_message::affected_sop_instance_uid(), kcenon::pacs::services::basic_color_image_box_sop_class_uid, kcenon::pacs::services::basic_film_box_sop_class_uid, kcenon::pacs::services::basic_film_session_sop_class_uid, kcenon::pacs::services::basic_grayscale_image_box_sop_class_uid, kcenon::pacs::network::dimse::dimse_message::command_set(), kcenon::pacs::network::dimse::dimse_message::dataset(), film_boxes_, kcenon::pacs::services::print_tags::film_orientation, kcenon::pacs::services::print_tags::film_size_id, kcenon::pacs::network::dimse::dimse_message::has_dataset(), image_boxes_, kcenon::pacs::services::print_tags::image_display_format, images_set_, kcenon::pacs::services::print_tags::medium_type, kcenon::pacs::network::dimse::dimse_message::message_id(), mutex_, kcenon::pacs::services::print_tags::number_of_copies, kcenon::pacs::services::print_tags::print_priority, send_response(), and sessions_.

Referenced by handle_message().

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

◆ images_set()

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

Definition at line 104 of file print_scp.cpp.

104 {
105 return images_set_.load();
106}

References images_set_.

◆ printer_queries()

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

Definition at line 112 of file print_scp.cpp.

112 {
113 return printer_queries_.load();
114}

References printer_queries_.

◆ prints_executed()

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

Definition at line 108 of file print_scp.cpp.

108 {
109 return prints_executed_.load();
110}

References prints_executed_.

◆ reset_statistics()

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

Definition at line 116 of file print_scp.cpp.

116 {
119 images_set_ = 0;
122}

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

◆ send_response()

network::Result< std::monostate > kcenon::pacs::services::print_scp::send_response ( network::association & assoc,
uint8_t context_id,
network::dimse::command_field response_type,
uint16_t message_id,
std::string_view sop_class_uid,
const std::string & sop_instance_uid,
network::dimse::status_code status )
nodiscardprivate

Definition at line 679 of file print_scp.cpp.

686 {
687
688 using namespace network::dimse;
689
690 dimse_message response{response_type, 0};
691 response.set_message_id_responded_to(message_id);
692 response.set_affected_sop_class_uid(sop_class_uid);
693 response.set_status(status);
694
695 if (!sop_instance_uid.empty()) {
696 response.set_affected_sop_instance_uid(sop_instance_uid);
697 }
698
699 return assoc.send_dimse(context_id, response);
700}

References kcenon::pacs::network::association::send_dimse().

Referenced by create_film_session(), handle_n_action(), handle_n_create(), handle_n_delete(), handle_n_get(), and handle_n_set().

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

◆ service_name()

std::string_view kcenon::pacs::services::print_scp::service_name ( ) const
nodiscardoverridevirtualnoexcept

Get the service name.

Returns
"Print SCP"

Implements kcenon::pacs::services::scp_service.

Definition at line 88 of file print_scp.cpp.

88 {
89 return "Print SCP";
90}

◆ sessions_created()

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

Definition at line 96 of file print_scp.cpp.

96 {
97 return sessions_created_.load();
98}

References sessions_created_.

◆ set_print_handler()

void kcenon::pacs::services::print_scp::set_print_handler ( print_action_handler handler)

Set handler for print action (film box print)

Definition at line 38 of file print_scp.cpp.

38 {
39 print_handler_ = std::move(handler);
40}

References print_handler_.

◆ set_printer_status_handler()

void kcenon::pacs::services::print_scp::set_printer_status_handler ( printer_status_handler handler)

Set handler for printer status query.

Definition at line 42 of file print_scp.cpp.

42 {
43 printer_status_handler_ = std::move(handler);
44}

References printer_status_handler_.

◆ set_session_handler()

void kcenon::pacs::services::print_scp::set_session_handler ( print_session_handler handler)

Set handler for film session creation.

Definition at line 34 of file print_scp.cpp.

34 {
35 session_handler_ = std::move(handler);
36}

References session_handler_.

◆ supported_sop_classes()

std::vector< std::string > kcenon::pacs::services::print_scp::supported_sop_classes ( ) const
nodiscardoverridevirtual

Get supported SOP Class UIDs.

Returns
Vector of Print Management SOP Class UIDs

Implements kcenon::pacs::services::scp_service.

Definition at line 50 of file print_scp.cpp.

50 {
51 return {
56 std::string(printer_sop_class_uid),
59 };
60}
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::services::basic_color_image_box_sop_class_uid, kcenon::pacs::services::basic_color_print_meta_sop_class_uid, kcenon::pacs::services::basic_film_box_sop_class_uid, kcenon::pacs::services::basic_film_session_sop_class_uid, kcenon::pacs::services::basic_grayscale_image_box_sop_class_uid, kcenon::pacs::services::basic_grayscale_print_meta_sop_class_uid, and kcenon::pacs::services::printer_sop_class_uid.

Member Data Documentation

◆ film_boxes_

std::unordered_map<std::string, film_box> kcenon::pacs::services::print_scp::film_boxes_
private

Active film boxes indexed by SOP Instance UID.

Definition at line 388 of file print_scp.h.

Referenced by create_film_box(), handle_n_action(), handle_n_delete(), and handle_n_set().

◆ film_boxes_created_

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

Definition at line 401 of file print_scp.h.

401{0};

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

◆ image_boxes_

std::unordered_map<std::string, image_box> kcenon::pacs::services::print_scp::image_boxes_
private

Active image boxes indexed by SOP Instance UID.

Definition at line 391 of file print_scp.h.

Referenced by create_film_box(), handle_n_delete(), and handle_n_set().

◆ images_set_

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

Definition at line 402 of file print_scp.h.

402{0};

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

◆ mutex_

std::mutex kcenon::pacs::services::print_scp::mutex_
mutableprivate

Mutex for state management.

Definition at line 394 of file print_scp.h.

Referenced by create_film_box(), create_film_session(), handle_n_action(), handle_n_delete(), and handle_n_set().

◆ print_handler_

print_action_handler kcenon::pacs::services::print_scp::print_handler_
private

Definition at line 381 of file print_scp.h.

Referenced by handle_n_action(), and set_print_handler().

◆ printer_queries_

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

Definition at line 404 of file print_scp.h.

404{0};

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

◆ printer_status_handler_

printer_status_handler kcenon::pacs::services::print_scp::printer_status_handler_
private

Definition at line 382 of file print_scp.h.

Referenced by handle_n_get(), and set_printer_status_handler().

◆ prints_executed_

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

Definition at line 403 of file print_scp.h.

403{0};

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

◆ session_handler_

print_session_handler kcenon::pacs::services::print_scp::session_handler_
private

Definition at line 380 of file print_scp.h.

Referenced by create_film_session(), and set_session_handler().

◆ sessions_

std::unordered_map<std::string, film_session> kcenon::pacs::services::print_scp::sessions_
private

Active film sessions indexed by SOP Instance UID.

Definition at line 385 of file print_scp.h.

Referenced by create_film_box(), create_film_session(), handle_n_action(), handle_n_delete(), and handle_n_set().

◆ sessions_created_

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

Statistics.

Definition at line 400 of file print_scp.h.

400{0};

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

◆ uid_counter_

std::atomic<uint32_t> kcenon::pacs::services::print_scp::uid_counter_ {0}
private

UID generation counter.

Definition at line 397 of file print_scp.h.

397{0};

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