PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1
25
26#include <algorithm>
27#include <cctype>
28#include <cstdint>
29#include <filesystem>
30#include <fstream>
31#include <iomanip>
32#include <iostream>
33#include <map>
34#include <optional>
35#include <sstream>
36#include <string>
37#include <variant>
38#include <vector>
39
40namespace {
41
45struct options {
46 std::filesystem::path input_path;
47 std::filesystem::path output_path;
48 std::filesystem::path template_path;
49 std::filesystem::path bulk_data_dir;
50 std::string transfer_syntax;
51 bool verbose{false};
52 bool quiet{false};
53};
54
55// ============================================================================
56// Minimal JSON Parser
57// ============================================================================
58
62struct json_value;
63using json_object = std::map<std::string, json_value>;
64using json_array = std::vector<json_value>;
65
66struct json_value {
67 std::variant<std::nullptr_t, bool, double, std::string, json_array, json_object> data;
68
69 bool is_null() const { return std::holds_alternative<std::nullptr_t>(data); }
70 bool is_bool() const { return std::holds_alternative<bool>(data); }
71 bool is_number() const { return std::holds_alternative<double>(data); }
72 bool is_string() const { return std::holds_alternative<std::string>(data); }
73 bool is_array() const { return std::holds_alternative<json_array>(data); }
74 bool is_object() const { return std::holds_alternative<json_object>(data); }
75
76 bool as_bool() const { return std::get<bool>(data); }
77 double as_number() const { return std::get<double>(data); }
78 const std::string& as_string() const { return std::get<std::string>(data); }
79 const json_array& as_array() const { return std::get<json_array>(data); }
80 const json_object& as_object() const { return std::get<json_object>(data); }
81
82 bool has(const std::string& key) const {
83 if (!is_object()) return false;
84 return as_object().count(key) > 0;
85 }
86
87 const json_value& operator[](const std::string& key) const {
88 return as_object().at(key);
89 }
90
91 const json_value& operator[](size_t idx) const {
92 return as_array().at(idx);
93 }
94};
95
99class json_parser {
100public:
101 explicit json_parser(const std::string& input) : input_(input), pos_(0) {}
102
103 json_value parse() {
104 skip_whitespace();
105 return parse_value();
106 }
107
108private:
109 const std::string& input_;
110 size_t pos_;
111
112 char peek() const {
113 return pos_ < input_.size() ? input_[pos_] : '\0';
114 }
115
116 char get() {
117 return pos_ < input_.size() ? input_[pos_++] : '\0';
118 }
119
120 void skip_whitespace() {
121 while (pos_ < input_.size() && std::isspace(static_cast<unsigned char>(input_[pos_]))) {
122 ++pos_;
123 }
124 }
125
126 void expect(char c) {
127 skip_whitespace();
128 if (get() != c) {
129 throw std::runtime_error("Expected '" + std::string(1, c) + "'");
130 }
131 }
132
133 json_value parse_value() {
134 skip_whitespace();
135 char c = peek();
136
137 if (c == '{') return parse_object();
138 if (c == '[') return parse_array();
139 if (c == '"') return parse_string();
140 if (c == 't' || c == 'f') return parse_bool();
141 if (c == 'n') return parse_null();
142 if (c == '-' || std::isdigit(static_cast<unsigned char>(c))) return parse_number();
143
144 throw std::runtime_error("Unexpected character in JSON");
145 }
146
147 json_value parse_object() {
148 json_object obj;
149 expect('{');
150 skip_whitespace();
151
152 if (peek() == '}') {
153 get();
154 return json_value{obj};
155 }
156
157 while (true) {
158 skip_whitespace();
159 if (peek() != '"') {
160 throw std::runtime_error("Expected string key in object");
161 }
162 std::string key = parse_string_value();
163 expect(':');
164 obj[key] = parse_value();
165 skip_whitespace();
166
167 char c = get();
168 if (c == '}') break;
169 if (c != ',') throw std::runtime_error("Expected ',' or '}' in object");
170 }
171
172 return json_value{obj};
173 }
174
175 json_value parse_array() {
176 json_array arr;
177 expect('[');
178 skip_whitespace();
179
180 if (peek() == ']') {
181 get();
182 return json_value{arr};
183 }
184
185 while (true) {
186 arr.push_back(parse_value());
187 skip_whitespace();
188
189 char c = get();
190 if (c == ']') break;
191 if (c != ',') throw std::runtime_error("Expected ',' or ']' in array");
192 }
193
194 return json_value{arr};
195 }
196
197 std::string parse_string_value() {
198 expect('"');
199 std::string result;
200
201 while (peek() != '"') {
202 char c = get();
203 if (c == '\\') {
204 c = get();
205 switch (c) {
206 case '"': result += '"'; break;
207 case '\\': result += '\\'; break;
208 case '/': result += '/'; break;
209 case 'b': result += '\b'; break;
210 case 'f': result += '\f'; break;
211 case 'n': result += '\n'; break;
212 case 'r': result += '\r'; break;
213 case 't': result += '\t'; break;
214 case 'u': {
215 std::string hex;
216 for (int i = 0; i < 4; ++i) hex += get();
217 int code = std::stoi(hex, nullptr, 16);
218 if (code < 0x80) {
219 result += static_cast<char>(code);
220 } else if (code < 0x800) {
221 result += static_cast<char>(0xC0 | (code >> 6));
222 result += static_cast<char>(0x80 | (code & 0x3F));
223 } else {
224 result += static_cast<char>(0xE0 | (code >> 12));
225 result += static_cast<char>(0x80 | ((code >> 6) & 0x3F));
226 result += static_cast<char>(0x80 | (code & 0x3F));
227 }
228 break;
229 }
230 default: result += c; break;
231 }
232 } else {
233 result += c;
234 }
235 }
236 get(); // consume closing quote
237 return result;
238 }
239
240 json_value parse_string() {
241 return json_value{parse_string_value()};
242 }
243
244 json_value parse_number() {
245 size_t start = pos_;
246 if (peek() == '-') get();
247 while (std::isdigit(static_cast<unsigned char>(peek()))) get();
248 if (peek() == '.') {
249 get();
250 while (std::isdigit(static_cast<unsigned char>(peek()))) get();
251 }
252 if (peek() == 'e' || peek() == 'E') {
253 get();
254 if (peek() == '+' || peek() == '-') get();
255 while (std::isdigit(static_cast<unsigned char>(peek()))) get();
256 }
257 return json_value{std::stod(input_.substr(start, pos_ - start))};
258 }
259
260 json_value parse_bool() {
261 if (input_.substr(pos_, 4) == "true") {
262 pos_ += 4;
263 return json_value{true};
264 }
265 if (input_.substr(pos_, 5) == "false") {
266 pos_ += 5;
267 return json_value{false};
268 }
269 throw std::runtime_error("Invalid boolean");
270 }
271
272 json_value parse_null() {
273 if (input_.substr(pos_, 4) == "null") {
274 pos_ += 4;
275 return json_value{nullptr};
276 }
277 throw std::runtime_error("Invalid null");
278 }
279};
280
281// ============================================================================
282// Base64 Decoding
283// ============================================================================
284
288constexpr int8_t base64_decode_table[] = {
289 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
290 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
291 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
292 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
293 -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
294 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
295 -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
296 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
297};
298
304[[nodiscard]] std::vector<uint8_t> from_base64(const std::string& input) {
305 std::vector<uint8_t> result;
306 result.reserve((input.size() * 3) / 4);
307
308 size_t i = 0;
309 while (i < input.size()) {
310 // Skip whitespace
311 while (i < input.size() && std::isspace(static_cast<unsigned char>(input[i]))) ++i;
312 if (i >= input.size()) break;
313
314 uint32_t sextet[4] = {0, 0, 0, 0};
315 int padding = 0;
316
317 for (int j = 0; j < 4 && i < input.size(); ++j) {
318 char c = input[i++];
319 if (c == '=') {
320 padding++;
321 sextet[j] = 0;
322 } else if (static_cast<unsigned char>(c) < 128 && base64_decode_table[static_cast<unsigned char>(c)] >= 0) {
323 sextet[j] = static_cast<uint32_t>(base64_decode_table[static_cast<unsigned char>(c)]);
324 } else {
325 // Invalid character, skip
326 --j;
327 }
328 }
329
330 uint32_t triple = (sextet[0] << 18) | (sextet[1] << 12) | (sextet[2] << 6) | sextet[3];
331
332 result.push_back(static_cast<uint8_t>((triple >> 16) & 0xFF));
333 if (padding < 2) result.push_back(static_cast<uint8_t>((triple >> 8) & 0xFF));
334 if (padding < 1) result.push_back(static_cast<uint8_t>(triple & 0xFF));
335 }
336
337 return result;
338}
339
340// ============================================================================
341// Utility Functions
342// ============================================================================
343
349[[nodiscard]] kcenon::pacs::encoding::vr_type parse_vr(const std::string& vr_str) {
350 using namespace kcenon::pacs::encoding;
351
352 static const std::map<std::string, vr_type> vr_map = {
353 {"AE", vr_type::AE}, {"AS", vr_type::AS}, {"AT", vr_type::AT},
354 {"CS", vr_type::CS}, {"DA", vr_type::DA}, {"DS", vr_type::DS},
355 {"DT", vr_type::DT}, {"FL", vr_type::FL}, {"FD", vr_type::FD},
356 {"IS", vr_type::IS}, {"LO", vr_type::LO}, {"LT", vr_type::LT},
357 {"OB", vr_type::OB}, {"OD", vr_type::OD}, {"OF", vr_type::OF},
358 {"OL", vr_type::OL}, {"OV", vr_type::OV}, {"OW", vr_type::OW},
359 {"PN", vr_type::PN}, {"SH", vr_type::SH}, {"SL", vr_type::SL},
360 {"SQ", vr_type::SQ}, {"SS", vr_type::SS}, {"ST", vr_type::ST},
361 {"SV", vr_type::SV}, {"TM", vr_type::TM}, {"UC", vr_type::UC},
362 {"UI", vr_type::UI}, {"UL", vr_type::UL}, {"UN", vr_type::UN},
363 {"UR", vr_type::UR}, {"US", vr_type::US}, {"UT", vr_type::UT},
364 {"UV", vr_type::UV}
365 };
366
367 auto it = vr_map.find(vr_str);
368 return it != vr_map.end() ? it->second : vr_type::UN;
369}
370
376[[nodiscard]] std::optional<kcenon::pacs::core::dicom_tag> parse_tag(const std::string& tag_str) {
377 if (tag_str.length() != 8) {
378 return std::nullopt;
379 }
380
381 try {
382 uint16_t group = static_cast<uint16_t>(std::stoul(tag_str.substr(0, 4), nullptr, 16));
383 uint16_t elem = static_cast<uint16_t>(std::stoul(tag_str.substr(4, 4), nullptr, 16));
384 return kcenon::pacs::core::dicom_tag{group, elem};
385 } catch (...) {
386 return std::nullopt;
387 }
388}
389
395[[nodiscard]] std::string read_file(const std::filesystem::path& path) {
396 std::ifstream file(path, std::ios::binary);
397 if (!file) {
398 throw std::runtime_error("Cannot open file: " + path.string());
399 }
400
401 std::ostringstream oss;
402 oss << file.rdbuf();
403 return oss.str();
404}
405
412[[nodiscard]] std::vector<uint8_t> read_bulk_data(const std::string& uri,
413 const std::filesystem::path& bulk_dir) {
414 std::string path = uri;
415
416 // Remove file:// prefix if present
417 if (path.substr(0, 7) == "file://") {
418 path = path.substr(7);
419 }
420
421 // Handle relative paths
422 std::filesystem::path file_path = path;
423 if (!file_path.is_absolute() && !bulk_dir.empty()) {
424 file_path = bulk_dir / file_path;
425 }
426
427 std::ifstream file(file_path, std::ios::binary);
428 if (!file) {
429 throw std::runtime_error("Cannot open bulk data file: " + file_path.string());
430 }
431
432 return std::vector<uint8_t>(
433 std::istreambuf_iterator<char>(file),
434 std::istreambuf_iterator<char>()
435 );
436}
437
438// Forward declaration
439void parse_dataset(const json_object& json_obj,
441 const options& opts);
442
450[[nodiscard]] kcenon::pacs::core::dicom_element create_element(
452 const json_value& element_json,
453 const options& opts) {
454 using namespace kcenon::pacs::core;
455 using namespace kcenon::pacs::encoding;
456
457 if (!element_json.is_object()) {
458 throw std::runtime_error("Element value must be an object");
459 }
460
461 const auto& obj = element_json.as_object();
462
463 // Get VR
464 std::string vr_str = "UN";
465 if (obj.count("vr")) {
466 vr_str = obj.at("vr").as_string();
467 }
468 auto vr = parse_vr(vr_str);
469
470 // Handle sequence
471 if (vr == vr_type::SQ) {
472 dicom_element elem{tag, vr};
473 if (obj.count("Value") && obj.at("Value").is_array()) {
474 auto& items = elem.sequence_items();
475 for (const auto& item_json : obj.at("Value").as_array()) {
476 if (item_json.is_object()) {
477 dicom_dataset item_dataset;
478 parse_dataset(item_json.as_object(), item_dataset, opts);
479 items.push_back(std::move(item_dataset));
480 }
481 }
482 }
483 return elem;
484 }
485
486 // Handle InlineBinary (Base64)
487 if (obj.count("InlineBinary")) {
488 std::string base64 = obj.at("InlineBinary").as_string();
489 auto data = from_base64(base64);
490 return dicom_element{tag, vr, std::span<const uint8_t>(data)};
491 }
492
493 // Handle BulkDataURI
494 if (obj.count("BulkDataURI")) {
495 std::string uri = obj.at("BulkDataURI").as_string();
496 auto data = read_bulk_data(uri, opts.bulk_data_dir);
497 return dicom_element{tag, vr, std::span<const uint8_t>(data)};
498 }
499
500 // Handle Value array
501 if (!obj.count("Value") || !obj.at("Value").is_array()) {
502 // Empty element
503 return dicom_element{tag, vr};
504 }
505
506 const auto& values = obj.at("Value").as_array();
507 if (values.empty()) {
508 return dicom_element{tag, vr};
509 }
510
511 // Handle PersonName (PN)
512 if (vr == vr_type::PN) {
513 std::string combined;
514 for (size_t i = 0; i < values.size(); ++i) {
515 if (i > 0) combined += "\\";
516 if (values[i].is_object()) {
517 const auto& pn = values[i].as_object();
518 if (pn.count("Alphabetic")) {
519 combined += pn.at("Alphabetic").as_string();
520 }
521 } else if (values[i].is_string()) {
522 combined += values[i].as_string();
523 }
524 }
525 return dicom_element::from_string(tag, vr, combined);
526 }
527
528 // Handle string VRs
529 if (is_string_vr(vr)) {
530 std::string combined;
531 for (size_t i = 0; i < values.size(); ++i) {
532 if (i > 0) combined += "\\";
533 if (values[i].is_string()) {
534 combined += values[i].as_string();
535 } else if (values[i].is_number()) {
536 // Some string VRs like IS, DS contain numeric values
537 std::ostringstream oss;
538 oss << std::setprecision(17) << values[i].as_number();
539 combined += oss.str();
540 }
541 }
542 return dicom_element::from_string(tag, vr, combined);
543 }
544
545 // Handle numeric VRs
546 if (is_numeric_vr(vr)) {
547 std::vector<uint8_t> data;
548
549 auto write_values = [&]<typename T>() {
550 data.reserve(values.size() * sizeof(T));
551 for (const auto& v : values) {
552 T num{};
553 if (v.is_number()) {
554 num = static_cast<T>(v.as_number());
555 } else if (v.is_string()) {
556 // Handle string representation of numbers
557 if constexpr (std::is_floating_point_v<T>) {
558 num = static_cast<T>(std::stod(v.as_string()));
559 } else {
560 num = static_cast<T>(std::stoll(v.as_string()));
561 }
562 }
563 const uint8_t* ptr = reinterpret_cast<const uint8_t*>(&num);
564 data.insert(data.end(), ptr, ptr + sizeof(T));
565 }
566 };
567
568 switch (vr) {
569 case vr_type::US: write_values.template operator()<uint16_t>(); break;
570 case vr_type::SS: write_values.template operator()<int16_t>(); break;
571 case vr_type::UL: write_values.template operator()<uint32_t>(); break;
572 case vr_type::SL: write_values.template operator()<int32_t>(); break;
573 case vr_type::FL: write_values.template operator()<float>(); break;
574 case vr_type::FD: write_values.template operator()<double>(); break;
575 case vr_type::UV: write_values.template operator()<uint64_t>(); break;
576 case vr_type::SV: write_values.template operator()<int64_t>(); break;
577 default: break;
578 }
579
580 return dicom_element{tag, vr, std::span<const uint8_t>(data)};
581 }
582
583 // Handle AT (Attribute Tag)
584 if (vr == vr_type::AT) {
585 std::vector<uint8_t> data;
586 for (const auto& v : values) {
587 if (v.is_string()) {
588 auto tag_opt = parse_tag(v.as_string());
589 if (tag_opt) {
590 uint16_t group = tag_opt->group();
591 uint16_t elem = tag_opt->element();
592 data.push_back(static_cast<uint8_t>(group & 0xFF));
593 data.push_back(static_cast<uint8_t>((group >> 8) & 0xFF));
594 data.push_back(static_cast<uint8_t>(elem & 0xFF));
595 data.push_back(static_cast<uint8_t>((elem >> 8) & 0xFF));
596 }
597 }
598 }
599 return dicom_element{tag, vr, std::span<const uint8_t>(data)};
600 }
601
602 // Fallback: treat as string
603 if (values[0].is_string()) {
604 std::string combined;
605 for (size_t i = 0; i < values.size(); ++i) {
606 if (i > 0) combined += "\\";
607 combined += values[i].as_string();
608 }
609 return dicom_element::from_string(tag, vr, combined);
610 }
611
612 return dicom_element{tag, vr};
613}
614
621void parse_dataset(const json_object& json_obj,
623 const options& opts) {
624 for (const auto& [key, value] : json_obj) {
625 // Skip non-tag keys (e.g., metadata fields)
626 auto tag_opt = parse_tag(key);
627 if (!tag_opt) {
628 continue;
629 }
630
631 try {
632 auto element = create_element(*tag_opt, value, opts);
633 dataset.insert(std::move(element));
634 } catch (const std::exception& e) {
635 if (opts.verbose) {
636 std::cerr << "Warning: Failed to parse element " << key
637 << ": " << e.what() << "\n";
638 }
639 }
640 }
641}
642
647void print_usage(const char* program_name) {
648 std::cout << R"(
649JSON to DICOM Converter (DICOM PS3.18)
650
651Usage: )" << program_name
652 << R"( <json-file> <output-dcm> [options]
653
654Arguments:
655 json-file Input JSON file (DICOM PS3.18 format)
656 output-dcm Output DICOM file
657
658Options:
659 -h, --help Show this help message
660 -t, --transfer-syntax Transfer Syntax UID (default: Explicit VR Little Endian)
661 --template <dcm> Template DICOM file (copies pixel data and missing tags)
662 --bulk-data-dir <dir> Directory for BulkDataURI resolution
663 -v, --verbose Verbose output
664 -q, --quiet Quiet mode (errors only)
665
666Transfer Syntax Options:
667 1.2.840.10008.1.2 Implicit VR Little Endian
668 1.2.840.10008.1.2.1 Explicit VR Little Endian (default)
669 1.2.840.10008.1.2.2 Explicit VR Big Endian
670
671Examples:
672 )" << program_name
673 << R"( metadata.json output.dcm
674 )" << program_name
675 << R"( metadata.json output.dcm --template original.dcm
676 )" << program_name
677 << R"( metadata.json output.dcm --bulk-data-dir ./bulk/
678 )" << program_name
679 << R"( metadata.json output.dcm -t 1.2.840.10008.1.2
680
681Input Format (DICOM PS3.18 JSON):
682 {
683 "00100010": {
684 "vr": "PN",
685 "Value": [{"Alphabetic": "DOE^JOHN"}]
686 },
687 "00100020": {
688 "vr": "LO",
689 "Value": ["12345678"]
690 }
691 }
692
693Exit Codes:
694 0 Success
695 1 Invalid arguments
696 2 File error or invalid JSON
697)";
698}
699
707bool parse_arguments(int argc, char* argv[], options& opts) {
708 if (argc < 3) {
709 return false;
710 }
711
712 for (int i = 1; i < argc; ++i) {
713 std::string arg = argv[i];
714
715 if (arg == "--help" || arg == "-h") {
716 return false;
717 } else if ((arg == "--transfer-syntax" || arg == "-t") && i + 1 < argc) {
718 opts.transfer_syntax = argv[++i];
719 } else if (arg == "--template" && i + 1 < argc) {
720 opts.template_path = argv[++i];
721 } else if (arg == "--bulk-data-dir" && i + 1 < argc) {
722 opts.bulk_data_dir = argv[++i];
723 } else if (arg == "--verbose" || arg == "-v") {
724 opts.verbose = true;
725 } else if (arg == "--quiet" || arg == "-q") {
726 opts.quiet = true;
727 } else if (arg[0] == '-') {
728 std::cerr << "Error: Unknown option '" << arg << "'\n";
729 return false;
730 } else if (opts.input_path.empty()) {
731 opts.input_path = arg;
732 } else if (opts.output_path.empty()) {
733 opts.output_path = arg;
734 } else {
735 std::cerr << "Error: Too many arguments\n";
736 return false;
737 }
738 }
739
740 if (opts.input_path.empty()) {
741 std::cerr << "Error: No input file specified\n";
742 return false;
743 }
744
745 if (opts.output_path.empty()) {
746 std::cerr << "Error: No output file specified\n";
747 return false;
748 }
749
750 if (opts.quiet) {
751 opts.verbose = false;
752 }
753
754 return true;
755}
756
762int convert_file(const options& opts) {
763 using namespace kcenon::pacs::core;
764 using namespace kcenon::pacs::encoding;
765
766 // Read and parse JSON
767 std::string json_content;
768 try {
769 json_content = read_file(opts.input_path);
770 } catch (const std::exception& e) {
771 std::cerr << "Error: " << e.what() << "\n";
772 return 2;
773 }
774
775 json_value json;
776 try {
777 json_parser parser(json_content);
778 json = parser.parse();
779 } catch (const std::exception& e) {
780 std::cerr << "Error: Failed to parse JSON: " << e.what() << "\n";
781 return 2;
782 }
783
784 if (!json.is_object()) {
785 std::cerr << "Error: JSON root must be an object\n";
786 return 2;
787 }
788
789 // Create dataset from JSON
790 dicom_dataset dataset;
791 try {
792 parse_dataset(json.as_object(), dataset, opts);
793 } catch (const std::exception& e) {
794 std::cerr << "Error: Failed to create dataset: " << e.what() << "\n";
795 return 2;
796 }
797
798 // Load template if specified
799 std::optional<dicom_file> template_file;
800 if (!opts.template_path.empty()) {
801 auto result = dicom_file::open(opts.template_path);
802 if (result.is_err()) {
803 std::cerr << "Error: Failed to open template file: "
804 << result.error().message << "\n";
805 return 2;
806 }
807 template_file = std::move(result.value());
808
809 // Merge template data (JSON values override template)
810 for (const auto& [tag, element] : template_file->dataset()) {
811 if (dataset.get(tag) == nullptr) {
812 // Copy from template if not in JSON
813 dataset.insert(element);
814 }
815 }
816 }
817
818 // Determine transfer syntax
819 transfer_syntax ts = transfer_syntax::explicit_vr_little_endian;
820 if (!opts.transfer_syntax.empty()) {
821 auto ts_opt = find_transfer_syntax(opts.transfer_syntax);
822 if (ts_opt) {
823 ts = *ts_opt;
824 } else {
825 std::cerr << "Warning: Unknown transfer syntax '" << opts.transfer_syntax
826 << "', using Explicit VR Little Endian\n";
827 }
828 } else if (template_file) {
829 ts = template_file->transfer_syntax();
830 }
831
832 // Create DICOM file
833 auto file = dicom_file::create(dataset, ts);
834
835 // Save to output
836 auto save_result = file.save(opts.output_path);
837 if (save_result.is_err()) {
838 std::cerr << "Error: Failed to save DICOM file: "
839 << save_result.error().message << "\n";
840 return 2;
841 }
842
843 if (!opts.quiet) {
844 std::cout << "Successfully converted: " << opts.input_path.string()
845 << " -> " << opts.output_path.string() << "\n";
846 }
847
848 return 0;
849}
850
851} // namespace
852
853int main(int argc, char* argv[]) {
854 options opts;
855
856 if (!parse_arguments(argc, argv, opts)) {
857 std::cout << R"(
858 _ ____ ___ _ _ _____ ___ ____ ____ __ __
859 | / ___| / _ \| \ | | |_ _|/ _ \ | _ \ / ___| \/ |
860 _ | \___ \| | | | \| | | | | | | | | | | | | | |\/| |
861 | |_| |___) | |_| | |\ | | | | |_| | | |_| | |___| | | |
862 \___/|____/ \___/|_| \_| |_| \___/ |____/ \____|_| |_|
863
864 JSON to DICOM Converter (PS3.18)
865)" << "\n";
866 print_usage(argv[0]);
867 return 1;
868 }
869
870 // Check if input path exists
871 if (!std::filesystem::exists(opts.input_path)) {
872 std::cerr << "Error: Input file does not exist: " << opts.input_path.string() << "\n";
873 return 2;
874 }
875
876 // Show banner in non-quiet mode
877 if (!opts.quiet) {
878 std::cout << R"(
879 _ ____ ___ _ _ _____ ___ ____ ____ __ __
880 | / ___| / _ \| \ | | |_ _|/ _ \ | _ \ / ___| \/ |
881 _ | \___ \| | | | \| | | | | | | | | | | | | | |\/| |
882 | |_| |___) | |_| | |\ | | | | |_| | | |_| | |___| | | |
883 \___/|____/ \___/|_| \_| |_| \___/ |____/ \____|_| |_|
884
885 JSON to DICOM Converter (PS3.18)
886)" << "\n";
887 }
888
889 return convert_file(opts);
890}
void insert(dicom_element element)
Insert or replace an element in the dataset.
DICOM Data Dictionary for tag metadata lookup.
DICOM Part 10 file handling for reading/writing DICOM files.
Compile-time constants for commonly used DICOM tags.
size_t pos_
int main()
Definition main.cpp:84
std::optional< transfer_syntax > find_transfer_syntax(std::string_view uid)
Looks up a Transfer Syntax by its UID.
constexpr bool is_numeric_vr(vr_type vr) noexcept
Checks if a VR is a numeric type.
Definition vr_type.h:214
vr_type
DICOM Value Representation (VR) types.
Definition vr_type.h:29
constexpr bool is_string_vr(vr_type vr) noexcept
Checks if a VR is a string type.
Definition vr_type.h:175
@ get
C-GET retrieve request/response.
Transfer Syntax UIDs.
Definition main.cpp:78
std::string_view code
vr_encoding vr