41constexpr const char* default_calling_ae =
"RETRIEVE_SCU";
47constexpr int progress_bar_width = 40;
52enum class retrieve_mode {
60enum class retrieve_level {
70enum class storage_structure {
82 std::string called_ae;
83 std::string calling_ae{default_calling_ae};
86 retrieve_mode mode{retrieve_mode::c_get};
87 std::string query_model{
"study"};
91 uint16_t local_storage_port{0};
94 retrieve_level level{retrieve_level::study};
96 std::string study_uid;
97 std::string series_uid;
101 std::filesystem::path output_dir{
"./downloads"};
102 storage_structure structure{storage_structure::hierarchical};
103 bool overwrite{
false};
104 bool show_progress{
true};
111void print_usage(
const char* program_name) {
113Retrieve SCU - DICOM C-MOVE/C-GET Client
115Usage: )" << program_name << R"( <host> <port> <called_ae> [options]
118 host Remote host address (IP or hostname)
119 port Remote port number (typically 104 or 11112)
120 called_ae Called AE Title (remote SCP's AE title)
123 --mode <mode> Retrieve mode: move, get (default: get)
124 move: Transfer to destination AE (requires --dest-ae)
125 get: Direct retrieval to local machine
127 --dest-ae <ae> Destination AE Title (for C-MOVE mode)
128 --local-port <port> Local Storage SCP port (for C-MOVE, default: auto)
131 --model <model> Query model: patient, study (default: study)
133Retrieve Level and Identifiers:
134 --level <level> Retrieve level: PATIENT, STUDY, SERIES, IMAGE
136 --patient-id <id> Patient ID (for PATIENT level)
137 --study-uid <uid> Study Instance UID
138 --series-uid <uid> Series Instance UID
139 --sop-instance-uid <uid> SOP Instance UID (for IMAGE level)
142 --output, -o <dir> Output directory (default: ./downloads)
143 --structure <type> Storage structure: hierarchical, flat (default: hierarchical)
144 --overwrite Overwrite existing files (default: skip)
145 --no-progress Disable progress display
148 --calling-ae <ae> Calling AE Title (default: RETRIEVE_SCU)
149 --verbose, -v Show detailed progress
150 --help, -h Show this help message
153 # C-GET: Retrieve study directly
154 )" << program_name << R"( localhost 11112 PACS_SCP --mode get --study-uid "1.2.3.4.5" -o ./data
156 # C-MOVE: Transfer study to another PACS
157 )" << program_name << R"( localhost 11112 PACS_SCP --mode move --dest-ae LOCAL_SCP --study-uid "1.2.3.4.5"
159 # Retrieve specific series
160 )" << program_name << R"( localhost 11112 PACS_SCP --level SERIES --series-uid "1.2.3.4.5.6"
162 # Retrieve all studies for a patient
163 )" << program_name << R"( localhost 11112 PACS_SCP --level PATIENT --patient-id "12345"
166 0 Success - Retrieval completed
167 1 Partial success - Some images failed
168 2 Error - Retrieval failed or invalid arguments
175std::optional<retrieve_mode> parse_mode(std::string_view mode_str) {
176 if (mode_str ==
"move" || mode_str ==
"MOVE" || mode_str ==
"c-move") {
177 return retrieve_mode::c_move;
179 if (mode_str ==
"get" || mode_str ==
"GET" || mode_str ==
"c-get") {
180 return retrieve_mode::c_get;
188std::optional<retrieve_level> parse_level(std::string_view level_str) {
189 if (level_str ==
"PATIENT" || level_str ==
"patient") {
190 return retrieve_level::patient;
192 if (level_str ==
"STUDY" || level_str ==
"study") {
193 return retrieve_level::study;
195 if (level_str ==
"SERIES" || level_str ==
"series") {
196 return retrieve_level::series;
198 if (level_str ==
"IMAGE" || level_str ==
"image" ||
199 level_str ==
"INSTANCE" || level_str ==
"instance") {
200 return retrieve_level::image;
208std::string_view to_string(retrieve_level level) {
210 case retrieve_level::patient:
return "PATIENT";
211 case retrieve_level::study:
return "STUDY";
212 case retrieve_level::series:
return "SERIES";
213 case retrieve_level::image:
return "IMAGE";
214 default:
return "UNKNOWN";
221bool parse_arguments(
int argc,
char* argv[], options& opts) {
230 int port_int = std::stoi(argv[2]);
231 if (port_int < 1 || port_int > 65535) {
232 std::cerr <<
"Error: Port must be between 1 and 65535\n";
235 opts.port =
static_cast<uint16_t
>(port_int);
236 }
catch (
const std::exception&) {
237 std::cerr <<
"Error: Invalid port number '" << argv[2] <<
"'\n";
241 opts.called_ae = argv[3];
242 if (opts.called_ae.length() > 16) {
243 std::cerr <<
"Error: Called AE title exceeds 16 characters\n";
248 for (
int i = 4; i < argc; ++i) {
249 std::string arg = argv[i];
251 if (arg ==
"--help" || arg ==
"-h") {
254 if (arg ==
"--verbose" || arg ==
"-v") {
256 }
else if (arg ==
"--mode" && i + 1 < argc) {
257 auto mode = parse_mode(argv[++i]);
259 std::cerr <<
"Error: Invalid mode '" << argv[i] <<
"'\n";
263 }
else if (arg ==
"--model" && i + 1 < argc) {
264 opts.query_model = argv[++i];
265 if (opts.query_model !=
"patient" && opts.query_model !=
"study") {
266 std::cerr <<
"Error: Invalid query model (use 'patient' or 'study')\n";
269 }
else if (arg ==
"--dest-ae" && i + 1 < argc) {
270 opts.move_destination = argv[++i];
271 if (opts.move_destination.length() > 16) {
272 std::cerr <<
"Error: Destination AE title exceeds 16 characters\n";
275 }
else if (arg ==
"--local-port" && i + 1 < argc) {
277 int port_int = std::stoi(argv[++i]);
278 if (port_int < 1 || port_int > 65535) {
279 std::cerr <<
"Error: Local port must be between 1 and 65535\n";
282 opts.local_storage_port =
static_cast<uint16_t
>(port_int);
283 }
catch (
const std::exception&) {
284 std::cerr <<
"Error: Invalid local port number\n";
287 }
else if (arg ==
"--level" && i + 1 < argc) {
288 auto level = parse_level(argv[++i]);
290 std::cerr <<
"Error: Invalid retrieve level '" << argv[i] <<
"'\n";
294 }
else if (arg ==
"--patient-id" && i + 1 < argc) {
295 opts.patient_id = argv[++i];
296 }
else if (arg ==
"--study-uid" && i + 1 < argc) {
297 opts.study_uid = argv[++i];
298 }
else if (arg ==
"--series-uid" && i + 1 < argc) {
299 opts.series_uid = argv[++i];
300 }
else if (arg ==
"--sop-instance-uid" && i + 1 < argc) {
301 opts.sop_instance_uid = argv[++i];
302 }
else if ((arg ==
"--output" || arg ==
"-o") && i + 1 < argc) {
303 opts.output_dir = argv[++i];
304 }
else if (arg ==
"--structure" && i + 1 < argc) {
305 std::string struct_str = argv[++i];
306 if (struct_str ==
"hierarchical") {
307 opts.structure = storage_structure::hierarchical;
308 }
else if (struct_str ==
"flat") {
309 opts.structure = storage_structure::flat;
311 std::cerr <<
"Error: Invalid structure (use 'hierarchical' or 'flat')\n";
314 }
else if (arg ==
"--overwrite") {
315 opts.overwrite =
true;
316 }
else if (arg ==
"--no-progress") {
317 opts.show_progress =
false;
318 }
else if (arg ==
"--calling-ae" && i + 1 < argc) {
319 opts.calling_ae = argv[++i];
320 if (opts.calling_ae.length() > 16) {
321 std::cerr <<
"Error: Calling AE title exceeds 16 characters\n";
325 std::cerr <<
"Error: Unknown option '" << arg <<
"'\n";
336bool validate_options(
const options& opts) {
338 if (opts.mode == retrieve_mode::c_move && opts.move_destination.empty()) {
339 std::cerr <<
"Error: C-MOVE mode requires --dest-ae option\n";
344 bool has_identifier = !opts.patient_id.empty() ||
345 !opts.study_uid.empty() ||
346 !opts.series_uid.empty() ||
347 !opts.sop_instance_uid.empty();
349 if (!has_identifier) {
350 std::cerr <<
"Error: At least one identifier is required "
351 <<
"(--patient-id, --study-uid, --series-uid, or --sop-instance-uid)\n";
356 switch (opts.level) {
357 case retrieve_level::patient:
358 if (opts.patient_id.empty()) {
359 std::cerr <<
"Error: PATIENT level requires --patient-id\n";
363 case retrieve_level::study:
364 if (opts.study_uid.empty()) {
365 std::cerr <<
"Error: STUDY level requires --study-uid\n";
369 case retrieve_level::series:
370 if (opts.series_uid.empty()) {
371 std::cerr <<
"Error: SERIES level requires --series-uid\n";
375 case retrieve_level::image:
376 if (opts.sop_instance_uid.empty()) {
377 std::cerr <<
"Error: IMAGE level requires --sop-instance-uid\n";
389std::string_view get_retrieve_sop_class_uid(
const options& opts) {
390 if (opts.mode == retrieve_mode::c_move) {
391 if (opts.query_model ==
"patient") {
396 if (opts.query_model ==
"patient") {
412 std::string level_str{to_string(opts.level)};
416 if (!opts.patient_id.empty()) {
420 if (!opts.study_uid.empty()) {
424 if (!opts.series_uid.empty()) {
428 if (!opts.sop_instance_uid.empty()) {
438struct progress_state {
439 std::atomic<uint16_t> remaining{0};
441 std::atomic<uint16_t>
failed{0};
442 std::atomic<uint16_t>
warning{0};
443 std::atomic<size_t> bytes_received{0};
444 std::chrono::steady_clock::time_point start_time;
452 start_time = std::chrono::steady_clock::now();
455 [[nodiscard]] uint16_t total()
const {
463void display_progress(
const progress_state& state,
bool verbose) {
464 auto total = state.total();
465 if (total == 0)
return;
467 uint16_t
done = state.completed + state.failed + state.warning;
468 float progress =
static_cast<float>(
done) / total;
471 auto elapsed = std::chrono::steady_clock::now() - state.start_time;
472 auto elapsed_sec = std::chrono::duration<double>(elapsed).count();
473 double speed = elapsed_sec > 0 ? state.bytes_received / elapsed_sec / 1024.0 : 0;
480 int filled =
static_cast<int>(progress * progress_bar_width);
481 for (
int i = 0; i < progress_bar_width; ++i) {
482 if (i < filled) std::cout <<
"=";
483 else if (i == filled) std::cout <<
">";
484 else std::cout <<
" ";
489 std::cout << std::fixed << std::setprecision(1) << (progress * 100) <<
"% ";
490 std::cout <<
"(" <<
done <<
"/" << total <<
") ";
493 std::cout << std::setprecision(1) << speed <<
" KB/s ";
494 if (state.failed > 0) {
495 std::cout <<
"[" << state.failed <<
" failed] ";
499 std::cout << std::flush;
505std::filesystem::path generate_file_path(
511 std::filesystem::path path = opts.output_dir;
513 if (opts.structure == storage_structure::hierarchical) {
516 auto study_uid = dataset.
get_string(tags::study_instance_uid,
"UNKNOWN");
517 auto series_uid = dataset.
get_string(tags::series_instance_uid,
"UNKNOWN");
518 auto sop_uid = dataset.
get_string(tags::sop_instance_uid,
"UNKNOWN");
524 path /= sop_uid +
".dcm";
527 auto sop_uid = dataset.
get_string(tags::sop_instance_uid,
"UNKNOWN");
528 path /= sop_uid +
".dcm";
538 const std::filesystem::path& path,
543 if (std::filesystem::exists(path) && !overwrite) {
548 std::filesystem::create_directories(path.parent_path());
555 auto result = file.save(path);
556 return result.is_ok();
564 std::string_view sop_class_uid,
565 std::string_view move_destination) {
569 dimse_message msg{command_field::c_move_rq,
message_id};
570 msg.set_affected_sop_class_uid(sop_class_uid);
571 msg.set_priority(priority_medium);
574 msg.command_set().set_string(
575 tag_move_destination,
577 std::string(move_destination));
587 std::string_view sop_class_uid) {
591 dimse_message msg{command_field::c_get_rq,
message_id};
592 msg.set_affected_sop_class_uid(sop_class_uid);
593 msg.set_priority(priority_medium);
601int perform_c_get(
const options& opts) {
608 std::cout <<
"Performing C-GET retrieval\n";
609 std::cout <<
" Host: " << opts.host <<
":" << opts.port <<
"\n";
610 std::cout <<
" Calling AE: " << opts.calling_ae <<
"\n";
611 std::cout <<
" Called AE: " << opts.called_ae <<
"\n";
612 std::cout <<
" Query Model: " << opts.query_model <<
" root\n";
613 std::cout <<
" Level: " << to_string(opts.level) <<
"\n";
614 std::cout <<
" Output: " << opts.output_dir <<
"\n\n";
618 std::filesystem::create_directories(opts.output_dir);
630 std::string(sop_class_uid),
632 "1.2.840.10008.1.2.1",
640 static const std::vector<std::string_view> storage_sop_classes = {
641 "1.2.840.10008.5.1.4.1.1.2",
642 "1.2.840.10008.5.1.4.1.1.4",
643 "1.2.840.10008.5.1.4.1.1.7",
644 "1.2.840.10008.5.1.4.1.1.1",
645 "1.2.840.10008.5.1.4.1.1.1.1",
646 "1.2.840.10008.5.1.4.1.1.12.1",
647 "1.2.840.10008.5.1.4.1.1.6.1",
648 "1.2.840.10008.5.1.4.1.1.88.11",
649 "1.2.840.10008.5.1.4.1.1.88.22",
652 uint8_t context_id = 3;
653 for (
auto sop_class : storage_sop_classes) {
656 std::string(sop_class),
658 "1.2.840.10008.1.2.1",
666 auto start_time = std::chrono::steady_clock::now();
667 auto connect_result = association::connect(opts.host, opts.port, config, default_timeout);
669 if (connect_result.is_err()) {
670 std::cerr <<
"Failed to establish association: "
671 << connect_result.error().message <<
"\n";
675 auto& assoc = connect_result.value();
678 auto connect_time = std::chrono::steady_clock::now();
679 auto connect_duration = std::chrono::duration_cast<std::chrono::milliseconds>(
680 connect_time - start_time);
681 std::cout <<
"Association established in " << connect_duration.count() <<
" ms\n";
685 if (!assoc.has_accepted_context(sop_class_uid)) {
686 std::cerr <<
"Error: C-GET SOP Class not accepted by remote SCP\n";
691 auto context_id_opt = assoc.accepted_context_id(sop_class_uid);
692 if (!context_id_opt) {
693 std::cerr <<
"Error: Could not get presentation context ID\n";
697 uint8_t get_context_id = *context_id_opt;
700 auto query_ds = build_query_dataset(opts);
703 auto get_rq = make_c_get_rq(1, sop_class_uid);
704 get_rq.set_dataset(std::move(query_ds));
707 std::cout <<
"Sending C-GET request...\n";
711 auto send_result = assoc.send_dimse(get_context_id, get_rq);
712 if (send_result.is_err()) {
713 std::cerr <<
"Failed to send C-GET: " << send_result.error().message <<
"\n";
719 progress_state progress;
723 bool retrieve_complete =
false;
724 uint16_t total_completed = 0;
725 uint16_t total_failed = 0;
726 uint16_t total_warning = 0;
728 while (!retrieve_complete) {
729 auto recv_result = assoc.receive_dimse(default_timeout);
730 if (recv_result.is_err()) {
731 std::cerr <<
"\nFailed to receive response: "
732 << recv_result.error().message <<
"\n";
737 auto& [recv_context_id, msg] = recv_result.value();
738 auto cmd = msg.command();
740 if (cmd == command_field::c_get_rsp) {
742 auto status = msg.status();
745 if (
auto remaining = msg.remaining_subops()) {
746 progress.remaining = *remaining;
748 if (
auto completed = msg.completed_subops()) {
752 if (
auto failed = msg.failed_subops()) {
753 progress.failed = *
failed;
756 if (
auto warning = msg.warning_subops()) {
761 if (opts.show_progress) {
762 display_progress(progress, opts.verbose);
766 if (status == status_success ||
767 status == status_cancel ||
768 (status & 0xF000) == 0xA000 ||
769 (status & 0xF000) == 0xC000) {
771 retrieve_complete =
true;
773 if (status != status_success && status != status_cancel) {
774 std::cerr <<
"\nC-GET failed with status: 0x"
775 << std::hex <<
status << std::dec <<
"\n";
779 }
else if (cmd == command_field::c_store_rq) {
781 if (msg.has_dataset()) {
782 auto dataset_result = msg.dataset();
783 if (dataset_result.is_err()) {
785 std::cerr <<
"\nWarning: Failed to get dataset\n";
789 const auto& dataset = dataset_result.value().
get();
792 auto file_path = generate_file_path(opts, dataset);
795 bool saved = save_dicom_file(file_path, dataset, opts.overwrite);
798 progress.bytes_received += 1024;
801 auto sop_class = msg.affected_sop_class_uid();
802 auto sop_instance = msg.affected_sop_instance_uid();
804 auto store_rsp = make_c_store_rsp(
808 saved ? status_success : 0xA700
811 auto send_rsp_result = assoc.send_dimse(recv_context_id, store_rsp);
812 if (send_rsp_result.is_err() && opts.verbose) {
813 std::cerr <<
"\nWarning: Failed to send C-STORE response\n";
816 if (opts.verbose && !saved) {
817 std::cerr <<
"\nWarning: Failed to save " << file_path <<
"\n";
824 if (opts.show_progress) {
830 std::cout <<
"Releasing association...\n";
833 auto release_result = assoc.release(default_timeout);
834 if (release_result.is_err() && opts.verbose) {
835 std::cerr <<
"Warning: Release failed: " << release_result.error().message <<
"\n";
839 auto end_time = std::chrono::steady_clock::now();
840 auto total_duration = std::chrono::duration_cast<std::chrono::milliseconds>(
841 end_time - start_time);
843 std::cout <<
"\n========================================\n";
844 std::cout <<
" Retrieve Summary\n";
845 std::cout <<
"========================================\n";
846 std::cout <<
" Mode: C-GET\n";
847 std::cout <<
" Level: " << to_string(opts.level) <<
"\n";
848 std::cout <<
" Output: " << opts.output_dir <<
"\n";
849 std::cout <<
" ----------------------------------------\n";
850 std::cout <<
" Completed: " << total_completed <<
"\n";
851 if (total_warning > 0) {
852 std::cout <<
" Warnings: " << total_warning <<
"\n";
854 if (total_failed > 0) {
855 std::cout <<
" Failed: " << total_failed <<
"\n";
857 std::cout <<
" Total time: " << total_duration.count() <<
" ms\n";
858 std::cout <<
"========================================\n";
861 if (total_failed > 0 && total_completed == 0) {
863 }
else if (total_failed > 0) {
872int perform_c_move(
const options& opts) {
879 std::cout <<
"Performing C-MOVE retrieval\n";
880 std::cout <<
" Host: " << opts.host <<
":" << opts.port <<
"\n";
881 std::cout <<
" Calling AE: " << opts.calling_ae <<
"\n";
882 std::cout <<
" Called AE: " << opts.called_ae <<
"\n";
883 std::cout <<
" Destination: " << opts.move_destination <<
"\n";
884 std::cout <<
" Query Model: " << opts.query_model <<
" root\n";
885 std::cout <<
" Level: " << to_string(opts.level) <<
"\n\n";
898 std::string(sop_class_uid),
900 "1.2.840.10008.1.2.1",
906 auto start_time = std::chrono::steady_clock::now();
907 auto connect_result = association::connect(opts.host, opts.port, config, default_timeout);
909 if (connect_result.is_err()) {
910 std::cerr <<
"Failed to establish association: "
911 << connect_result.error().message <<
"\n";
915 auto& assoc = connect_result.value();
918 auto connect_time = std::chrono::steady_clock::now();
919 auto connect_duration = std::chrono::duration_cast<std::chrono::milliseconds>(
920 connect_time - start_time);
921 std::cout <<
"Association established in " << connect_duration.count() <<
" ms\n";
925 if (!assoc.has_accepted_context(sop_class_uid)) {
926 std::cerr <<
"Error: C-MOVE SOP Class not accepted by remote SCP\n";
931 auto context_id_opt = assoc.accepted_context_id(sop_class_uid);
932 if (!context_id_opt) {
933 std::cerr <<
"Error: Could not get presentation context ID\n";
937 uint8_t move_context_id = *context_id_opt;
940 auto query_ds = build_query_dataset(opts);
943 auto move_rq = make_c_move_rq(1, sop_class_uid, opts.move_destination);
944 move_rq.set_dataset(std::move(query_ds));
947 std::cout <<
"Sending C-MOVE request to move images to " << opts.move_destination <<
"...\n";
951 auto send_result = assoc.send_dimse(move_context_id, move_rq);
952 if (send_result.is_err()) {
953 std::cerr <<
"Failed to send C-MOVE: " << send_result.error().message <<
"\n";
959 progress_state progress;
963 bool move_complete =
false;
964 uint16_t total_completed = 0;
965 uint16_t total_failed = 0;
966 uint16_t total_warning = 0;
968 while (!move_complete) {
969 auto recv_result = assoc.receive_dimse(default_timeout);
970 if (recv_result.is_err()) {
971 std::cerr <<
"\nFailed to receive C-MOVE response: "
972 << recv_result.error().message <<
"\n";
977 auto& [recv_context_id, msg] = recv_result.value();
979 if (msg.command() != command_field::c_move_rsp) {
980 std::cerr <<
"\nError: Unexpected response (expected C-MOVE-RSP)\n";
985 auto status = msg.status();
988 if (
auto remaining = msg.remaining_subops()) {
989 progress.remaining = *remaining;
991 if (
auto completed = msg.completed_subops()) {
995 if (
auto failed = msg.failed_subops()) {
996 progress.failed = *
failed;
999 if (
auto warning = msg.warning_subops()) {
1004 if (opts.show_progress) {
1005 display_progress(progress, opts.verbose);
1009 if (status == status_success ||
1010 status == status_cancel ||
1011 (status & 0xF000) == 0xA000 ||
1012 (status & 0xF000) == 0xC000) {
1014 move_complete =
true;
1016 if (status != status_success && status != status_cancel) {
1017 std::cerr <<
"\nC-MOVE failed with status: 0x"
1018 << std::hex <<
status << std::dec <<
"\n";
1024 if (opts.show_progress) {
1030 std::cout <<
"Releasing association...\n";
1033 auto release_result = assoc.release(default_timeout);
1034 if (release_result.is_err() && opts.verbose) {
1035 std::cerr <<
"Warning: Release failed: " << release_result.error().message <<
"\n";
1039 auto end_time = std::chrono::steady_clock::now();
1040 auto total_duration = std::chrono::duration_cast<std::chrono::milliseconds>(
1041 end_time - start_time);
1043 std::cout <<
"\n========================================\n";
1044 std::cout <<
" Retrieve Summary\n";
1045 std::cout <<
"========================================\n";
1046 std::cout <<
" Mode: C-MOVE\n";
1047 std::cout <<
" Destination: " << opts.move_destination <<
"\n";
1048 std::cout <<
" Level: " << to_string(opts.level) <<
"\n";
1049 std::cout <<
" ----------------------------------------\n";
1050 std::cout <<
" Completed: " << total_completed <<
"\n";
1051 if (total_warning > 0) {
1052 std::cout <<
" Warnings: " << total_warning <<
"\n";
1054 if (total_failed > 0) {
1055 std::cout <<
" Failed: " << total_failed <<
"\n";
1057 std::cout <<
" Total time: " << total_duration.count() <<
" ms\n";
1058 std::cout <<
"========================================\n";
1061 if (total_failed > 0 && total_completed == 0) {
1063 }
else if (total_failed > 0) {
1073 ____ _____ _____ ____ ___ _______ _______ ____ ____ _ _
1074 | _ \| ____|_ _| _ \|_ _| ____\ \ / / ____| / ___| / ___| | | |
1075 | |_) | _| | | | |_) || || _| \ \ / /| _| \___ \| | | | | |
1076 | _ <| |___ | | | _ < | || |___ \ V / | |___ ___) | |___| |_| |
1077 |_| \_\_____| |_| |_| \_\___|_____| \_/ |_____| |____/ \____|\___/
1079 DICOM C-MOVE/C-GET Client
1084 if (!parse_arguments(argc, argv, opts)) {
1085 print_usage(argv[0]);
1089 if (!validate_options(opts)) {
1094 if (opts.mode == retrieve_mode::c_move) {
1095 return perform_c_move(opts);
1097 return perform_c_get(opts);
DICOM Association management per PS3.8.
auto get(dicom_tag tag) noexcept -> dicom_element *
Get a pointer to the element with the given tag.
auto get_string(dicom_tag tag, std::string_view default_value="") const -> std::string
Get the string value of an element.
static auto create(dicom_dataset dataset, const encoding::transfer_syntax &ts) -> dicom_file
Create a new DICOM file from a dataset.
static const transfer_syntax explicit_vr_little_endian
Explicit VR Little Endian (1.2.840.10008.1.2.1)
DICOM Part 10 file handling for reading/writing DICOM files.
Compile-time constants for commonly used DICOM tags.
DIMSE message encoding and decoding.
@ failed
Job failed with error.
@ completed
Job completed successfully.
@ LO
Long String (64 chars max)
@ UI
Unique Identifier (64 chars max)
@ CS
Code String (16 chars max, uppercase + digits + space + underscore)
@ AE
Application Entity (16 chars max)
std::chrono::milliseconds default_timeout()
Default timeout for test operations (5s normal, 30s CI)
@ done
Job completed successfully.
constexpr std::string_view study_root_move_sop_class_uid
Study Root Query/Retrieve Information Model - MOVE.
constexpr std::string_view study_root_get_sop_class_uid
Study Root Query/Retrieve Information Model - GET.
constexpr std::string_view patient_root_move_sop_class_uid
Patient Root Query/Retrieve Information Model - MOVE.
constexpr std::string_view patient_root_get_sop_class_uid
Patient Root Query/Retrieve Information Model - GET.
@ flat
{SOPUID}.dcm (flat structure)
DICOM Retrieve SCP service (C-MOVE/C-GET handler)
DICOM Storage SCP service (C-STORE handler)
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