67 using namespace network::dimse;
70 case command_field::n_create_rq:
72 case command_field::n_set_rq:
74 case command_field::n_get_rq:
76 case command_field::n_action_rq:
78 case command_field::n_delete_rq:
83 "Unexpected command for Print SCP: " +
133 using namespace network::dimse;
145 assoc, context_id, command_field::n_create_rsp,
147 "", status_refused_sop_class_not_supported);
159 using namespace network::dimse;
162 if (sop_class_uid.empty()) {
164 tag_requested_sop_class_uid);
167 auto sop_instance_uid = request.
command_set().get_string(
168 tag_requested_sop_instance_uid);
169 if (sop_instance_uid.empty()) {
173 if (sop_instance_uid.empty()) {
175 assoc, context_id, command_field::n_set_rsp,
177 "", status_error_missing_attribute);
184 std::lock_guard<std::mutex> lock(
mutex_);
189 assoc, context_id, command_field::n_set_rsp,
191 sop_instance_uid, status_error_invalid_object_instance);
195 it->second.data = request.
dataset().value().get();
196 it->second.has_pixel_data =
true;
202 assoc, context_id, command_field::n_set_rsp,
204 sop_instance_uid, status_success);
209 std::lock_guard<std::mutex> lock(
mutex_);
211 auto it =
sessions_.find(sop_instance_uid);
214 assoc, context_id, command_field::n_set_rsp,
216 sop_instance_uid, status_error_invalid_object_instance);
220 const auto& ds = request.
dataset().value().get();
221 it->second.data = ds;
225 if (!copies_str.empty()) {
226 it->second.number_of_copies =
227 static_cast<uint32_t
>(std::stoul(copies_str));
239 assoc, context_id, command_field::n_set_rsp,
241 sop_instance_uid, status_success);
246 std::lock_guard<std::mutex> lock(
mutex_);
251 assoc, context_id, command_field::n_set_rsp,
253 sop_instance_uid, status_error_invalid_object_instance);
257 const auto& ds = request.
dataset().value().get();
258 it->second.data = ds;
261 it->second.image_display_format =
273 assoc, context_id, command_field::n_set_rsp,
275 sop_instance_uid, status_success);
279 assoc, context_id, command_field::n_set_rsp,
281 sop_instance_uid, status_refused_sop_class_not_supported);
293 using namespace network::dimse;
296 if (sop_class_uid.empty()) {
298 tag_requested_sop_class_uid);
303 assoc, context_id, command_field::n_get_rsp,
305 "", status_refused_sop_class_not_supported);
313 using namespace encoding;
317 if (result.is_ok()) {
318 auto& [status, info_ds] = result.value();
319 response_ds = std::move(info_ds);
334 dimse_message response{command_field::n_get_rsp, 0};
335 response.set_message_id_responded_to(request.
message_id());
337 response.set_status(status_success);
339 auto sop_instance_uid = request.
command_set().get_string(
340 tag_requested_sop_instance_uid);
341 if (sop_instance_uid.empty()) {
344 if (!sop_instance_uid.empty()) {
345 response.set_affected_sop_instance_uid(sop_instance_uid);
348 response.set_dataset(std::move(response_ds));
349 return assoc.
send_dimse(context_id, response);
361 using namespace network::dimse;
369 assoc, context_id, command_field::n_action_rsp,
371 "", status_refused_sop_class_not_supported);
375 if (sop_instance_uid.empty()) {
377 assoc, context_id, command_field::n_action_rsp,
379 "", status_error_missing_attribute);
384 std::lock_guard<std::mutex> lock(
mutex_);
389 assoc, context_id, command_field::n_action_rsp,
391 sop_instance_uid, status_error_invalid_object_instance);
396 assoc, context_id, command_field::n_action_rsp,
398 sop_instance_uid, status_error_invalid_object_instance);
406 if (result.is_err()) {
408 assoc, context_id, command_field::n_action_rsp,
410 sop_instance_uid, status_error_unable_to_process);
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);
424 if (action_type.has_value()) {
425 response.set_action_type_id(action_type.value());
428 return assoc.
send_dimse(context_id, response);
440 using namespace network::dimse;
445 if (sop_instance_uid.empty()) {
447 assoc, context_id, command_field::n_delete_rsp,
449 "", status_error_missing_attribute);
452 std::lock_guard<std::mutex> lock(
mutex_);
455 auto it =
sessions_.find(sop_instance_uid);
458 assoc, context_id, command_field::n_delete_rsp,
460 sop_instance_uid, status_error_invalid_object_instance);
464 for (
const auto& fb_uid : it->second.film_box_uids) {
467 for (
const auto& ib_uid : fb_it->second.image_box_uids) {
479 assoc, context_id, command_field::n_delete_rsp,
481 sop_instance_uid, status_error_invalid_object_instance);
485 for (
const auto& ib_uid : it->second.image_box_uids) {
491 auto& fb_uids = session.film_box_uids;
493 std::remove(fb_uids.begin(), fb_uids.end(), sop_instance_uid),
500 assoc, context_id, command_field::n_delete_rsp,
502 sop_instance_uid, status_refused_sop_class_not_supported);
506 assoc, context_id, command_field::n_delete_rsp,
508 sop_instance_uid, status_success);
520 using namespace network::dimse;
523 if (sop_instance_uid.empty()) {
531 const auto& ds = request.
dataset().value().get();
536 if (!copies_str.empty()) {
538 static_cast<uint32_t
>(std::stoul(copies_str));
555 if (result.is_err()) {
557 assoc, context_id, command_field::n_create_rsp,
559 sop_instance_uid, status_error_unable_to_process);
564 std::lock_guard<std::mutex> lock(
mutex_);
565 sessions_.emplace(sop_instance_uid, std::move(session));
571 assoc, context_id, command_field::n_create_rsp,
573 sop_instance_uid, status_success);
585 using namespace network::dimse;
588 if (sop_instance_uid.empty()) {
596 const auto& ds = request.
dataset().value().get();
612 uint16_t num_image_boxes = 1;
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);
628 std::lock_guard<std::mutex> lock(
mutex_);
632 session.film_box_uids.push_back(sop_instance_uid);
637 for (uint16_t i = 1; i <= num_image_boxes; ++i) {
646 film_boxes_.emplace(sop_instance_uid, std::move(box));
650 dimse_message response{command_field::n_create_rsp, 0};
651 response.set_message_id_responded_to(request.
message_id());
653 response.set_affected_sop_instance_uid(sop_instance_uid);
654 response.set_status(status_success);
657 using namespace encoding;
662 for (
size_t i = 0; i < fb.image_box_uids.size(); ++i) {
667 fb.image_box_uids[i]);
668 seq.push_back(std::move(ref_item));
670 response.set_dataset(std::move(response_ds));
672 return assoc.
send_dimse(context_id, response);
684 std::string_view sop_class_uid,
685 const std::string& sop_instance_uid,
688 using namespace network::dimse;
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);
695 if (!sop_instance_uid.empty()) {
696 response.set_affected_sop_instance_uid(sop_instance_uid);
699 return assoc.
send_dimse(context_id, response);
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();
711 std::ostringstream oss;
712 oss <<
"2.25." << ms <<
"." << counter;
void set_string(dicom_tag tag, encoding::vr_type vr, std::string_view value)
Set a string value for the given tag.
auto get_or_create_sequence(dicom_tag tag) -> std::vector< dicom_dataset > &
Insert or create a sequence element with the given tag.
Result< std::monostate > send_dimse(uint8_t context_id, const dimse::dimse_message &msg)
Send a DIMSE message.
auto message_id() const noexcept -> uint16_t
Get the message ID.
auto affected_sop_class_uid() const -> std::string
Get the Affected SOP Class UID.
auto command_set() noexcept -> core::dicom_dataset &
Get mutable reference to the command set.
auto has_dataset() const noexcept -> bool
Check if the message has an associated data set.
auto dataset() -> kcenon::pacs::Result< std::reference_wrapper< core::dicom_dataset > >
Get mutable reference to the data set.
auto action_type_id() const -> std::optional< uint16_t >
Get the Action Type ID (for N-ACTION)
auto affected_sop_instance_uid() const -> std::string
Get the Affected SOP Instance UID.
auto command() const noexcept -> command_field
Get the command field.
std::atomic< size_t > images_set_
size_t printer_queries() const noexcept
print_session_handler session_handler_
std::vector< std::string > supported_sop_classes() const override
Get supported SOP Class UIDs.
size_t sessions_created() const noexcept
size_t film_boxes_created() const noexcept
network::Result< std::monostate > handle_n_set(network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
std::atomic< size_t > sessions_created_
Statistics.
print_action_handler print_handler_
network::Result< std::monostate > create_film_box(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)
std::unordered_map< std::string, film_box > film_boxes_
Active film boxes indexed by SOP Instance UID.
std::unordered_map< std::string, film_session > sessions_
Active film sessions indexed by SOP Instance UID.
std::atomic< size_t > prints_executed_
size_t prints_executed() const noexcept
size_t images_set() const noexcept
std::string_view service_name() const noexcept override
Get the service name.
void set_session_handler(print_session_handler handler)
Set handler for film session creation.
network::Result< std::monostate > handle_n_create(network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
printer_status_handler printer_status_handler_
void set_print_handler(print_action_handler handler)
Set handler for print action (film box print)
std::atomic< size_t > printer_queries_
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
network::Result< std::monostate > handle_n_get(network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
std::atomic< size_t > film_boxes_created_
void reset_statistics() noexcept
network::Result< std::monostate > create_film_session(network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
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::unordered_map< std::string, image_box > image_boxes_
Active image boxes indexed by SOP Instance UID.
std::mutex mutex_
Mutex for state management.
print_scp(std::shared_ptr< di::ILogger > logger=nullptr)
Construct Print SCP with optional logger.
void set_printer_status_handler(printer_status_handler handler)
Set handler for printer status query.
DIMSE command field enumeration.
Compile-time constants for commonly used DICOM tags.
constexpr int print_unexpected_command
command_field
DIMSE command field values.
uint16_t status_code
DIMSE status code type alias.
constexpr std::string_view basic_grayscale_print_meta_sop_class_uid
Basic Grayscale Print Management Meta SOP Class UID.
constexpr std::string_view basic_color_print_meta_sop_class_uid
Basic Color Print Management Meta SOP Class UID.
constexpr std::string_view basic_film_session_sop_class_uid
Basic Film Session SOP Class UID.
std::function< network::Result< std::monostate >( const film_session &session)> print_session_handler
Handler for film session creation.
constexpr std::string_view basic_color_image_box_sop_class_uid
Basic Color Image Box SOP Class UID.
std::function< network::Result< std::monostate >( const std::string &film_box_uid)> print_action_handler
Handler for print action (film box print)
std::function< network::Result< std::pair< printer_status, core::dicom_dataset > >()> printer_status_handler
Handler for printer status query.
constexpr std::string_view basic_film_box_sop_class_uid
Basic Film Box SOP Class UID.
auto to_string(mpps_status status) -> std::string_view
Convert mpps_status to DICOM string representation.
constexpr std::string_view basic_grayscale_image_box_sop_class_uid
Basic Grayscale Image Box SOP Class UID.
constexpr std::string_view printer_sop_class_uid
Printer SOP Class UID.
VoidResult pacs_void_error(int code, const std::string &message, const std::string &details="")
Create a PACS void error result.
DICOM Print Management SCP service (PS3.4 Annex H)
Result<T> type aliases and helpers for PACS system.
Film box data created by N-CREATE.
std::string film_orientation
Film orientation (PORTRAIT, LANDSCAPE)
std::vector< std::string > image_box_uids
Associated image box UIDs.
std::string sop_instance_uid
SOP Instance UID.
std::string film_size_id
Film size ID (8INX10IN, 14INX17IN, etc.)
std::string film_session_uid
Parent film session UID.
std::string image_display_format
Image display format (STANDARD\1,1 etc.)
core::dicom_dataset data
Complete dataset from request.
Film session data created by N-CREATE.
std::string sop_instance_uid
SOP Instance UID.
uint32_t number_of_copies
Number of copies.
std::string print_priority
Print priority (HIGH, MED, LOW)
core::dicom_dataset data
Complete dataset from request.
std::string film_destination
Film destination (MAGAZINE, PROCESSOR)
std::string medium_type
Medium type (PAPER, CLEAR FILM, BLUE FILM)
Image box data set by N-SET.
std::string film_box_uid
Parent film box UID.
std::string sop_instance_uid
SOP Instance UID.
uint16_t image_position
Image position (1-based)