PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
print_scp.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 <sstream>
20
21namespace kcenon::pacs::services {
22
23// =============================================================================
24// Construction
25// =============================================================================
26
27print_scp::print_scp(std::shared_ptr<di::ILogger> logger)
28 : scp_service(std::move(logger)) {}
29
30// =============================================================================
31// Configuration
32// =============================================================================
33
35 session_handler_ = std::move(handler);
36}
37
39 print_handler_ = std::move(handler);
40}
41
45
46// =============================================================================
47// scp_service Interface
48// =============================================================================
49
50std::vector<std::string> print_scp::supported_sop_classes() const {
51 return {
56 std::string(printer_sop_class_uid),
59 };
60}
61
64 uint8_t context_id,
65 const network::dimse::dimse_message& request) {
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}
87
88std::string_view print_scp::service_name() const noexcept {
89 return "Print SCP";
90}
91
92// =============================================================================
93// Statistics
94// =============================================================================
95
96size_t print_scp::sessions_created() const noexcept {
97 return sessions_created_.load();
98}
99
100size_t print_scp::film_boxes_created() const noexcept {
101 return film_boxes_created_.load();
102}
103
104size_t print_scp::images_set() const noexcept {
105 return images_set_.load();
106}
107
108size_t print_scp::prints_executed() const noexcept {
109 return prints_executed_.load();
110}
111
112size_t print_scp::printer_queries() const noexcept {
113 return printer_queries_.load();
114}
115
119 images_set_ = 0;
122}
123
124// =============================================================================
125// N-CREATE Handler
126// =============================================================================
127
130 uint8_t context_id,
131 const network::dimse::dimse_message& request) {
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}
149
150// =============================================================================
151// N-SET Handler
152// =============================================================================
153
156 uint8_t context_id,
157 const network::dimse::dimse_message& request) {
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}
283
284// =============================================================================
285// N-GET Handler (Printer Status)
286// =============================================================================
287
290 uint8_t context_id,
291 const network::dimse::dimse_message& request) {
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}
351
352// =============================================================================
353// N-ACTION Handler (Print Film Box)
354// =============================================================================
355
358 uint8_t context_id,
359 const network::dimse::dimse_message& request) {
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}
430
431// =============================================================================
432// N-DELETE Handler
433// =============================================================================
434
437 uint8_t context_id,
438 const network::dimse::dimse_message& request) {
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}
510
511// =============================================================================
512// Film Session Creation
513// =============================================================================
514
517 uint8_t context_id,
518 const network::dimse::dimse_message& request) {
519
520 using namespace network::dimse;
521
522 auto sop_instance_uid = request.affected_sop_instance_uid();
523 if (sop_instance_uid.empty()) {
524 sop_instance_uid = generate_uid();
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,
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,
573 sop_instance_uid, status_success);
574}
575
576// =============================================================================
577// Film Box Creation
578// =============================================================================
579
582 uint8_t context_id,
583 const network::dimse::dimse_message& request) {
584
585 using namespace network::dimse;
586
587 auto sop_instance_uid = request.affected_sop_instance_uid();
588 if (sop_instance_uid.empty()) {
589 sop_instance_uid = generate_uid();
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)) {
601 }
602 if (ds.contains(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;
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;
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}
674
675// =============================================================================
676// Response Helper
677// =============================================================================
678
681 uint8_t context_id,
682 network::dimse::command_field response_type,
683 uint16_t message_id,
684 std::string_view sop_class_uid,
685 const std::string& sop_instance_uid,
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}
701
702// =============================================================================
703// UID Generation
704// =============================================================================
705
706auto print_scp::generate_uid() -> std::string {
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}
715
716} // namespace kcenon::pacs::services
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_
Definition print_scp.h:402
size_t printer_queries() const noexcept
print_session_handler session_handler_
Definition print_scp.h:380
std::vector< std::string > supported_sop_classes() const override
Get supported SOP Class UIDs.
Definition print_scp.cpp:50
size_t sessions_created() const noexcept
Definition print_scp.cpp:96
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.
Definition print_scp.h:400
print_action_handler print_handler_
Definition print_scp.h:381
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.
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
std::atomic< size_t > prints_executed_
Definition print_scp.h:403
size_t prints_executed() const noexcept
size_t images_set() const noexcept
std::string_view service_name() const noexcept override
Get the service name.
Definition print_scp.cpp:88
void set_session_handler(print_session_handler handler)
Set handler for film session creation.
Definition print_scp.cpp:34
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_
Definition print_scp.h:382
void set_print_handler(print_action_handler handler)
Set handler for print action (film box print)
Definition print_scp.cpp:38
std::atomic< size_t > printer_queries_
Definition print_scp.h:404
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_
Definition print_scp.h:401
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.
Definition print_scp.cpp:62
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
print_scp(std::shared_ptr< di::ILogger > logger=nullptr)
Construct Print SCP with optional logger.
Definition print_scp.cpp:27
void set_printer_status_handler(printer_status_handler handler)
Set handler for printer status query.
Definition print_scp.cpp:42
DIMSE command field enumeration.
Compile-time constants for commonly used DICOM tags.
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_unexpected_command
Definition result.h:198
command_field
DIMSE command field values.
uint16_t status_code
DIMSE status code type alias.
constexpr core::dicom_tag printer_status_tag
Printer Status (2110,0010)
Definition print_scp.h:456
constexpr core::dicom_tag image_display_format
Image Display Format (2010,0010)
Definition print_scp.h:429
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 medium_type
Medium Type (2000,0030)
Definition print_scp.h:420
constexpr core::dicom_tag referenced_image_box_sequence
Referenced Image Box Sequence (2010,0510)
Definition print_scp.h:444
constexpr core::dicom_tag number_of_copies
Number of Copies (2000,0010)
Definition print_scp.h:414
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 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
std::function< network::Result< std::monostate >( const film_session &session)> print_session_handler
Handler for film session creation.
Definition print_scp.h:188
constexpr std::string_view basic_color_image_box_sop_class_uid
Basic Color Image Box SOP Class UID.
Definition print_scp.h:52
std::function< network::Result< std::monostate >( const std::string &film_box_uid)> print_action_handler
Handler for print action (film box print)
Definition print_scp.h:197
std::function< network::Result< std::pair< printer_status, core::dicom_dataset > >()> printer_status_handler
Handler for printer status query.
Definition print_scp.h:205
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
VoidResult pacs_void_error(int code, const std::string &message, const std::string &details="")
Create a PACS void error result.
Definition result.h:249
DICOM Print Management SCP service (PS3.4 Annex H)
Result<T> type aliases and helpers for PACS system.
DIMSE status codes.
Film box data created by N-CREATE.
Definition print_scp.h:135
std::string film_orientation
Film orientation (PORTRAIT, LANDSCAPE)
Definition print_scp.h:146
std::vector< std::string > image_box_uids
Associated image box UIDs.
Definition print_scp.h:152
std::string sop_instance_uid
SOP Instance UID.
Definition print_scp.h:137
std::string film_size_id
Film size ID (8INX10IN, 14INX17IN, etc.)
Definition print_scp.h:149
std::string film_session_uid
Parent film session UID.
Definition print_scp.h:140
std::string image_display_format
Image display format (STANDARD\1,1 etc.)
Definition print_scp.h:143
core::dicom_dataset data
Complete dataset from request.
Definition print_scp.h:155
Film session data created by N-CREATE.
Definition print_scp.h:109
std::string sop_instance_uid
SOP Instance UID.
Definition print_scp.h:111
uint32_t number_of_copies
Number of copies.
Definition print_scp.h:114
std::string print_priority
Print priority (HIGH, MED, LOW)
Definition print_scp.h:117
core::dicom_dataset data
Complete dataset from request.
Definition print_scp.h:129
std::string film_destination
Film destination (MAGAZINE, PROCESSOR)
Definition print_scp.h:123
std::string medium_type
Medium type (PAPER, CLEAR FILM, BLUE FILM)
Definition print_scp.h:120
Image box data set by N-SET.
Definition print_scp.h:161
std::string film_box_uid
Parent film box UID.
Definition print_scp.h:166
std::string sop_instance_uid
SOP Instance UID.
Definition print_scp.h:163
uint16_t image_position
Image position (1-based)
Definition print_scp.h:169