37constexpr const char* default_calling_ae =
"PRINT_SCU";
45enum class print_command {
57 std::string called_ae;
58 std::string calling_ae{default_calling_ae};
61 print_command command{print_command::print};
68 std::string session_label;
71 std::string display_format{
"STANDARD\\1,1"};
72 std::string orientation{
"PORTRAIT"};
73 std::string film_size{
"8INX10IN"};
74 std::string magnification;
83void print_usage(
const char* program_name) {
85Print SCU - DICOM Print Management Client
87Usage: )" << program_name << R"( <host> <port> <called_ae> <command> [options]
90 host Remote host address (IP or hostname)
91 port Remote port number (typically 10400)
92 called_ae Called AE Title (remote Print SCP's AE title)
93 command 'print' or 'status'
96 print Execute full print workflow (session + film box + print + cleanup)
97 status Query printer status via N-GET
99Film Session Options (for 'print' command):
100 --copies <n> Number of copies [default: 1]
101 --priority <p> Print priority: HIGH, MED, LOW [default: MED]
102 --medium <type> Medium type: PAPER, CLEAR FILM, BLUE FILM [default: BLUE FILM]
103 --destination <dest> Film destination: MAGAZINE, PROCESSOR [default: MAGAZINE]
104 --label <text> Film session label
106Film Box Options (for 'print' command):
107 --format <fmt> Image display format [default: STANDARD\1,1]
108 --orientation <o> Film orientation: PORTRAIT, LANDSCAPE [default: PORTRAIT]
109 --film-size <size> Film size: 8INX10IN, 14INX17IN, etc. [default: 8INX10IN]
110 --magnification <m> Magnification type: REPLICATE, BILINEAR, CUBIC, NONE
113 --calling-ae <ae> Calling AE Title [default: PRINT_SCU]
114 --verbose, -v Show detailed progress
115 --help, -h Show this help message
118 # Print with default settings
119 )" << program_name << R"( localhost 10400 PRINTER print
121 # Print with custom options
122 )" << program_name << R"( localhost 10400 PRINTER print \
123 --copies 2 --priority HIGH --medium PAPER \
124 --format "STANDARD\2,2" --orientation LANDSCAPE
126 # Query printer status
127 )" << program_name << R"( localhost 10400 PRINTER status
131 1 Print operation failed
132 2 Connection or argument error
139bool parse_arguments(
int argc,
char* argv[], options& opts) {
148 int port_int = std::stoi(argv[2]);
149 if (port_int < 1 || port_int > 65535) {
150 std::cerr <<
"Error: Port must be between 1 and 65535\n";
153 opts.port =
static_cast<uint16_t
>(port_int);
154 }
catch (
const std::exception&) {
155 std::cerr <<
"Error: Invalid port number '" << argv[2] <<
"'\n";
159 opts.called_ae = argv[3];
160 if (opts.called_ae.length() > 16) {
161 std::cerr <<
"Error: Called AE title exceeds 16 characters\n";
166 std::string cmd = argv[4];
167 if (cmd ==
"print") {
168 opts.command = print_command::print;
169 }
else if (cmd ==
"status") {
170 opts.command = print_command::status;
171 }
else if (cmd ==
"--help" || cmd ==
"-h") {
174 std::cerr <<
"Error: Unknown command '" << cmd <<
"'. Use 'print' or 'status'\n";
179 for (
int i = 5; i < argc; ++i) {
180 std::string arg = argv[i];
182 if (arg ==
"--help" || arg ==
"-h") {
185 if (arg ==
"--verbose" || arg ==
"-v") {
187 }
else if (arg ==
"--calling-ae" && i + 1 < argc) {
188 opts.calling_ae = argv[++i];
189 if (opts.calling_ae.length() > 16) {
190 std::cerr <<
"Error: Calling AE title exceeds 16 characters\n";
195 else if (arg ==
"--copies" && i + 1 < argc) {
196 opts.copies =
static_cast<uint32_t
>(std::stoul(argv[++i]));
197 }
else if (arg ==
"--priority" && i + 1 < argc) {
198 opts.priority = argv[++i];
199 }
else if (arg ==
"--medium" && i + 1 < argc) {
200 opts.medium_type = argv[++i];
201 }
else if (arg ==
"--destination" && i + 1 < argc) {
202 opts.film_destination = argv[++i];
203 }
else if (arg ==
"--label" && i + 1 < argc) {
204 opts.session_label = argv[++i];
207 else if (arg ==
"--format" && i + 1 < argc) {
208 opts.display_format = argv[++i];
209 }
else if (arg ==
"--orientation" && i + 1 < argc) {
210 opts.orientation = argv[++i];
211 }
else if (arg ==
"--film-size" && i + 1 < argc) {
212 opts.film_size = argv[++i];
213 }
else if (arg ==
"--magnification" && i + 1 < argc) {
214 opts.magnification = argv[++i];
216 std::cerr <<
"Error: Unknown option '" << arg <<
"'\n";
228 const options& opts) {
243 std::string(basic_grayscale_print_meta_sop_class_uid),
245 "1.2.840.10008.1.2.1",
253 std::string(basic_film_session_sop_class_uid),
254 {
"1.2.840.10008.1.2.1",
"1.2.840.10008.1.2"}
258 std::string(basic_film_box_sop_class_uid),
259 {
"1.2.840.10008.1.2.1",
"1.2.840.10008.1.2"}
263 std::string(basic_grayscale_image_box_sop_class_uid),
264 {
"1.2.840.10008.1.2.1",
"1.2.840.10008.1.2"}
268 std::string(printer_sop_class_uid),
269 {
"1.2.840.10008.1.2.1",
"1.2.840.10008.1.2"}
272 return association::connect(opts.host, opts.port, config, default_timeout);
278int perform_print(
const options& opts) {
282 std::cout <<
"=== Print Workflow ===\n";
283 std::cout <<
"Connecting to " << opts.host <<
":" << opts.port <<
"...\n";
284 std::cout <<
" Calling AE: " << opts.calling_ae <<
"\n";
285 std::cout <<
" Called AE: " << opts.called_ae <<
"\n";
286 std::cout <<
" Copies: " << opts.copies <<
"\n";
287 std::cout <<
" Priority: " << opts.priority <<
"\n";
288 std::cout <<
" Medium: " << opts.medium_type <<
"\n";
289 std::cout <<
" Format: " << opts.display_format <<
"\n";
290 std::cout <<
" Orientation: " << opts.orientation <<
"\n";
291 std::cout <<
" Film Size: " << opts.film_size <<
"\n\n";
294 auto start_time = std::chrono::steady_clock::now();
297 auto connect_result = create_print_association(opts);
298 if (connect_result.is_err()) {
299 std::cerr <<
"Failed to establish association: "
300 << connect_result.error().message <<
"\n";
303 auto& assoc = connect_result.value();
306 auto connect_time = std::chrono::steady_clock::now();
307 auto connect_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
308 connect_time - start_time);
309 std::cout <<
"Association established in " << connect_ms.count() <<
" ms\n";
316 std::cout <<
"\n[1/4] Creating Film Session...\n";
327 if (session_result.is_err()) {
328 std::cerr <<
"Failed to create Film Session: "
329 << session_result.error().message <<
"\n";
333 if (!session_result.value().is_success()) {
334 std::cerr <<
"Film Session creation returned status: 0x"
335 << std::hex << session_result.value().status << std::dec <<
"\n";
336 (void)assoc.release(default_timeout);
340 auto session_uid = session_result.value().sop_instance_uid;
342 std::cout <<
" Film Session UID: " << session_uid <<
"\n";
347 std::cout <<
"\n[2/4] Creating Film Box...\n";
358 if (box_result.is_err()) {
359 std::cerr <<
"Failed to create Film Box: "
360 << box_result.error().message <<
"\n";
362 (void)assoc.release(default_timeout);
365 if (!box_result.value().is_success()) {
366 std::cerr <<
"Film Box creation returned status: 0x"
367 << std::hex << box_result.value().status << std::dec <<
"\n";
369 (void)assoc.release(default_timeout);
373 auto film_box_uid = box_result.value().sop_instance_uid;
375 std::cout <<
" Film Box UID: " << film_box_uid <<
"\n";
380 std::cout <<
"\n[3/4] Printing Film Box...\n";
384 if (print_result_val.is_err()) {
385 std::cerr <<
"Failed to print Film Box: "
386 << print_result_val.error().message <<
"\n";
388 (void)assoc.release(default_timeout);
391 if (!print_result_val.value().is_success()) {
392 std::cerr <<
"Print action returned status: 0x"
393 << std::hex << print_result_val.value().status << std::dec <<
"\n";
394 if (!print_result_val.value().error_comment.empty()) {
395 std::cerr <<
" Error: " << print_result_val.value().error_comment <<
"\n";
398 (void)assoc.release(default_timeout);
404 std::cout <<
"\n[4/4] Cleaning up (deleting Film Session)...\n";
408 if (delete_result.is_err() && opts.verbose) {
409 std::cerr <<
"Warning: Film Session delete failed: "
410 << delete_result.error().message <<
"\n";
415 std::cout <<
"\nReleasing association...\n";
417 auto release_result = assoc.release(default_timeout);
418 if (release_result.is_err() && opts.verbose) {
419 std::cerr <<
"Warning: Release failed: " << release_result.error().message <<
"\n";
422 auto end_time = std::chrono::steady_clock::now();
423 auto total_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
424 end_time - start_time);
428 std::cout <<
"========================================\n";
429 std::cout <<
" Print Completed Successfully\n";
430 std::cout <<
"========================================\n";
431 std::cout <<
" Session UID: " << session_uid <<
"\n";
432 std::cout <<
" Film Box UID: " << film_box_uid <<
"\n";
433 std::cout <<
" Copies: " << opts.copies <<
"\n";
434 std::cout <<
" Priority: " << opts.priority <<
"\n";
435 std::cout <<
" Medium: " << opts.medium_type <<
"\n";
436 std::cout <<
" Film Size: " << opts.film_size <<
"\n";
437 std::cout <<
" Total time: " << total_ms.count() <<
" ms\n";
438 std::cout <<
"========================================\n";
446int perform_status_query(
const options& opts) {
450 std::cout <<
"=== Printer Status Query ===\n";
451 std::cout <<
"Connecting to " << opts.host <<
":" << opts.port <<
"...\n";
452 std::cout <<
" Calling AE: " << opts.calling_ae <<
"\n";
453 std::cout <<
" Called AE: " << opts.called_ae <<
"\n\n";
456 auto start_time = std::chrono::steady_clock::now();
458 auto connect_result = create_print_association(opts);
459 if (connect_result.is_err()) {
460 std::cerr <<
"Failed to establish association: "
461 << connect_result.error().message <<
"\n";
464 auto& assoc = connect_result.value();
467 auto connect_time = std::chrono::steady_clock::now();
468 auto connect_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
469 connect_time - start_time);
470 std::cout <<
"Association established in " << connect_ms.count() <<
" ms\n";
471 std::cout <<
"Sending N-GET Printer Status...\n";
477 if (status_result.is_err()) {
478 std::cerr <<
"Failed to query printer status: "
479 << status_result.error().message <<
"\n";
484 if (!status_result.value().is_success()) {
485 std::cerr <<
"Printer status query returned status: 0x"
486 << std::hex << status_result.value().status << std::dec <<
"\n";
487 (void)assoc.release(default_timeout);
492 const auto& response = status_result.value().response_data;
493 std::string printer_status_str =
"UNKNOWN";
495 std::string printer_name_str;
497 if (response.contains(print_tags::printer_status_tag)) {
498 printer_status_str = response.get_string(print_tags::printer_status_tag);
500 if (response.contains(print_tags::printer_status_info)) {
503 if (response.contains(print_tags::printer_name)) {
504 printer_name_str = response.get_string(print_tags::printer_name);
509 std::cout <<
"Releasing association...\n";
511 auto release_result = assoc.release(default_timeout);
512 if (release_result.is_err() && opts.verbose) {
513 std::cerr <<
"Warning: Release failed: " << release_result.error().message <<
"\n";
516 auto end_time = std::chrono::steady_clock::now();
517 auto total_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
518 end_time - start_time);
522 std::cout <<
"========================================\n";
523 std::cout <<
" Printer Status\n";
524 std::cout <<
"========================================\n";
525 std::cout <<
" Status: " << printer_status_str <<
"\n";
529 if (!printer_name_str.empty()) {
530 std::cout <<
" Printer Name: " << printer_name_str <<
"\n";
532 std::cout <<
" Query time: " << total_ms.count() <<
" ms\n";
533 std::cout <<
"========================================\n";
540int main(
int argc,
char* argv[]) {
542 ____ _ _ ____ ____ _ _
543 | _ \ _ __(_)_ __ | |_ / ___| / ___| | | |
544 | |_) | '__| | '_ \| __| \___ \| | | | | |
545 | __/| | | | | | | |_ ___) | |___| |_| |
546 |_| |_| |_|_| |_|\__| |____/ \____|\___/
548 DICOM Print Management Client
553 if (!parse_arguments(argc, argv, opts)) {
554 print_usage(argv[0]);
558 if (opts.command == print_command::print) {
559 return perform_print(opts);
561 return perform_status_query(opts);
DICOM Association management per PS3.8.
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)
network::Result< print_result > delete_film_session(network::association &assoc, std::string_view session_uid)
Delete a Film Session (N-DELETE)
network::Result< print_result > query_printer_status(network::association &assoc)
Query printer status (N-GET)
std::chrono::milliseconds default_timeout()
Default timeout for test operations (5s normal, 30s CI)
@ print
Print Management Service Class.
DICOM Print Management SCU service (PS3.4 Annex H)
Configuration for SCU association request.
std::string called_ae_title
Remote AE Title (16 chars max)
std::string calling_ae_title
Our AE Title (16 chars max)
std::string implementation_class_uid
std::string implementation_version_name
std::vector< proposed_presentation_context > proposed_contexts
Data for creating a Film Box via N-CREATE.
std::string magnification_type
Magnification type (REPLICATE, BILINEAR, CUBIC, NONE)
std::string film_orientation
Film orientation (PORTRAIT, LANDSCAPE)
std::string film_size_id
Film size ID (8INX10IN, 14INX17IN, etc.)
std::string image_display_format
Image display format (e.g., "STANDARD\\1,1")
std::string film_session_uid
Parent film session SOP Instance UID.
Data for creating a Film Session via N-CREATE.
std::string medium_type
Medium type (PAPER, CLEAR FILM, BLUE FILM)
std::string film_session_label
Film session label.
std::string film_destination
Film destination (MAGAZINE, PROCESSOR)
uint32_t number_of_copies
Number of copies to print.
std::string print_priority
Print priority (HIGH, MED, LOW)