50constexpr const char* version_string =
"1.0.0";
53constexpr const char* default_calling_ae =
"STORESCU";
56constexpr const char* default_called_ae =
"ANY-SCP";
59constexpr auto default_connection_timeout = std::chrono::seconds{30};
62constexpr auto default_acse_timeout = std::chrono::seconds{30};
65constexpr auto default_dimse_timeout = std::chrono::seconds{0};
68constexpr size_t max_ae_title_length = 16;
71constexpr size_t default_max_pdu_size = 16384;
79constexpr const char* implicit_vr_le =
"1.2.840.10008.1.2";
80constexpr const char* explicit_vr_le =
"1.2.840.10008.1.2.1";
81constexpr const char* explicit_vr_be =
"1.2.840.10008.1.2.2";
82constexpr const char* jpeg_baseline =
"1.2.840.10008.1.2.4.50";
83constexpr const char* jpeg_extended =
"1.2.840.10008.1.2.4.51";
84constexpr const char* jpeg_lossless =
"1.2.840.10008.1.2.4.70";
85constexpr const char* jpeg2000_lossless =
"1.2.840.10008.1.2.4.90";
86constexpr const char* jpeg2000_lossy =
"1.2.840.10008.1.2.4.91";
87constexpr const char* rle =
"1.2.840.10008.1.2.5";
97enum class verbosity_level {
107enum class transfer_syntax_mode {
123 std::string peer_host;
124 uint16_t peer_port{0};
125 std::string calling_ae_title{default_calling_ae};
126 std::string called_ae_title{default_called_ae};
130 std::chrono::seconds acse_timeout{default_acse_timeout};
131 std::chrono::seconds dimse_timeout{default_dimse_timeout};
134 std::vector<std::filesystem::path> input_paths;
135 bool recursive{
false};
136 std::string scan_pattern{
"*.dcm"};
139 transfer_syntax_mode ts_mode{transfer_syntax_mode::propose_all};
142 bool continue_on_error{
true};
143 size_t max_pdu_size{default_max_pdu_size};
146 bool show_progress{
false};
147 std::string report_file;
150 verbosity_level verbosity{verbosity_level::normal};
154 std::string tls_cert_file;
155 std::string tls_key_file;
156 std::string tls_ca_file;
159 bool show_help{
false};
160 bool show_version{
false};
166struct file_store_result {
167 std::filesystem::path file_path;
171 uint16_t status_code{0};
172 std::string error_message;
174 std::chrono::milliseconds transfer_time{0};
180struct store_statistics {
181 size_t total_files{0};
182 size_t successful{0};
185 size_t total_bytes{0};
186 std::chrono::milliseconds total_time{0};
187 std::chrono::milliseconds association_time{0};
189 [[nodiscard]]
double success_rate()
const {
190 return total_files > 0
191 ? (
static_cast<double>(successful) / total_files) * 100.0
195 [[nodiscard]]
double throughput_mbps()
const {
196 if (total_time.count() == 0)
return 0.0;
197 double bytes_per_sec =
198 static_cast<double>(total_bytes) / (total_time.count() / 1000.0);
199 return bytes_per_sec / (1024.0 * 1024.0);
212 ____ _____ ___ ____ _____ ____ ____ _ _
213 / ___|_ _/ _ \| _ \| ____| / ___| / ___| | | |
214 \___ \ | || | | | |_) | _| \___ \| | | | | |
215 ___) || || |_| | _ <| |___ ___) | |___| |_| |
216 |____/ |_| \___/|_| \_\_____| |____/ \____|\___/
218 DICOM Image Sender v)" << version_string
227void print_usage(
const char* program_name) {
228 std::cout <<
"Usage: " << program_name
229 << R
"( [options] <peer> <port> <dcmfile-in> [dcmfile-in...]
232 peer Remote host address (IP or hostname)
233 port Remote port number (typically 104 or 11112)
234 dcmfile-in DICOM file(s) or directory to send
237 -h, --help Show this help message and exit
238 -v, --verbose Verbose output mode
239 -d, --debug Debug output mode (more details than verbose)
240 -q, --quiet Quiet mode (minimal output)
241 --version Show version information
244 -aet, --aetitle <aetitle> Calling AE Title (default: STORESCU)
245 -aec, --call <aetitle> Called AE Title (default: ANY-SCP)
246 -to, --timeout <seconds> Connection timeout (default: 30)
247 -ta, --acse-timeout <seconds> ACSE timeout (default: 30)
248 -td, --dimse-timeout <seconds> DIMSE timeout (default: 0=infinite)
251 -r, --recursive Recursively process directories
252 -xs, --prefer-lossless Prefer lossless transfer syntaxes
253 -xv, --propose-implicit Propose only Implicit VR Little Endian
254 -xe, --propose-explicit Propose only Explicit VR Little Endian
255 +xa, --propose-all Propose all transfer syntaxes (default)
258 --scan-pattern <pattern> File pattern for directory scan (default: *.dcm)
259 --continue-on-error Continue after failures (default)
260 --stop-on-error Stop on first error
261 --max-pdu <size> Maximum PDU size (default: 16384)
264 -p, --progress Show progress bar
265 --report-file <file> Write transfer report to file
267TLS Options (not yet implemented):
268 --tls Enable TLS connection
269 --tls-cert <file> TLS certificate file
270 --tls-key <file> TLS private key file
271 --tls-ca <file> TLS CA certificate file
275 )" << program_name << R"( localhost 11112 image.dcm
277 # Send with custom AE Titles
278 )" << program_name << R"( -aet MYSCU -aec PACS localhost 11112 image.dcm
280 # Send directory recursively with progress
281 )" << program_name << R"( -r --progress localhost 11112 ./patient_data/
283 # Send with report file
284 )" << program_name << R"( --report-file transfer.log localhost 11112 *.dcm
286 # Prefer lossless transfer syntax
287 )" << program_name << R"( --prefer-lossless localhost 11112 *.dcm
290 0 Success - All files sent successfully
291 1 Error - One or more files failed to send
292 2 Error - Invalid arguments or connection failure
299void print_version() {
300 std::cout <<
"store_scu version " << version_string <<
"\n";
301 std::cout <<
"PACS System DICOM Utilities\n";
302 std::cout <<
"Copyright (c) 2024\n";
312bool parse_timeout(
const std::string& value, std::chrono::seconds& result,
313 const std::string& option_name) {
315 int seconds = std::stoi(value);
317 std::cerr <<
"Error: " << option_name <<
" must be non-negative\n";
320 result = std::chrono::seconds{seconds};
322 }
catch (
const std::exception&) {
323 std::cerr <<
"Error: Invalid value for " << option_name <<
": '"
332bool parse_size(
const std::string& value,
size_t& result,
333 const std::string& option_name,
size_t min_value = 0) {
335 long long val = std::stoll(value);
336 if (val < 0 ||
static_cast<size_t>(val) < min_value) {
337 std::cerr <<
"Error: " << option_name <<
" must be at least "
338 << min_value <<
"\n";
341 result =
static_cast<size_t>(val);
343 }
catch (
const std::exception&) {
344 std::cerr <<
"Error: Invalid value for " << option_name <<
": '"
353bool validate_ae_title(
const std::string& ae_title,
354 const std::string& option_name) {
355 if (ae_title.empty()) {
356 std::cerr <<
"Error: " << option_name <<
" cannot be empty\n";
359 if (ae_title.length() > max_ae_title_length) {
360 std::cerr <<
"Error: " << option_name <<
" exceeds "
361 << max_ae_title_length <<
" characters\n";
370bool parse_arguments(
int argc,
char* argv[], options& opts) {
371 std::vector<std::string> positional_args;
373 for (
int i = 1; i < argc; ++i) {
374 std::string arg = argv[i];
377 if (arg ==
"-h" || arg ==
"--help") {
378 opts.show_help =
true;
381 if (arg ==
"--version") {
382 opts.show_version =
true;
387 if (arg ==
"-v" || arg ==
"--verbose") {
388 opts.verbosity = verbosity_level::verbose;
391 if (arg ==
"-d" || arg ==
"--debug") {
392 opts.verbosity = verbosity_level::debug;
395 if (arg ==
"-q" || arg ==
"--quiet") {
396 opts.verbosity = verbosity_level::quiet;
401 if ((arg ==
"-aet" || arg ==
"--aetitle") && i + 1 < argc) {
402 opts.calling_ae_title = argv[++i];
403 if (!validate_ae_title(opts.calling_ae_title,
"Calling AE Title")) {
408 if ((arg ==
"-aec" || arg ==
"--call") && i + 1 < argc) {
409 opts.called_ae_title = argv[++i];
410 if (!validate_ae_title(opts.called_ae_title,
"Called AE Title")) {
417 if ((arg ==
"-to" || arg ==
"--timeout") && i + 1 < argc) {
418 if (!parse_timeout(argv[++i], opts.connection_timeout,
419 "Connection timeout")) {
424 if ((arg ==
"-ta" || arg ==
"--acse-timeout") && i + 1 < argc) {
425 if (!parse_timeout(argv[++i], opts.acse_timeout,
"ACSE timeout")) {
430 if ((arg ==
"-td" || arg ==
"--dimse-timeout") && i + 1 < argc) {
431 if (!parse_timeout(argv[++i], opts.dimse_timeout,
"DIMSE timeout")) {
438 if (arg ==
"-r" || arg ==
"--recursive") {
439 opts.recursive =
true;
442 if (arg ==
"-xs" || arg ==
"--prefer-lossless") {
443 opts.ts_mode = transfer_syntax_mode::prefer_lossless;
446 if (arg ==
"-xv" || arg ==
"--propose-implicit") {
447 opts.ts_mode = transfer_syntax_mode::propose_implicit;
450 if (arg ==
"-xe" || arg ==
"--propose-explicit") {
451 opts.ts_mode = transfer_syntax_mode::propose_explicit;
454 if (arg ==
"+xa" || arg ==
"--propose-all") {
455 opts.ts_mode = transfer_syntax_mode::propose_all;
460 if (arg ==
"--scan-pattern" && i + 1 < argc) {
461 opts.scan_pattern = argv[++i];
464 if (arg ==
"--continue-on-error") {
465 opts.continue_on_error =
true;
468 if (arg ==
"--stop-on-error") {
469 opts.continue_on_error =
false;
472 if (arg ==
"--max-pdu" && i + 1 < argc) {
473 if (!parse_size(argv[++i], opts.max_pdu_size,
"Max PDU size", 4096)) {
480 if (arg ==
"-p" || arg ==
"--progress") {
481 opts.show_progress =
true;
484 if (arg ==
"--report-file" && i + 1 < argc) {
485 opts.report_file = argv[++i];
490 if (arg ==
"--tls") {
494 if (arg ==
"--tls-cert" && i + 1 < argc) {
495 opts.tls_cert_file = argv[++i];
498 if (arg ==
"--tls-key" && i + 1 < argc) {
499 opts.tls_key_file = argv[++i];
502 if (arg ==
"--tls-ca" && i + 1 < argc) {
503 opts.tls_ca_file = argv[++i];
508 if (arg.starts_with(
"-") && arg !=
"-") {
509 std::cerr <<
"Error: Unknown option '" << arg <<
"'\n";
514 positional_args.push_back(arg);
518 if (positional_args.size() < 3) {
520 <<
"Error: Expected <peer> <port> <dcmfile-in> [dcmfile-in...]\n";
524 opts.peer_host = positional_args[0];
528 int port_int = std::stoi(positional_args[1]);
529 if (port_int < 1 || port_int > 65535) {
530 std::cerr <<
"Error: Port must be between 1 and 65535\n";
533 opts.peer_port =
static_cast<uint16_t
>(port_int);
534 }
catch (
const std::exception&) {
535 std::cerr <<
"Error: Invalid port number '" << positional_args[1]
541 for (
size_t i = 2; i < positional_args.size(); ++i) {
542 opts.input_paths.emplace_back(positional_args[i]);
547 std::cerr <<
"Warning: TLS support is not yet implemented\n";
560bool is_dicom_file_candidate(
const std::filesystem::path& path) {
561 auto ext = path.extension().string();
562 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
563 return ext ==
".dcm" || ext ==
".dicom" || ext.empty();
569std::vector<std::filesystem::path> collect_files(
570 const std::vector<std::filesystem::path>& input_paths,
bool recursive) {
571 std::vector<std::filesystem::path> files;
573 for (
const auto& path : input_paths) {
574 if (!std::filesystem::exists(path)) {
575 std::cerr <<
"Warning: Path does not exist: " << path.string()
580 if (std::filesystem::is_regular_file(path)) {
581 files.push_back(path);
582 }
else if (std::filesystem::is_directory(path)) {
584 for (
const auto& entry :
585 std::filesystem::recursive_directory_iterator(path)) {
586 if (entry.is_regular_file() &&
587 is_dicom_file_candidate(entry.path())) {
588 files.push_back(entry.path());
592 for (
const auto& entry :
593 std::filesystem::directory_iterator(path)) {
594 if (entry.is_regular_file() &&
595 is_dicom_file_candidate(entry.path())) {
596 files.push_back(entry.path());
613void show_progress_bar(
size_t current,
size_t total,
int width = 40) {
614 if (total == 0)
return;
616 float progress =
static_cast<float>(current) /
static_cast<float>(total);
617 int filled =
static_cast<int>(progress * width);
620 for (
int i = 0; i < width; ++i) {
623 }
else if (i == filled) {
629 std::cout <<
"] " << std::setw(3) <<
static_cast<int>(progress * 100)
631 <<
"(" << current <<
"/" << total <<
")" << std::flush;
637std::string format_size(
size_t bytes) {
638 constexpr size_t KB = 1024;
639 constexpr size_t MB = KB * 1024;
640 constexpr size_t GB = MB * 1024;
642 std::ostringstream oss;
643 oss << std::fixed << std::setprecision(2);
646 oss << static_cast<double>(bytes) / GB <<
" GB";
647 }
else if (bytes >= MB) {
648 oss << static_cast<double>(bytes) / MB <<
" MB";
649 }
else if (bytes >= KB) {
650 oss << static_cast<double>(bytes) / KB <<
" KB";
652 oss << bytes <<
" B";
661std::string format_duration(std::chrono::milliseconds duration) {
662 auto ms = duration.count();
665 return std::to_string(ms) +
" ms";
668 auto seconds = ms / 1000;
669 auto minutes = seconds / 60;
672 std::ostringstream oss;
674 oss << minutes <<
"m ";
676 oss << seconds <<
"s";
687std::vector<std::string> get_transfer_syntaxes(transfer_syntax_mode mode) {
689 case transfer_syntax_mode::propose_implicit:
690 return {ts::implicit_vr_le};
691 case transfer_syntax_mode::propose_explicit:
692 return {ts::explicit_vr_le};
693 case transfer_syntax_mode::prefer_lossless:
694 return {ts::jpeg_lossless, ts::jpeg2000_lossless, ts::rle,
695 ts::explicit_vr_le, ts::implicit_vr_le};
696 case transfer_syntax_mode::propose_all:
698 return {ts::explicit_vr_le, ts::implicit_vr_le,
699 ts::explicit_vr_be, ts::jpeg_baseline,
700 ts::jpeg_extended, ts::jpeg_lossless,
701 ts::jpeg2000_lossless, ts::jpeg2000_lossy,
713void generate_report(
const std::string& report_file,
714 const std::vector<file_store_result>& results,
715 const store_statistics& stats,
const options& opts) {
716 std::ofstream ofs(report_file);
717 if (!ofs.is_open()) {
718 std::cerr <<
"Warning: Could not open report file: " << report_file
723 auto now = std::chrono::system_clock::now();
724 auto time = std::chrono::system_clock::to_time_t(now);
726 ofs <<
"========================================\n";
727 ofs <<
" DICOM Store SCU Transfer Report\n";
728 ofs <<
"========================================\n";
729 ofs <<
"Generated: " << std::ctime(&time);
732 ofs <<
"Connection Info:\n";
733 ofs <<
" Peer: " << opts.peer_host <<
":" << opts.peer_port
735 ofs <<
" Calling AE: " << opts.calling_ae_title <<
"\n";
736 ofs <<
" Called AE: " << opts.called_ae_title <<
"\n";
740 ofs <<
" Total Files: " << stats.total_files <<
"\n";
741 ofs <<
" Successful: " << stats.successful <<
"\n";
742 ofs <<
" Warnings: " << stats.warnings <<
"\n";
743 ofs <<
" Failed: " << stats.failed <<
"\n";
744 ofs <<
" Data Sent: " << format_size(stats.total_bytes) <<
"\n";
745 ofs <<
" Duration: " << format_duration(stats.total_time) <<
"\n";
746 ofs << std::fixed << std::setprecision(2);
747 ofs <<
" Throughput: " << stats.throughput_mbps() <<
" MB/s\n";
748 ofs <<
" Success Rate: " << stats.success_rate() <<
"%\n";
751 if (stats.failed > 0) {
752 ofs <<
"Failed Transfers:\n";
753 ofs <<
"----------------------------------------\n";
754 for (
const auto& result : results) {
755 if (!result.success) {
756 ofs <<
" File: " << result.file_path.filename().string()
758 ofs <<
" Error: " << result.error_message <<
"\n";
759 ofs <<
" Status: 0x" << std::hex << result.status_code
766 ofs <<
"All Transfers:\n";
767 ofs <<
"----------------------------------------\n";
768 for (
const auto& result : results) {
769 ofs << (result.success ?
"[OK] " :
"[FAIL]") <<
" ";
770 ofs << result.file_path.filename().string();
771 if (result.success) {
772 ofs <<
" (" << format_size(result.file_size) <<
", "
773 << result.transfer_time.count() <<
"ms)";
775 ofs <<
" - " << result.error_message;
790std::vector<std::pair<std::filesystem::path, std::string>> analyze_files(
791 const std::vector<std::filesystem::path>& files,
bool verbose) {
794 std::vector<std::pair<std::filesystem::path, std::string>> valid_files;
796 for (
const auto& file_path : files) {
797 auto result = dicom_file::open(file_path);
798 if (result.is_ok()) {
799 auto sop_class = result.value().sop_class_uid();
800 if (!sop_class.empty()) {
801 valid_files.emplace_back(file_path, sop_class);
802 }
else if (verbose) {
803 std::cerr <<
"Warning: No SOP Class UID in file: "
804 << file_path.string() <<
"\n";
806 }
else if (verbose) {
807 std::cerr <<
"Warning: Skipping invalid file: "
808 << file_path.string() <<
"\n";
818int perform_store(
const options& opts) {
823 bool is_quiet = opts.verbosity == verbosity_level::quiet;
824 bool is_verbose = opts.verbosity == verbosity_level::verbose ||
825 opts.verbosity == verbosity_level::debug;
827 store_statistics stats;
828 std::vector<file_store_result> results;
829 auto start_time = std::chrono::steady_clock::now();
833 std::cout <<
"Scanning for DICOM files...\n";
835 auto files = collect_files(opts.input_paths, opts.recursive);
838 std::cerr <<
"Error: No DICOM files found\n";
843 std::cout <<
"Found " << files.size() <<
" file(s) to analyze\n";
848 std::cout <<
"Analyzing files...\n";
850 auto valid_files = analyze_files(files, is_verbose);
852 if (valid_files.empty()) {
853 std::cerr <<
"Error: No valid DICOM files found\n";
858 std::vector<std::string> sop_classes;
859 for (
const auto& [path, sop_class] : valid_files) {
860 if (std::find(sop_classes.begin(), sop_classes.end(), sop_class) ==
862 sop_classes.push_back(sop_class);
867 std::cout <<
"Valid DICOM files: " << valid_files.size() <<
"\n";
868 std::cout <<
"SOP Classes found: " << sop_classes.size() <<
"\n\n";
871 stats.total_files = valid_files.size();
875 std::cout <<
"Connecting to " << opts.peer_host <<
":"
876 << opts.peer_port <<
"\n";
877 std::cout <<
" Calling AE Title: " << opts.calling_ae_title <<
"\n";
878 std::cout <<
" Called AE Title: " << opts.called_ae_title <<
"\n";
881 std::cout <<
" Connection Timeout: "
882 << opts.connection_timeout.count() <<
"s\n";
883 std::cout <<
" Max PDU Size: " << opts.max_pdu_size <<
"\n";
896 auto transfer_syntaxes = get_transfer_syntaxes(opts.ts_mode);
897 uint8_t context_id = 1;
899 for (
const auto& sop_class : sop_classes) {
901 {context_id, sop_class, transfer_syntaxes});
906 auto timeout = std::chrono::duration_cast<std::chrono::milliseconds>(
907 opts.connection_timeout);
908 auto connect_result =
909 association::connect(opts.peer_host, opts.peer_port, config, timeout);
911 if (connect_result.is_err()) {
912 std::cerr <<
"Error: Failed to establish association: "
913 << connect_result.error().message <<
"\n";
917 auto& assoc = connect_result.value();
918 auto connect_time = std::chrono::steady_clock::now();
919 stats.association_time =
920 std::chrono::duration_cast<std::chrono::milliseconds>(connect_time -
924 std::cout <<
"Association established in "
925 << stats.association_time.count() <<
" ms\n\n";
932 std::chrono::duration_cast<std::chrono::milliseconds>(
933 opts.dimse_timeout.count() > 0 ? opts.dimse_timeout
934 : std::chrono::seconds{30});
936 storage_scu scu{scu_config};
940 std::cout <<
"Sending files...\n";
943 for (
size_t i = 0; i < valid_files.size(); ++i) {
944 const auto& [file_path, sop_class] = valid_files[i];
946 file_store_result file_result;
947 file_result.file_path = file_path;
948 file_result.sop_class_uid = sop_class;
952 file_result.file_size = std::filesystem::file_size(file_path);
954 file_result.file_size = 0;
957 if (opts.show_progress && !is_quiet) {
958 show_progress_bar(i + 1, valid_files.size());
961 auto file_start = std::chrono::steady_clock::now();
962 auto result = scu.store_file(assoc, file_path);
963 auto file_end = std::chrono::steady_clock::now();
965 file_result.transfer_time =
966 std::chrono::duration_cast<std::chrono::milliseconds>(file_end -
969 if (result.is_ok()) {
975 file_result.success =
true;
977 stats.total_bytes += file_result.file_size;
979 if (is_verbose && !opts.show_progress) {
980 std::cout <<
" [OK] " << file_path.filename().string()
981 <<
" (" << format_size(file_result.file_size)
985 file_result.success =
true;
988 stats.total_bytes += file_result.file_size;
990 if (is_verbose && !opts.show_progress) {
991 std::cout <<
" [WARN] " << file_path.filename().string()
992 <<
" (Status: 0x" << std::hex
996 file_result.success =
false;
1000 if (is_verbose && !opts.show_progress) {
1001 std::cout <<
" [FAIL] " << file_path.filename().string()
1005 if (!opts.continue_on_error) {
1006 results.push_back(file_result);
1011 file_result.success =
false;
1012 file_result.error_message = result.error().message;
1015 if (is_verbose && !opts.show_progress) {
1016 std::cout <<
" [FAIL] " << file_path.filename().string()
1017 <<
" - " << result.error().message <<
"\n";
1020 if (!opts.continue_on_error) {
1021 results.push_back(file_result);
1026 results.push_back(file_result);
1029 if (opts.show_progress && !is_quiet) {
1035 std::cout <<
"\nReleasing association...\n";
1037 auto release_result = assoc.release(timeout);
1038 if (release_result.is_err() && is_verbose) {
1039 std::cerr <<
"Warning: Release failed: " << release_result.error().message
1043 auto end_time = std::chrono::steady_clock::now();
1044 stats.total_time = std::chrono::duration_cast<std::chrono::milliseconds>(
1045 end_time - start_time);
1050 std::cout <<
"========================================\n";
1051 std::cout <<
" Summary\n";
1052 std::cout <<
"========================================\n";
1053 std::cout <<
" Files processed: " << stats.total_files <<
"\n";
1054 std::cout <<
" Successful: " << stats.successful <<
"\n";
1055 if (stats.warnings > 0) {
1056 std::cout <<
" Warnings: " << stats.warnings <<
"\n";
1058 std::cout <<
" Failed: " << stats.failed <<
"\n";
1059 std::cout <<
" Data sent: " << format_size(stats.total_bytes)
1061 std::cout <<
" Total time: " << format_duration(stats.total_time)
1063 std::cout << std::fixed << std::setprecision(2);
1064 std::cout <<
" Throughput: " << stats.throughput_mbps()
1067 if (stats.total_files > 0) {
1068 auto avg_time = stats.total_time.count() / stats.total_files;
1069 std::cout <<
" Avg time/file: " << avg_time <<
" ms\n";
1072 std::cout <<
"========================================\n";
1076 if (!opts.report_file.empty()) {
1077 generate_report(opts.report_file, results, stats, opts);
1079 std::cout <<
"Report written to: " << opts.report_file <<
"\n";
1084 if (stats.failed == 0) {
1086 std::cout <<
"Status: SUCCESS\n";
1089 }
else if (stats.successful > 0) {
1091 std::cout <<
"Status: PARTIAL FAILURE\n";
1096 std::cout <<
"Status: FAILURE\n";
1111 if (!parse_arguments(argc, argv, opts)) {
1112 if (!opts.show_help && !opts.show_version) {
1113 std::cerr <<
"\nUse --help for usage information.\n";
1118 if (opts.show_version) {
1123 if (opts.show_help) {
1125 print_usage(argv[0]);
1130 if (opts.verbosity != verbosity_level::quiet) {
1134 return perform_store(opts);
DICOM Association management per PS3.8.
DICOM Part 10 file handling for reading/writing DICOM files.
Compile-time constants for commonly used DICOM tags.
@ failed
Job failed with error.
constexpr int connection_timeout
constexpr int timeout
Lock timeout exceeded.
DICOM Storage SCU service (C-STORE sender)
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
Configuration for Storage SCU service.
std::chrono::milliseconds response_timeout
Timeout for receiving C-STORE response (milliseconds)
bool continue_on_error
Continue batch operation on error (true) or stop on first error (false)
Result of a C-STORE operation.
uint16_t status
DIMSE status code (0x0000 = success)
bool is_warning() const noexcept
Check if this was a warning status.
std::string error_comment
Error comment from the SCP (if any)
std::string sop_instance_uid
SOP Instance UID of the stored instance.
bool is_success() const noexcept
Check if the store operation was successful.