PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1
29
30#include <algorithm>
31#include <chrono>
32#include <cstdio>
33#include <cstring>
34#include <filesystem>
35#include <fstream>
36#include <iomanip>
37#include <iostream>
38#include <sstream>
39#include <string>
40#include <vector>
41
42#ifdef PACS_JPEG_FOUND
43#include <jpeglib.h>
44#endif
45
46#ifdef PACS_PNG_FOUND
47#include <png.h>
48#endif
49
50namespace {
51
55enum class output_format {
56 raw, // Raw pixel data
57 jpeg, // JPEG image
58 png, // PNG image
59 ppm // PPM/PGM (portable pixmap/graymap)
60};
61
65struct pixel_info {
66 uint16_t rows{0};
67 uint16_t columns{0};
68 uint16_t bits_allocated{0};
69 uint16_t bits_stored{0};
70 uint16_t high_bit{0};
71 uint16_t samples_per_pixel{1};
72 uint16_t pixel_representation{0};
73 uint16_t planar_configuration{0};
74 uint32_t number_of_frames{1};
75 std::string photometric_interpretation;
76 size_t pixel_data_size{0};
77 bool has_pixel_data{false};
78};
79
83struct options {
84 std::filesystem::path input_path;
85 std::filesystem::path output_path;
86 output_format format{output_format::raw};
87 int jpeg_quality{90};
88 uint32_t frame_number{0}; // 0 = all frames
89 bool extract_all_frames{true};
90 bool info_only{false};
91 bool recursive{false};
92 bool overwrite{false};
93 bool verbose{false};
94 bool quiet{false};
95 bool apply_window{false};
96 double window_center{0.0};
97 double window_width{0.0};
98};
99
103struct extraction_stats {
104 size_t total_files{0};
105 size_t success_count{0};
106 size_t skip_count{0};
107 size_t error_count{0};
108 std::chrono::milliseconds total_time{0};
109};
110
115void print_usage(const char* program_name) {
116 std::cout << R"(
117DICOM Extract - Pixel Data Extraction Utility
118
119Usage: )" << program_name
120 << R"( <input> [output] [options]
121
122Arguments:
123 input Input DICOM file or directory
124 output Output file or directory (optional for --info)
125
126Output Format Options:
127 --raw Raw pixel data (default)
128 --jpeg JPEG image (requires libjpeg)
129 --png PNG image (requires libpng)
130 --ppm PPM/PGM portable image format
131
132JPEG Options:
133 -q, --quality <1-100> JPEG quality (default: 90)
134
135Frame Selection:
136 --frame <n> Extract specific frame (0-indexed, default: all)
137 --all-frames Extract all frames (default)
138
139Windowing Options (for display):
140 --window <c> <w> Apply window center/width transformation
141
142Processing Options:
143 -r, --recursive Process directory recursively
144 --overwrite Overwrite existing output files
145 -v, --verbose Verbose output
146 --quiet Minimal output (errors only)
147
148Information:
149 --info Show pixel data information only (no extraction)
150 -h, --help Show this help message
151
152Supported Transfer Syntaxes:
153 - Uncompressed: Implicit VR, Explicit VR (LE/BE)
154 - Compressed: Requires codec support (JPEG, JPEG2000, RLE)
155
156Examples:
157 )" << program_name
158 << R"( image.dcm # Show pixel info
159 )" << program_name
160 << R"( image.dcm output.raw --raw # Extract raw pixels
161 )" << program_name
162 << R"( image.dcm output.jpg --jpeg # Extract as JPEG
163 )" << program_name
164 << R"( image.dcm output.png --png # Extract as PNG
165 )" << program_name
166 << R"( image.dcm output.ppm --ppm # Extract as PPM
167 )" << program_name
168 << R"( image.dcm output.jpg --jpeg --frame 0
169 )" << program_name
170 << R"( ./dicom/ ./images/ --recursive --jpeg
171
172Exit Codes:
173 0 Success - All files extracted successfully
174 1 Error - Invalid arguments
175 2 Error - Extraction failed for one or more files
176)";
177}
178
186bool parse_arguments(int argc, char* argv[], options& opts) {
187 if (argc < 2) {
188 return false;
189 }
190
191 for (int i = 1; i < argc; ++i) {
192 std::string arg = argv[i];
193
194 if (arg == "--help" || arg == "-h") {
195 return false;
196 } else if (arg == "--info") {
197 opts.info_only = true;
198 } else if (arg == "--raw") {
199 opts.format = output_format::raw;
200 } else if (arg == "--jpeg") {
201 opts.format = output_format::jpeg;
202 } else if (arg == "--png") {
203 opts.format = output_format::png;
204 } else if (arg == "--ppm") {
205 opts.format = output_format::ppm;
206 } else if ((arg == "-q" || arg == "--quality") && i + 1 < argc) {
207 try {
208 opts.jpeg_quality = std::stoi(argv[++i]);
209 if (opts.jpeg_quality < 1 || opts.jpeg_quality > 100) {
210 std::cerr << "Error: Quality must be between 1 and 100\n";
211 return false;
212 }
213 } catch (...) {
214 std::cerr << "Error: Invalid quality value\n";
215 return false;
216 }
217 } else if (arg == "--frame" && i + 1 < argc) {
218 try {
219 opts.frame_number = static_cast<uint32_t>(std::stoul(argv[++i]));
220 opts.extract_all_frames = false;
221 } catch (...) {
222 std::cerr << "Error: Invalid frame number\n";
223 return false;
224 }
225 } else if (arg == "--all-frames") {
226 opts.extract_all_frames = true;
227 } else if (arg == "--window" && i + 2 < argc) {
228 try {
229 opts.window_center = std::stod(argv[++i]);
230 opts.window_width = std::stod(argv[++i]);
231 opts.apply_window = true;
232 } catch (...) {
233 std::cerr << "Error: Invalid window values\n";
234 return false;
235 }
236 } else if (arg == "-r" || arg == "--recursive") {
237 opts.recursive = true;
238 } else if (arg == "--overwrite") {
239 opts.overwrite = true;
240 } else if (arg == "-v" || arg == "--verbose") {
241 opts.verbose = true;
242 } else if (arg == "--quiet") {
243 opts.quiet = true;
244 } else if (arg[0] == '-') {
245 std::cerr << "Error: Unknown option '" << arg << "'\n";
246 return false;
247 } else if (opts.input_path.empty()) {
248 opts.input_path = arg;
249 } else if (opts.output_path.empty()) {
250 opts.output_path = arg;
251 } else {
252 std::cerr << "Error: Too many arguments\n";
253 return false;
254 }
255 }
256
257 if (opts.input_path.empty()) {
258 std::cerr << "Error: No input path specified\n";
259 return false;
260 }
261
262 if (opts.output_path.empty() && !opts.info_only) {
263 // Default: show info only
264 opts.info_only = true;
265 }
266
267 // Quiet mode overrides verbose
268 if (opts.quiet) {
269 opts.verbose = false;
270 }
271
272 return true;
273}
274
280pixel_info get_pixel_info(const kcenon::pacs::core::dicom_dataset& dataset) {
281 using namespace kcenon::pacs::core;
282
283 pixel_info info;
284
285 // Image dimensions
286 if (auto val = dataset.get_numeric<uint16_t>(tags::rows); val) {
287 info.rows = *val;
288 }
289 if (auto val = dataset.get_numeric<uint16_t>(tags::columns); val) {
290 info.columns = *val;
291 }
292
293 // Bit depth
294 if (auto val = dataset.get_numeric<uint16_t>(dicom_tag{0x0028, 0x0100}); val) {
295 info.bits_allocated = *val;
296 }
297 if (auto val = dataset.get_numeric<uint16_t>(dicom_tag{0x0028, 0x0101}); val) {
298 info.bits_stored = *val;
299 }
300 if (auto val = dataset.get_numeric<uint16_t>(dicom_tag{0x0028, 0x0102}); val) {
301 info.high_bit = *val;
302 }
303
304 // Samples and representation
305 if (auto val = dataset.get_numeric<uint16_t>(tags::samples_per_pixel); val) {
306 info.samples_per_pixel = *val;
307 }
308 if (auto val = dataset.get_numeric<uint16_t>(dicom_tag{0x0028, 0x0103}); val) {
309 info.pixel_representation = *val;
310 }
311 if (auto val = dataset.get_numeric<uint16_t>(dicom_tag{0x0028, 0x0006}); val) {
312 info.planar_configuration = *val;
313 }
314
315 // Number of frames
316 auto frames_str = dataset.get_string(dicom_tag{0x0028, 0x0008});
317 if (!frames_str.empty()) {
318 try {
319 info.number_of_frames = static_cast<uint32_t>(std::stoul(frames_str));
320 } catch (...) {
321 info.number_of_frames = 1;
322 }
323 }
324
325 // Photometric interpretation
326 info.photometric_interpretation =
327 dataset.get_string(tags::photometric_interpretation);
328
329 // Check for pixel data
330 auto* pixel_data = dataset.get(tags::pixel_data);
331 if (pixel_data != nullptr) {
332 info.has_pixel_data = true;
333 info.pixel_data_size = pixel_data->length();
334 }
335
336 return info;
337}
338
344void print_pixel_info(const pixel_info& info,
345 const std::filesystem::path& file_path) {
346 std::cout << "\n";
347 std::cout << "========================================\n";
348 std::cout << " Pixel Data Information\n";
349 std::cout << "========================================\n";
350 std::cout << " File: " << file_path.filename().string() << "\n";
351 std::cout << " Dimensions: " << info.columns << " x " << info.rows << "\n";
352 std::cout << " Bits Allocated: " << info.bits_allocated << "\n";
353 std::cout << " Bits Stored: " << info.bits_stored << "\n";
354 std::cout << " High Bit: " << info.high_bit << "\n";
355 std::cout << " Samples/Pixel: " << info.samples_per_pixel << "\n";
356 std::cout << " Pixel Rep: "
357 << (info.pixel_representation == 0 ? "Unsigned" : "Signed") << "\n";
358 std::cout << " Photometric: " << info.photometric_interpretation << "\n";
359 std::cout << " Number of Frames: " << info.number_of_frames << "\n";
360 std::cout << " Has Pixel Data: " << (info.has_pixel_data ? "Yes" : "No") << "\n";
361 if (info.has_pixel_data) {
362 std::cout << " Pixel Data Size: " << info.pixel_data_size << " bytes\n";
363
364 // Calculate expected size
365 size_t expected = static_cast<size_t>(info.columns) * info.rows *
366 info.samples_per_pixel * ((info.bits_allocated + 7) / 8) *
367 info.number_of_frames;
368 std::cout << " Expected Size: " << expected << " bytes\n";
369
370 if (info.pixel_data_size != expected) {
371 std::cout << " Note: Size mismatch - data may be compressed\n";
372 }
373 }
374 std::cout << "========================================\n";
375}
376
384uint8_t apply_window_level(int pixel, double center, double width) {
385 double lower = center - width / 2.0;
386 double upper = center + width / 2.0;
387
388 if (pixel <= lower) return 0;
389 if (pixel >= upper) return 255;
390
391 return static_cast<uint8_t>(
392 ((pixel - lower) / width) * 255.0);
393}
394
395#ifdef PACS_JPEG_FOUND
406bool write_jpeg(const std::filesystem::path& output_path,
407 const std::vector<uint8_t>& pixels,
408 int width, int height, int components, int quality) {
409 FILE* file = fopen(output_path.string().c_str(), "wb");
410 if (file == nullptr) {
411 std::cerr << "Error: Cannot create file: " << output_path << "\n";
412 return false;
413 }
414
415 struct jpeg_compress_struct cinfo {};
416 struct jpeg_error_mgr jerr {};
417
418 cinfo.err = jpeg_std_error(&jerr);
419 jpeg_create_compress(&cinfo);
420 jpeg_stdio_dest(&cinfo, file);
421
422 cinfo.image_width = static_cast<JDIMENSION>(width);
423 cinfo.image_height = static_cast<JDIMENSION>(height);
424 cinfo.input_components = components;
425 cinfo.in_color_space = (components == 1) ? JCS_GRAYSCALE : JCS_RGB;
426
427 jpeg_set_defaults(&cinfo);
428 jpeg_set_quality(&cinfo, quality, TRUE);
429 jpeg_start_compress(&cinfo, TRUE);
430
431 JSAMPROW row_pointer[1];
432 int row_stride = width * components;
433
434 while (cinfo.next_scanline < cinfo.image_height) {
435 row_pointer[0] =
436 const_cast<JSAMPROW>(&pixels[cinfo.next_scanline * static_cast<size_t>(row_stride)]);
437 jpeg_write_scanlines(&cinfo, row_pointer, 1);
438 }
439
440 jpeg_finish_compress(&cinfo);
441 jpeg_destroy_compress(&cinfo);
442 fclose(file);
443
444 return true;
445}
446#endif
447
448#ifdef PACS_PNG_FOUND
458bool write_png(const std::filesystem::path& output_path,
459 const std::vector<uint8_t>& pixels,
460 int width, int height, int components) {
461 FILE* file = fopen(output_path.string().c_str(), "wb");
462 if (file == nullptr) {
463 std::cerr << "Error: Cannot create file: " << output_path << "\n";
464 return false;
465 }
466
467 // Create PNG write struct
468 png_structp png_ptr = png_create_write_struct(
469 PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
470 if (png_ptr == nullptr) {
471 std::cerr << "Error: Failed to create PNG write struct\n";
472 fclose(file);
473 return false;
474 }
475
476 // Create PNG info struct
477 png_infop info_ptr = png_create_info_struct(png_ptr);
478 if (info_ptr == nullptr) {
479 std::cerr << "Error: Failed to create PNG info struct\n";
480 png_destroy_write_struct(&png_ptr, nullptr);
481 fclose(file);
482 return false;
483 }
484
485 // Set up error handling (required for libpng)
486 if (setjmp(png_jmpbuf(png_ptr))) {
487 std::cerr << "Error: PNG write error\n";
488 png_destroy_write_struct(&png_ptr, &info_ptr);
489 fclose(file);
490 return false;
491 }
492
493 // Initialize I/O
494 png_init_io(png_ptr, file);
495
496 // Set compression level (6 = default, good balance of speed/size)
497 png_set_compression_level(png_ptr, 6);
498
499 // Set image attributes
500 int color_type = (components == 1) ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_RGB;
501 png_set_IHDR(png_ptr, info_ptr,
502 static_cast<png_uint_32>(width),
503 static_cast<png_uint_32>(height),
504 8, // Bit depth
505 color_type,
506 PNG_INTERLACE_NONE,
507 PNG_COMPRESSION_TYPE_DEFAULT,
508 PNG_FILTER_TYPE_DEFAULT);
509
510 // Write header
511 png_write_info(png_ptr, info_ptr);
512
513 // Write image data row by row
514 int row_stride = width * components;
515 for (int y = 0; y < height; ++y) {
516 png_bytep row = const_cast<png_bytep>(&pixels[static_cast<size_t>(y) * row_stride]);
517 png_write_row(png_ptr, row);
518 }
519
520 // Finish writing
521 png_write_end(png_ptr, nullptr);
522
523 // Cleanup
524 png_destroy_write_struct(&png_ptr, &info_ptr);
525 fclose(file);
526
527 return true;
528}
529#endif
530
540bool write_ppm(const std::filesystem::path& output_path,
541 const std::vector<uint8_t>& pixels,
542 int width, int height, int components) {
543 std::ofstream file(output_path, std::ios::binary);
544 if (!file) {
545 std::cerr << "Error: Cannot create file: " << output_path << "\n";
546 return false;
547 }
548
549 // Write header
550 if (components == 1) {
551 file << "P5\n"; // PGM binary
552 } else {
553 file << "P6\n"; // PPM binary
554 }
555 file << width << " " << height << "\n";
556 file << "255\n";
557
558 // Write pixel data
559 file.write(reinterpret_cast<const char*>(pixels.data()), static_cast<std::streamsize>(pixels.size()));
560
561 return file.good();
562}
563
570bool write_raw(const std::filesystem::path& output_path,
571 const std::vector<uint8_t>& pixels) {
572 std::ofstream file(output_path, std::ios::binary);
573 if (!file) {
574 std::cerr << "Error: Cannot create file: " << output_path << "\n";
575 return false;
576 }
577
578 file.write(reinterpret_cast<const char*>(pixels.data()), static_cast<std::streamsize>(pixels.size()));
579
580 return file.good();
581}
582
590std::vector<uint8_t> convert_to_8bit(const std::vector<uint8_t>& data,
591 const pixel_info& info,
592 const options& opts) {
593 size_t num_pixels = static_cast<size_t>(info.columns) * info.rows * info.samples_per_pixel;
594 std::vector<uint8_t> result(num_pixels);
595
596 // Find min/max for auto-windowing if not specified
597 double window_center = opts.window_center;
598 double window_width = opts.window_width;
599
600 if (!opts.apply_window) {
601 // Auto-calculate window from data
602 int min_val = std::numeric_limits<int>::max();
603 int max_val = std::numeric_limits<int>::min();
604
605 for (size_t i = 0; i < num_pixels; ++i) {
606 int16_t pixel;
607 if (info.pixel_representation == 0) {
608 pixel = static_cast<int16_t>(
609 data[i * 2] | (data[i * 2 + 1] << 8));
610 } else {
611 pixel = static_cast<int16_t>(
612 data[i * 2] | (data[i * 2 + 1] << 8));
613 }
614 min_val = std::min(min_val, static_cast<int>(pixel));
615 max_val = std::max(max_val, static_cast<int>(pixel));
616 }
617
618 window_width = static_cast<double>(max_val - min_val);
619 window_center = static_cast<double>(min_val + max_val) / 2.0;
620
621 if (window_width < 1) window_width = 1;
622 }
623
624 // Apply windowing
625 for (size_t i = 0; i < num_pixels; ++i) {
626 int16_t pixel = static_cast<int16_t>(
627 data[i * 2] | (data[i * 2 + 1] << 8));
628 result[i] = apply_window_level(pixel, window_center, window_width);
629 }
630
631 return result;
632}
633
641bool extract_file(const std::filesystem::path& input_path,
642 const std::filesystem::path& output_path,
643 const options& opts) {
644 using namespace kcenon::pacs::core;
645
646 // Open DICOM file
647 auto result = dicom_file::open(input_path);
648 if (result.is_err()) {
649 std::cerr << "Error: Failed to open '" << input_path.string()
650 << "': " << result.error().message << "\n";
651 return false;
652 }
653
654 auto& file = result.value();
655 const auto& dataset = file.dataset();
656
657 // Get pixel information
658 auto info = get_pixel_info(dataset);
659
660 // Info only mode
661 if (opts.info_only) {
662 print_pixel_info(info, input_path);
663 return true;
664 }
665
666 // Check if pixel data exists
667 if (!info.has_pixel_data) {
668 std::cerr << "Error: No pixel data in file: " << input_path << "\n";
669 return false;
670 }
671
672 // Check if output exists and overwrite is disabled
673 if (std::filesystem::exists(output_path) && !opts.overwrite) {
674 if (opts.verbose) {
675 std::cout << " Skipped (exists): " << output_path.filename().string()
676 << "\n";
677 }
678 return true;
679 }
680
681 // Get pixel data
682 auto* pixel_element = dataset.get(tags::pixel_data);
683 if (pixel_element == nullptr) {
684 std::cerr << "Error: Cannot read pixel data: " << input_path << "\n";
685 return false;
686 }
687
688 auto pixel_data = pixel_element->raw_data();
689 std::vector<uint8_t> pixels(pixel_data.begin(), pixel_data.end());
690
691 if (opts.verbose) {
692 std::cout << " Extracting: " << input_path.filename().string() << "\n";
693 std::cout << " Size: " << info.columns << " x " << info.rows << "\n";
694 std::cout << " Bits: " << info.bits_stored << "/" << info.bits_allocated
695 << "\n";
696 }
697
698 // Convert to 8-bit if necessary
699 if (info.bits_allocated == 16) {
700 pixels = convert_to_8bit(pixels, info, opts);
701 }
702
703 // Handle MONOCHROME1 inversion
704 if (info.photometric_interpretation == "MONOCHROME1") {
705 for (auto& p : pixels) {
706 p = static_cast<uint8_t>(255 - p);
707 }
708 }
709
710 // Ensure output directory exists
711 auto output_dir = output_path.parent_path();
712 if (!output_dir.empty() && !std::filesystem::exists(output_dir)) {
713 std::filesystem::create_directories(output_dir);
714 }
715
716 // Write output based on format
717 bool success = false;
718
719 switch (opts.format) {
720 case output_format::raw:
721 success = write_raw(output_path, pixels);
722 break;
723
724 case output_format::jpeg:
725#ifdef PACS_JPEG_FOUND
726 success = write_jpeg(output_path, pixels, info.columns, info.rows,
727 info.samples_per_pixel, opts.jpeg_quality);
728#else
729 std::cerr << "Error: JPEG support not available. Install libjpeg-turbo.\n";
730 success = false;
731#endif
732 break;
733
734 case output_format::png:
735#ifdef PACS_PNG_FOUND
736 success = write_png(output_path, pixels, info.columns, info.rows,
737 info.samples_per_pixel);
738#else
739 std::cerr << "Error: PNG support not available. Install libpng.\n";
740 success = false;
741#endif
742 break;
743
744 case output_format::ppm:
745 success =
746 write_ppm(output_path, pixels, info.columns, info.rows, info.samples_per_pixel);
747 break;
748 }
749
750 if (success && opts.verbose) {
751 std::cout << " Output: " << output_path.string() << "\n";
752 }
753
754 return success;
755}
756
762std::string get_output_extension(output_format format) {
763 switch (format) {
764 case output_format::raw:
765 return ".raw";
766 case output_format::jpeg:
767 return ".jpg";
768 case output_format::png:
769 return ".png";
770 case output_format::ppm:
771 return ".ppm";
772 }
773 return ".raw";
774}
775
781bool is_dicom_file(const std::filesystem::path& file_path) {
782 auto ext = file_path.extension().string();
783 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
784 return ext == ".dcm" || ext == ".dicom" || ext.empty();
785}
786
794void process_directory(const std::filesystem::path& input_dir,
795 const std::filesystem::path& output_dir,
796 const options& opts,
797 extraction_stats& stats) {
798 auto process_file = [&](const std::filesystem::path& file_path) {
799 if (!is_dicom_file(file_path)) {
800 return;
801 }
802
803 ++stats.total_files;
804
805 // Calculate output path with appropriate extension
806 auto relative_path = std::filesystem::relative(file_path, input_dir);
807 auto output_path = output_dir / relative_path;
808 output_path.replace_extension(get_output_extension(opts.format));
809
810 auto start = std::chrono::steady_clock::now();
811
812 if (extract_file(file_path, output_path, opts)) {
813 ++stats.success_count;
814 } else {
815 ++stats.error_count;
816 }
817
818 auto end = std::chrono::steady_clock::now();
819 stats.total_time +=
820 std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
821
822 if (!opts.quiet) {
823 std::cout << "\rProcessed: " << stats.total_files
824 << " (Success: " << stats.success_count
825 << ", Errors: " << stats.error_count << ")" << std::flush;
826 }
827 };
828
829 if (opts.recursive) {
830 for (const auto& entry :
831 std::filesystem::recursive_directory_iterator(input_dir)) {
832 if (entry.is_regular_file()) {
833 process_file(entry.path());
834 }
835 }
836 } else {
837 for (const auto& entry : std::filesystem::directory_iterator(input_dir)) {
838 if (entry.is_regular_file()) {
839 process_file(entry.path());
840 }
841 }
842 }
843
844 if (!opts.quiet) {
845 std::cout << "\n";
846 }
847}
848
853void print_summary(const extraction_stats& stats) {
854 std::cout << "\n";
855 std::cout << "========================================\n";
856 std::cout << " Extraction Summary\n";
857 std::cout << "========================================\n";
858 std::cout << " Total files: " << stats.total_files << "\n";
859 std::cout << " Successful: " << stats.success_count << "\n";
860 std::cout << " Skipped: " << stats.skip_count << "\n";
861 std::cout << " Errors: " << stats.error_count << "\n";
862 std::cout << " Total time: " << stats.total_time.count() << " ms\n";
863 if (stats.total_files > 0) {
864 auto avg_time =
865 stats.total_time.count() / static_cast<double>(stats.total_files);
866 std::cout << " Avg per file: " << std::fixed << std::setprecision(1)
867 << avg_time << " ms\n";
868 }
869 std::cout << "========================================\n";
870}
871
872} // namespace
873
874int main(int argc, char* argv[]) {
875 options opts;
876
877 if (!parse_arguments(argc, argv, opts)) {
878 std::cout << R"(
879 ____ ____ __ __ _______ _______ ____ _ ____ _____
880 | _ \ / ___| \/ | | ____\ \/ /_ _| _ \ / \ / ___|_ _|
881 | | | | | | |\/| | | _| \ / | | | |_) | / _ \| | | |
882 | |_| | |___| | | | | |___ / \ | | | _ < / ___ \ |___ | |
883 |____/ \____|_| |_| |_____/_/\_\ |_| |_| \_\/_/ \_\____| |_|
884
885 DICOM Pixel Data Extraction Utility
886)" << "\n";
887 print_usage(argv[0]);
888 return 1;
889 }
890
891 // Check input exists
892 if (!std::filesystem::exists(opts.input_path)) {
893 std::cerr << "Error: Input path does not exist: " << opts.input_path.string()
894 << "\n";
895 return 2;
896 }
897
898 // Show banner
899 if (!opts.quiet) {
900 std::cout << R"(
901 ____ ____ __ __ _______ _______ ____ _ ____ _____
902 | _ \ / ___| \/ | | ____\ \/ /_ _| _ \ / \ / ___|_ _|
903 | | | | | | |\/| | | _| \ / | | | |_) | / _ \| | | |
904 | |_| | |___| | | | | |___ / \ | | | _ < / ___ \ |___ | |
905 |____/ \____|_| |_| |_____/_/\_\ |_| |_| \_\/_/ \_\____| |_|
906
907 DICOM Pixel Data Extraction Utility
908)" << "\n";
909 }
910
911 extraction_stats stats;
912 auto start_time = std::chrono::steady_clock::now();
913
914 if (std::filesystem::is_directory(opts.input_path)) {
915 // Process directory
916 if (!opts.info_only) {
917 if (!std::filesystem::exists(opts.output_path)) {
918 std::filesystem::create_directories(opts.output_path);
919 }
920 }
921
922 if (!opts.quiet) {
923 std::cout << "Processing directory: " << opts.input_path.string() << "\n";
924 if (opts.recursive) {
925 std::cout << "Mode: Recursive\n\n";
926 }
927 }
928
929 process_directory(opts.input_path, opts.output_path, opts, stats);
930 } else {
931 // Process single file
932 ++stats.total_files;
933
934 if (extract_file(opts.input_path, opts.output_path, opts)) {
935 ++stats.success_count;
936 if (!opts.quiet && !opts.info_only) {
937 std::cout << "Extraction completed successfully.\n";
938 std::cout << " Output: " << opts.output_path.string() << "\n";
939 }
940 } else {
941 ++stats.error_count;
942 }
943 }
944
945 auto end_time = std::chrono::steady_clock::now();
946 stats.total_time =
947 std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
948
949 // Print summary for directory processing
950 if (std::filesystem::is_directory(opts.input_path) && !opts.quiet &&
951 !opts.info_only) {
952 print_summary(stats);
953 }
954
955 return stats.error_count > 0 ? 2 : 0;
956}
auto get(dicom_tag tag) noexcept -> dicom_element *
Get a pointer to the element with the given tag.
auto get_numeric(dicom_tag tag) const -> std::optional< T >
Get the numeric value of an element.
auto get_string(dicom_tag tag, std::string_view default_value="") const -> std::string
Get the string value of an element.
DICOM Dataset - ordered collection of Data Elements.
DICOM Data Element representation (Tag, VR, Value)
DICOM Part 10 file handling for reading/writing DICOM files.
DICOM Tag representation (Group, Element pairs)
Compile-time constants for commonly used DICOM tags.
int main()
Definition main.cpp:84
constexpr dicom_tag high_bit
High Bit.
constexpr dicom_tag window_width
Window Width.
constexpr dicom_tag window_center
Window Center.
constexpr dicom_tag bits_allocated
Bits Allocated.
constexpr dicom_tag rows
Rows.
constexpr dicom_tag columns
Columns.
constexpr dicom_tag bits_stored
Bits Stored.
constexpr dicom_tag pixel_data
Pixel Data.
constexpr dicom_tag pixel_representation
Pixel Representation.
constexpr dicom_tag samples_per_pixel
Samples per Pixel.
constexpr core::dicom_tag number_of_frames
Number of Frames (0028,0008)