41 std::filesystem::path input_path;
42 std::filesystem::path output_path;
43 std::string target_transfer_syntax;
45 bool recursive{
false};
47 bool overwrite{
false};
50 bool show_progress{
true};
51 bool list_syntaxes{
false};
57struct conversion_stats {
58 size_t total_files{0};
59 size_t success_count{0};
61 size_t error_count{0};
62 std::chrono::milliseconds total_time{0};
69void print_usage(
const char* program_name) {
71DICOM Conversion - Transfer Syntax Conversion Utility
73Usage: )" << program_name
74 << R"( <input> <output> [options]
77 input Input DICOM file or directory
78 output Output DICOM file or directory
80Transfer Syntax Options:
81 --implicit Implicit VR Little Endian (1.2.840.10008.1.2)
82 --explicit Explicit VR Little Endian (1.2.840.10008.1.2.1) [default]
83 --explicit-be Explicit VR Big Endian (1.2.840.10008.1.2.2) [retired]
84 --jpeg-baseline JPEG Baseline (Process 1) - lossy
85 --jpeg-lossless JPEG Lossless, Non-Hierarchical
86 --jpeg2000 JPEG 2000 Image Compression (Lossless Only)
87 --jpeg2000-lossy JPEG 2000 Image Compression
88 --htj2k HTJ2K Lossless Only (1.2.840.10008.1.2.4.201)
89 --htj2k-rpcl HTJ2K with RPCL Options (1.2.840.10008.1.2.4.202)
90 --htj2k-lossy HTJ2K Lossy (1.2.840.10008.1.2.4.203)
92 -t, --transfer-syntax <uid> Specify Transfer Syntax by UID
95 -q, --quality <1-100> JPEG quality (default: 90, higher = better quality)
98 -r, --recursive Process directory recursively
99 --overwrite Overwrite existing output files
100 --verify Verify conversion result
101 -v, --verbose Verbose output
102 -q, --quiet Minimal output (errors only)
103 --no-progress Disable progress display
106 --list-syntaxes List all supported Transfer Syntaxes
107 -h, --help Show this help message
111 << R"( image.dcm converted.dcm --explicit
113 << R"( image.dcm compressed.dcm --jpeg-baseline -q 85
115 << R"( ./input_dir/ ./output_dir/ --recursive --implicit
117 << R"( image.dcm htj2k.dcm --htj2k
119 << R"( image.dcm output.dcm -t 1.2.840.10008.1.2.4.50
122 0 Success - All files converted successfully
123 1 Error - Invalid arguments or help requested
124 2 Error - Conversion failed for one or more files
131void list_supported_syntaxes() {
134 std::cout <<
"\nSupported Transfer Syntaxes:\n";
135 std::cout << std::string(70,
'-') <<
"\n";
136 std::cout << std::left << std::setw(40) <<
"Name"
137 << std::setw(30) <<
"UID" <<
"\n";
138 std::cout << std::string(70,
'-') <<
"\n";
141 for (
const auto&
ts : syntaxes) {
142 std::cout << std::left << std::setw(40) <<
ts.name()
143 << std::setw(30) <<
ts.uid() <<
"\n";
146 std::cout << std::string(70,
'-') <<
"\n";
147 std::cout <<
"Total: " << syntaxes.size() <<
" transfer syntaxes\n\n";
150 auto codec_uids = compression::codec_factory::supported_transfer_syntaxes();
151 if (!codec_uids.empty()) {
152 std::cout <<
"Compression Codecs Available:\n";
153 std::cout << std::string(50,
'-') <<
"\n";
154 for (
const auto&
uid : codec_uids) {
156 std::cout <<
" " <<
ts->name() <<
"\n";
159 std::cout << std::string(50,
'-') <<
"\n";
170bool parse_arguments(
int argc,
char* argv[], options& opts) {
178 opts.target_transfer_syntax = std::string(transfer_syntax::explicit_vr_little_endian.
uid());
180 for (
int i = 1; i < argc; ++i) {
181 std::string arg = argv[i];
183 if (arg ==
"--help" || arg ==
"-h") {
185 }
else if (arg ==
"--list-syntaxes") {
186 opts.list_syntaxes =
true;
188 }
else if (arg ==
"--implicit") {
189 opts.target_transfer_syntax = std::string(transfer_syntax::implicit_vr_little_endian.
uid());
190 }
else if (arg ==
"--explicit") {
191 opts.target_transfer_syntax = std::string(transfer_syntax::explicit_vr_little_endian.
uid());
192 }
else if (arg ==
"--explicit-be") {
193 opts.target_transfer_syntax = std::string(transfer_syntax::explicit_vr_big_endian.
uid());
194 }
else if (arg ==
"--jpeg-baseline") {
195 opts.target_transfer_syntax = std::string(transfer_syntax::jpeg_baseline.
uid());
196 }
else if (arg ==
"--jpeg-lossless") {
197 opts.target_transfer_syntax = std::string(transfer_syntax::jpeg_lossless.
uid());
198 }
else if (arg ==
"--jpeg2000") {
199 opts.target_transfer_syntax = std::string(transfer_syntax::jpeg2000_lossless.
uid());
200 }
else if (arg ==
"--jpeg2000-lossy") {
201 opts.target_transfer_syntax = std::string(transfer_syntax::jpeg2000_lossy.
uid());
202 }
else if (arg ==
"--htj2k") {
203 opts.target_transfer_syntax = std::string(transfer_syntax::htj2k_lossless.
uid());
204 }
else if (arg ==
"--htj2k-rpcl") {
205 opts.target_transfer_syntax = std::string(transfer_syntax::htj2k_rpcl.
uid());
206 }
else if (arg ==
"--htj2k-lossy") {
207 opts.target_transfer_syntax = std::string(transfer_syntax::htj2k_lossy.
uid());
208 }
else if (arg ==
"--rle") {
209 opts.target_transfer_syntax = std::string(transfer_syntax::rle_lossless.
uid());
210 }
else if ((arg ==
"-t" || arg ==
"--transfer-syntax") && i + 1 < argc) {
211 opts.target_transfer_syntax = argv[++i];
212 }
else if ((arg ==
"-q" || arg ==
"--quality") && i + 1 < argc) {
214 opts.quality = std::stoi(argv[++i]);
215 if (opts.quality < 1 || opts.quality > 100) {
216 std::cerr <<
"Error: Quality must be between 1 and 100\n";
220 std::cerr <<
"Error: Invalid quality value\n";
223 }
else if (arg ==
"-r" || arg ==
"--recursive") {
224 opts.recursive =
true;
225 }
else if (arg ==
"--overwrite") {
226 opts.overwrite =
true;
227 }
else if (arg ==
"--verify") {
229 }
else if (arg ==
"-v" || arg ==
"--verbose") {
231 }
else if (arg ==
"--quiet") {
233 opts.show_progress =
false;
234 }
else if (arg ==
"--no-progress") {
235 opts.show_progress =
false;
236 }
else if (arg[0] ==
'-') {
237 std::cerr <<
"Error: Unknown option '" << arg <<
"'\n";
239 }
else if (opts.input_path.empty()) {
240 opts.input_path = arg;
241 }
else if (opts.output_path.empty()) {
242 opts.output_path = arg;
244 std::cerr <<
"Error: Too many arguments\n";
251 if (!
ts.has_value()) {
252 std::cerr <<
"Error: Unknown Transfer Syntax UID '" << opts.target_transfer_syntax <<
"'\n";
253 std::cerr <<
"Use --list-syntaxes to see available options\n";
257 if (!opts.list_syntaxes) {
258 if (opts.input_path.empty()) {
259 std::cerr <<
"Error: No input path specified\n";
263 if (opts.output_path.empty()) {
264 std::cerr <<
"Error: No output path specified\n";
271 opts.verbose =
false;
284bool convert_file(
const std::filesystem::path& input_path,
285 const std::filesystem::path& output_path,
286 const options& opts) {
291 if (std::filesystem::exists(output_path) && !opts.overwrite) {
293 std::cout <<
" Skipped (exists): " << output_path.filename().string() <<
"\n";
299 auto result = dicom_file::open(input_path);
300 if (result.is_err()) {
301 std::cerr <<
"Error: Failed to open '" << input_path.string()
302 <<
"': " << result.error().message <<
"\n";
306 auto& input_file = result.value();
307 auto source_ts = input_file.transfer_syntax();
308 auto target_ts = transfer_syntax(opts.target_transfer_syntax);
311 if (source_ts == target_ts) {
313 std::cout <<
" Skipped (same TS): " << input_path.filename().string() <<
"\n";
316 std::filesystem::copy_file(input_path, output_path,
317 std::filesystem::copy_options::overwrite_existing);
322 if (!target_ts.is_supported()) {
323 std::cerr <<
"Error: Target Transfer Syntax '" << target_ts.name()
324 <<
"' is not currently supported\n";
329 std::cout <<
" Converting: " << input_path.filename().string() <<
"\n";
330 std::cout <<
" From: " << source_ts.name() <<
"\n";
331 std::cout <<
" To: " << target_ts.name() <<
"\n";
335 auto output_file = dicom_file::create(
336 std::move(input_file.dataset()),
341 auto output_dir = output_path.parent_path();
342 if (!output_dir.empty() && !std::filesystem::exists(output_dir)) {
343 std::filesystem::create_directories(output_dir);
347 auto save_result = output_file.save(output_path);
348 if (save_result.is_err()) {
349 std::cerr <<
"Error: Failed to save '" << output_path.string()
350 <<
"': " << save_result.error().message <<
"\n";
356 auto verify_result = dicom_file::open(output_path);
357 if (verify_result.is_err()) {
358 std::cerr <<
"Error: Verification failed for '" << output_path.string()
359 <<
"': " << verify_result.error().message <<
"\n";
363 auto& verified_file = verify_result.value();
364 if (verified_file.transfer_syntax() != target_ts) {
365 std::cerr <<
"Error: Verification failed - Transfer Syntax mismatch\n";
370 std::cout <<
" Verified: OK\n";
384void process_directory(
const std::filesystem::path& input_dir,
385 const std::filesystem::path& output_dir,
387 conversion_stats& stats) {
388 auto process_file = [&](
const std::filesystem::path& file_path) {
390 auto ext = file_path.extension().string();
391 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
392 if (ext !=
".dcm" && ext !=
".dicom" && !ext.empty()) {
399 auto relative_path = std::filesystem::relative(file_path, input_dir);
400 auto output_path = output_dir / relative_path;
402 auto start = std::chrono::steady_clock::now();
404 if (convert_file(file_path, output_path, opts)) {
405 ++stats.success_count;
410 auto end = std::chrono::steady_clock::now();
411 stats.total_time += std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
413 if (opts.show_progress && !opts.quiet) {
414 std::cout <<
"\rProcessed: " << stats.total_files
415 <<
" (Success: " << stats.success_count
416 <<
", Errors: " << stats.error_count <<
")" << std::flush;
420 if (opts.recursive) {
421 for (
const auto& entry : std::filesystem::recursive_directory_iterator(input_dir)) {
422 if (entry.is_regular_file()) {
423 process_file(entry.path());
427 for (
const auto& entry : std::filesystem::directory_iterator(input_dir)) {
428 if (entry.is_regular_file()) {
429 process_file(entry.path());
434 if (opts.show_progress && !opts.quiet) {
443void print_summary(
const conversion_stats& stats) {
445 std::cout <<
"========================================\n";
446 std::cout <<
" Conversion Summary\n";
447 std::cout <<
"========================================\n";
448 std::cout <<
" Total files: " << stats.total_files <<
"\n";
449 std::cout <<
" Successful: " << stats.success_count <<
"\n";
450 std::cout <<
" Skipped: " << stats.skip_count <<
"\n";
451 std::cout <<
" Errors: " << stats.error_count <<
"\n";
452 std::cout <<
" Total time: " << stats.total_time.count() <<
" ms\n";
453 if (stats.total_files > 0) {
454 auto avg_time = stats.total_time.count() /
static_cast<double>(stats.total_files);
455 std::cout <<
" Avg per file: " << std::fixed << std::setprecision(1)
456 << avg_time <<
" ms\n";
458 std::cout <<
"========================================\n";
463int main(
int argc,
char* argv[]) {
466 if (!parse_arguments(argc, argv, opts)) {
468 ____ ____ __ __ ____ ___ _ ___ __
469 | _ \ / ___| \/ | / ___/ _ \| \ | \ \ / /
470 | | | | | | |\/| | | | | | | | \| |\ \ / /
471 | |_| | |___| | | | | |__| |_| | |\ | \ V /
472 |____/ \____|_| |_| \____\___/|_| \_| \_/
474 DICOM Transfer Syntax Converter
476 print_usage(argv[0]);
480 if (opts.list_syntaxes) {
481 list_supported_syntaxes();
488 ____ ____ __ __ ____ ___ _ ___ __
489 | _ \ / ___| \/ | / ___/ _ \| \ | \ \ / /
490 | | | | | | |\/| | | | | | | | \| |\ \ / /
491 | |_| | |___| | | | | |__| |_| | |\ | \ V /
492 |____/ \____|_| |_| \____\___/|_| \_| \_/
494 DICOM Transfer Syntax Converter
499 if (!std::filesystem::exists(opts.input_path)) {
500 std::cerr <<
"Error: Input path does not exist: " << opts.input_path.string() <<
"\n";
506 std::cout <<
"Target Transfer Syntax: " << target_ts.name() <<
"\n";
507 std::cout <<
" UID: " << target_ts.uid() <<
"\n\n";
510 conversion_stats stats;
511 auto start_time = std::chrono::steady_clock::now();
513 if (std::filesystem::is_directory(opts.input_path)) {
515 if (!std::filesystem::exists(opts.output_path)) {
516 std::filesystem::create_directories(opts.output_path);
520 std::cout <<
"Processing directory: " << opts.input_path.string() <<
"\n";
521 if (opts.recursive) {
522 std::cout <<
"Mode: Recursive\n\n";
526 process_directory(opts.input_path, opts.output_path, opts, stats);
531 if (convert_file(opts.input_path, opts.output_path, opts)) {
532 ++stats.success_count;
538 auto end_time = std::chrono::steady_clock::now();
539 stats.total_time = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
542 if (std::filesystem::is_directory(opts.input_path) && !opts.quiet) {
543 print_summary(stats);
544 }
else if (!opts.quiet) {
545 if (stats.success_count > 0) {
546 std::cout <<
"Conversion completed successfully.\n";
550 return stats.error_count > 0 ? 2 : 0;
Represents a DICOM Transfer Syntax.
DICOM Part 10 file handling for reading/writing DICOM files.
Compile-time constants for commonly used DICOM tags.
std::optional< transfer_syntax > find_transfer_syntax(std::string_view uid)
Looks up a Transfer Syntax by its UID.
std::vector< transfer_syntax > supported_transfer_syntaxes()
Returns a list of all supported Transfer Syntaxes.