PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
print_scu.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
11
17
18#include <chrono>
19#include <random>
20#include <sstream>
21
22namespace kcenon::pacs::services {
23
24// =============================================================================
25// Local Helper Functions
26// =============================================================================
27
28namespace {
29
31constexpr const char* uid_root = "1.2.826.0.1.3680043.2.1545.1";
32
34constexpr core::dicom_tag tag_requested_sop_instance_uid{0x0000, 0x1001};
35
39network::dimse::dimse_message make_n_create_rq(
40 uint16_t message_id,
41 std::string_view sop_class_uid,
42 std::string_view sop_instance_uid,
43 core::dicom_dataset dataset) {
44
45 using namespace network::dimse;
46
47 dimse_message msg{command_field::n_create_rq, message_id};
48 msg.set_affected_sop_class_uid(sop_class_uid);
49 if (!sop_instance_uid.empty()) {
50 msg.set_affected_sop_instance_uid(sop_instance_uid);
51 }
52 msg.set_dataset(std::move(dataset));
53
54 return msg;
55}
56
60network::dimse::dimse_message make_n_set_rq(
61 uint16_t message_id,
62 std::string_view sop_class_uid,
63 std::string_view sop_instance_uid,
64 core::dicom_dataset modifications) {
65
66 using namespace network::dimse;
67 using namespace encoding;
68
69 dimse_message msg{command_field::n_set_rq, message_id};
70 msg.set_affected_sop_class_uid(sop_class_uid);
71 msg.command_set().set_string(
72 tag_requested_sop_instance_uid,
73 vr_type::UI,
74 std::string(sop_instance_uid));
75 msg.set_dataset(std::move(modifications));
76
77 return msg;
78}
79
83network::dimse::dimse_message make_n_get_rq(
84 uint16_t message_id,
85 std::string_view sop_class_uid,
86 std::string_view sop_instance_uid) {
87
88 using namespace network::dimse;
89 using namespace encoding;
90
91 dimse_message msg{command_field::n_get_rq, message_id};
92 msg.set_affected_sop_class_uid(sop_class_uid);
93 msg.command_set().set_string(
94 tag_requested_sop_instance_uid,
95 vr_type::UI,
96 std::string(sop_instance_uid));
97
98 return msg;
99}
100
104network::dimse::dimse_message make_n_action_rq(
105 uint16_t message_id,
106 std::string_view sop_class_uid,
107 std::string_view sop_instance_uid,
108 uint16_t action_type_id) {
109
110 using namespace network::dimse;
111
112 dimse_message msg{command_field::n_action_rq, message_id};
113 msg.set_affected_sop_class_uid(sop_class_uid);
114 msg.set_affected_sop_instance_uid(sop_instance_uid);
115 msg.set_action_type_id(action_type_id);
116
117 return msg;
118}
119
123network::dimse::dimse_message make_n_delete_rq(
124 uint16_t message_id,
125 std::string_view sop_class_uid,
126 std::string_view sop_instance_uid) {
127
128 using namespace network::dimse;
129
130 dimse_message msg{command_field::n_delete_rq, message_id};
131 msg.set_affected_sop_class_uid(sop_class_uid);
132 msg.set_affected_sop_instance_uid(sop_instance_uid);
133
134 return msg;
135}
136
137} // namespace
138
139// =============================================================================
140// Construction
141// =============================================================================
142
143print_scu::print_scu(std::shared_ptr<di::ILogger> logger)
144 : logger_(logger ? std::move(logger) : di::null_logger()) {}
145
147 std::shared_ptr<di::ILogger> logger)
148 : logger_(logger ? std::move(logger) : di::null_logger()),
149 config_(config) {}
150
151// =============================================================================
152// Film Session Operations
153// =============================================================================
154
157 const print_session_data& data) {
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
185 std::to_string(data.number_of_copies));
186 if (!data.print_priority.empty()) {
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()) {
194 }
195 if (!data.film_session_label.empty()) {
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}
253
256 std::string_view session_uid) {
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}
325
326// =============================================================================
327// Film Box Operations
328// =============================================================================
329
332 const print_film_box_data& data) {
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
354 if (!data.image_display_format.empty()) {
357 }
358 if (!data.film_orientation.empty()) {
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()) {
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;
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}
439
442 std::string_view film_box_uid) {
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}
515
518 std::string_view film_box_uid) {
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}
587
588// =============================================================================
589// Image Box Operations
590// =============================================================================
591
594 std::string_view image_box_uid,
595 const print_image_data& data,
596 bool use_color) {
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
622
623 if (data.image_position > 0) {
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}
681
682// =============================================================================
683// Printer Status
684// =============================================================================
685
687 network::association& assoc) {
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}
766
767// =============================================================================
768// Statistics
769// =============================================================================
770
771size_t print_scu::sessions_created() const noexcept {
772 return sessions_created_.load(std::memory_order_relaxed);
773}
774
775size_t print_scu::film_boxes_created() const noexcept {
776 return film_boxes_created_.load(std::memory_order_relaxed);
777}
778
779size_t print_scu::images_set() const noexcept {
780 return images_set_.load(std::memory_order_relaxed);
781}
782
783size_t print_scu::prints_executed() const noexcept {
784 return prints_executed_.load(std::memory_order_relaxed);
785}
786
787size_t print_scu::printer_queries() const noexcept {
788 return printer_queries_.load(std::memory_order_relaxed);
789}
790
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}
798
799// =============================================================================
800// Private Implementation
801// =============================================================================
802
803std::optional<uint8_t> print_scu::find_print_context(
805 std::string_view sop_class_uid) const {
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
815 if (context) {
816 return context;
817 }
818
820 return context;
821}
822
823std::string print_scu::generate_uid() const {
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}
834
835uint16_t print_scu::next_message_id() noexcept {
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}
842
843} // namespace kcenon::pacs::services
auto get(dicom_tag tag) noexcept -> dicom_element *
Get a pointer to the element with the given tag.
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.
bool is_established() const noexcept
Check if association is established and ready for DIMSE.
Result< std::monostate > send_dimse(uint8_t context_id, const dimse::dimse_message &msg)
Send a DIMSE message.
Result< std::pair< uint8_t, dimse::dimse_message > > receive_dimse(duration timeout=default_timeout)
Receive a DIMSE message.
std::optional< uint8_t > accepted_context_id(std::string_view abstract_syntax) const
Get the presentation context ID for an abstract syntax.
print_scu(std::shared_ptr< di::ILogger > logger=nullptr)
Construct Print SCU with default configuration.
size_t film_boxes_created() const noexcept
network::Result< print_result > create_film_session(network::association &assoc, const print_session_data &data)
Create a new Film Session (N-CREATE)
network::Result< print_result > print_film_box(network::association &assoc, std::string_view film_box_uid)
Print a Film Box (N-ACTION)
network::Result< print_result > create_film_box(network::association &assoc, const print_film_box_data &data)
Create a new Film Box (N-CREATE)
size_t prints_executed() const noexcept
std::atomic< size_t > sessions_created_
Statistics.
Definition print_scu.h:418
size_t sessions_created() const noexcept
network::Result< print_result > delete_film_box(network::association &assoc, std::string_view film_box_uid)
Delete a Film Box (N-DELETE)
network::Result< print_result > delete_film_session(network::association &assoc, std::string_view session_uid)
Delete a Film Session (N-DELETE)
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.
network::Result< print_result > query_printer_status(network::association &assoc)
Query printer status (N-GET)
size_t printer_queries() const noexcept
std::atomic< uint16_t > message_id_counter_
Message ID counter.
Definition print_scu.h:415
std::atomic< size_t > printer_queries_
Definition print_scu.h:422
std::string generate_uid() const
Generate a unique SOP Instance UID.
print_scu_config config_
Configuration.
Definition print_scu.h:412
network::Result< print_result > set_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)
std::shared_ptr< di::ILogger > logger_
Logger instance.
Definition print_scu.h:409
size_t images_set() const noexcept
std::atomic< size_t > film_boxes_created_
Definition print_scu.h:419
std::atomic< size_t > prints_executed_
Definition print_scu.h:421
uint16_t next_message_id() noexcept
Get the next message ID for DIMSE operations.
std::atomic< size_t > images_set_
Definition print_scu.h:420
DIMSE command field enumeration.
Compile-time constants for commonly used DICOM tags.
constexpr dicom_tag message_id
Message ID.
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 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 tag_requested_sop_instance_uid
Requested SOP Instance UID (0000,1001) - UI.
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 image_position
Image Position (2020,0010)
Definition print_scp.h:447
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 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 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
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
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_color_image_box_sop_class_uid
Basic Color Image Box SOP Class UID.
Definition print_scp.h:52
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
constexpr std::string_view basic_grayscale_image_box_sop_class_uid
Basic Grayscale Image Box SOP Class UID.
Definition print_scp.h:48
constexpr std::string_view printer_sop_class_uid
Printer SOP Class UID.
Definition print_scp.h:56
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
DICOM Print Management SCU service (PS3.4 Annex H)
Result<T> type aliases and helpers for PACS system.
DIMSE status codes.
Data for creating a Film Box via N-CREATE.
Definition print_scu.h:105
std::string magnification_type
Magnification type (REPLICATE, BILINEAR, CUBIC, NONE)
Definition print_scu.h:116
std::string film_orientation
Film orientation (PORTRAIT, LANDSCAPE)
Definition print_scu.h:110
std::string film_size_id
Film size ID (8INX10IN, 14INX17IN, etc.)
Definition print_scu.h:113
std::string image_display_format
Image display format (e.g., "STANDARD\\1,1")
Definition print_scu.h:107
std::string film_session_uid
Parent film session SOP Instance UID.
Definition print_scu.h:119
Data for setting an Image Box via N-SET.
Definition print_scu.h:125
core::dicom_dataset pixel_data
Pixel data to set on the image box.
Definition print_scu.h:127
uint16_t image_position
Image position within the film box (1-based)
Definition print_scu.h:130
Result of a Print DIMSE-N operation.
Definition print_scu.h:47
core::dicom_dataset response_data
Response dataset (e.g., printer status, referenced image box UIDs)
Definition print_scu.h:61
std::chrono::milliseconds elapsed
Time taken for the operation.
Definition print_scu.h:58
std::string error_comment
Error comment from the SCP (if any)
Definition print_scu.h:55
uint16_t status
DIMSE status code (0x0000 = success)
Definition print_scu.h:52
std::string sop_instance_uid
SOP Instance UID (session, film box, image box, or printer)
Definition print_scu.h:49
bool is_success() const noexcept
Check if the operation was successful.
Definition print_scu.h:64
Configuration for Print SCU service.
Definition print_scu.h:136
bool auto_generate_uid
Auto-generate SOP Instance UIDs if not provided.
Definition print_scu.h:141
std::chrono::milliseconds timeout
Timeout for receiving DIMSE response.
Definition print_scu.h:138
Data for creating a Film Session via N-CREATE.
Definition print_scu.h:82
std::string medium_type
Medium type (PAPER, CLEAR FILM, BLUE FILM)
Definition print_scu.h:93
std::string film_session_label
Film session label.
Definition print_scu.h:99
std::string film_destination
Film destination (MAGAZINE, PROCESSOR)
Definition print_scu.h:96
std::string sop_instance_uid
SOP Instance UID (generated if empty)
Definition print_scu.h:84
uint32_t number_of_copies
Number of copies to print.
Definition print_scu.h:87
std::string print_priority
Print priority (HIGH, MED, LOW)
Definition print_scu.h:90