PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
dicom_tag.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
11
12#include <array>
13#include <charconv>
14#include <iomanip>
15#include <sstream>
16
17namespace kcenon::pacs::core {
18
19namespace {
20
26constexpr auto hex_char_to_int(char ch) noexcept -> int {
27 if (ch >= '0' && ch <= '9') {
28 return ch - '0';
29 }
30 if (ch >= 'A' && ch <= 'F') {
31 return ch - 'A' + 10;
32 }
33 if (ch >= 'a' && ch <= 'f') {
34 return ch - 'a' + 10;
35 }
36 return -1;
37}
38
44auto parse_hex4(const char* str) noexcept -> std::optional<uint16_t> {
45 uint16_t result = 0;
46 for (int i = 0; i < 4; ++i) {
47 const int digit = hex_char_to_int(str[i]);
48 if (digit < 0) {
49 return std::nullopt;
50 }
51 result = static_cast<uint16_t>((result << 4) | digit);
52 }
53 return result;
54}
55
56} // namespace
57
58auto dicom_tag::from_string(std::string_view str) -> std::optional<dicom_tag> {
59 // Remove leading/trailing whitespace
60 while (!str.empty() && (str.front() == ' ' || str.front() == '\t')) {
61 str.remove_prefix(1);
62 }
63 while (!str.empty() && (str.back() == ' ' || str.back() == '\t')) {
64 str.remove_suffix(1);
65 }
66
67 if (str.empty()) {
68 return std::nullopt;
69 }
70
71 // Format 1: "(GGGG,EEEE)"
72 if (str.size() == 11 && str.front() == '(' && str.back() == ')' &&
73 str[5] == ',') {
74 const auto group = parse_hex4(str.data() + 1);
75 const auto element = parse_hex4(str.data() + 6);
76
77 if (group && element) {
78 return dicom_tag{*group, *element};
79 }
80 return std::nullopt;
81 }
82
83 // Format 2: "GGGGEEEE" (8 hex digits)
84 if (str.size() == 8) {
85 const auto group = parse_hex4(str.data());
86 const auto element = parse_hex4(str.data() + 4);
87
88 if (group && element) {
89 return dicom_tag{*group, *element};
90 }
91 return std::nullopt;
92 }
93
94 // Format 3: "GGGG,EEEE" (without parentheses)
95 if (str.size() == 9 && str[4] == ',') {
96 const auto group = parse_hex4(str.data());
97 const auto element = parse_hex4(str.data() + 5);
98
99 if (group && element) {
100 return dicom_tag{*group, *element};
101 }
102 return std::nullopt;
103 }
104
105 return std::nullopt;
106}
107
108auto dicom_tag::to_string() const -> std::string {
109 // Pre-allocate exact size: "(" + 4 hex + "," + 4 hex + ")" = 11 chars
110 std::string result;
111 result.reserve(11);
112
113 // Use lookup table for hex conversion (faster than std::format)
114 static constexpr std::array<char, 16> hex_chars = {
115 '0', '1', '2', '3', '4', '5', '6', '7',
116 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
117
118 const auto grp = group();
119 const auto elem = element();
120
121 result += '(';
122 result += hex_chars[(grp >> 12) & 0xF];
123 result += hex_chars[(grp >> 8) & 0xF];
124 result += hex_chars[(grp >> 4) & 0xF];
125 result += hex_chars[grp & 0xF];
126 result += ',';
127 result += hex_chars[(elem >> 12) & 0xF];
128 result += hex_chars[(elem >> 8) & 0xF];
129 result += hex_chars[(elem >> 4) & 0xF];
130 result += hex_chars[elem & 0xF];
131 result += ')';
132
133 return result;
134}
135
136} // namespace kcenon::pacs::core
static auto from_string(std::string_view str) -> std::optional< dicom_tag >
Parse tag from string representation.
Definition dicom_tag.cpp:58
auto to_string() const -> std::string
Convert to string representation.
constexpr auto group() const noexcept -> uint16_t
Get the group number.
Definition dicom_tag.h:90
constexpr auto element() const noexcept -> uint16_t
Get the element number.
Definition dicom_tag.h:98
DICOM Tag representation (Group, Element pairs)