PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
vr_info.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
6
7#include <algorithm>
8#include <array>
9#include <cctype>
10#include <unordered_map>
11
12namespace kcenon::pacs::encoding {
13
14namespace {
15
16// Static VR properties table
17// DICOM PS3.5 Table 6.2-1 defines these properties
18constexpr std::array<vr_info, 31> vr_table = {{
19 // String VRs
20 {vr_type::AE, "Application Entity", 16, ' ', false, 0},
21 {vr_type::AS, "Age String", 4, ' ', true, 4},
22 {vr_type::CS, "Code String", 16, ' ', false, 0},
23 {vr_type::DA, "Date", 8, ' ', true, 8},
24 {vr_type::DS, "Decimal String", 16, ' ', false, 0},
25 {vr_type::DT, "Date Time", 26, ' ', false, 0},
26 {vr_type::IS, "Integer String", 12, ' ', false, 0},
27 {vr_type::LO, "Long String", 64, ' ', false, 0},
28 {vr_type::LT, "Long Text", 10240, ' ', false, 0},
29 {vr_type::PN, "Person Name", 324, ' ', false, 0},
30 {vr_type::SH, "Short String", 16, ' ', false, 0},
31 {vr_type::ST, "Short Text", 1024, ' ', false, 0},
32 {vr_type::TM, "Time", 14, ' ', false, 0},
33 {vr_type::UC, "Unlimited Characters", 0xFFFFFFFE, ' ', false, 0},
34 {vr_type::UI, "Unique Identifier", 64, '\0', false, 0},
35 {vr_type::UR, "Universal Resource Identifier", 0xFFFFFFFE, ' ', false, 0},
36 {vr_type::UT, "Unlimited Text", 0xFFFFFFFE, ' ', false, 0},
37
38 // Numeric VRs (binary encoded, fixed length per value)
39 {vr_type::FL, "Floating Point Single", 4, '\0', true, 4},
40 {vr_type::FD, "Floating Point Double", 8, '\0', true, 8},
41 {vr_type::SL, "Signed Long", 4, '\0', true, 4},
42 {vr_type::SS, "Signed Short", 2, '\0', true, 2},
43 {vr_type::SV, "Signed 64-bit Very Long", 8, '\0', true, 8},
44 {vr_type::UL, "Unsigned Long", 4, '\0', true, 4},
45 {vr_type::US, "Unsigned Short", 2, '\0', true, 2},
46 {vr_type::UV, "Unsigned 64-bit Very Long", 8, '\0', true, 8},
47
48 // Binary VRs (raw bytes, variable length)
49 {vr_type::OB, "Other Byte", 0xFFFFFFFE, '\0', false, 0},
50 {vr_type::OD, "Other Double", 0xFFFFFFFE, '\0', false, 0},
51 {vr_type::OF, "Other Float", 0xFFFFFFFE, '\0', false, 0},
52 {vr_type::OL, "Other Long", 0xFFFFFFFE, '\0', false, 0},
53 {vr_type::OV, "Other 64-bit Very Long", 0xFFFFFFFE, '\0', false, 0},
54 {vr_type::OW, "Other Word", 0xFFFFFFFE, '\0', false, 0},
55}};
56
57// Additional VR info for special types
58constexpr vr_info at_info = {vr_type::AT, "Attribute Tag", 4, '\0', true, 4};
59constexpr vr_info sq_info = {vr_type::SQ, "Sequence of Items", 0xFFFFFFFF, '\0', false, 0};
60constexpr vr_info un_info = {vr_type::UN, "Unknown", 0xFFFFFFFE, '\0', false, 0};
61
62// Build lookup map at runtime
63const std::unordered_map<vr_type, const vr_info*>& get_vr_map() {
64 static const auto map = []() {
65 std::unordered_map<vr_type, const vr_info*> m;
66 for (const auto& info : vr_table) {
67 m[info.type] = &info;
68 }
69 m[vr_type::AT] = &at_info;
70 m[vr_type::SQ] = &sq_info;
71 m[vr_type::UN] = &un_info;
72 return m;
73 }();
74 return map;
75}
76
77// Character validation helpers
78bool is_cs_char(char c) {
79 return std::isupper(static_cast<unsigned char>(c)) ||
80 std::isdigit(static_cast<unsigned char>(c)) ||
81 c == ' ' || c == '_';
82}
83
84bool is_da_char(char c) {
85 return std::isdigit(static_cast<unsigned char>(c));
86}
87
88bool is_tm_char(char c) {
89 return std::isdigit(static_cast<unsigned char>(c)) ||
90 c == '.' || c == ':';
91}
92
93bool is_ui_char(char c) {
94 return std::isdigit(static_cast<unsigned char>(c)) || c == '.';
95}
96
97bool is_ds_char(char c) {
98 return std::isdigit(static_cast<unsigned char>(c)) ||
99 c == '+' || c == '-' || c == '.' ||
100 c == 'E' || c == 'e' || c == ' ';
101}
102
103bool is_is_char(char c) {
104 return std::isdigit(static_cast<unsigned char>(c)) ||
105 c == '+' || c == '-';
106}
107
108bool is_as_char(char c) {
109 return std::isdigit(static_cast<unsigned char>(c)) ||
110 c == 'D' || c == 'W' || c == 'M' || c == 'Y';
111}
112
113bool is_dt_char(char c) {
114 return std::isdigit(static_cast<unsigned char>(c)) ||
115 c == '.' || c == '+' || c == '-';
116}
117
118bool is_printable(char c) {
119 auto uc = static_cast<unsigned char>(c);
120 // Allow printable ASCII (0x20-0x7E), CR, LF, FF, TAB for text VRs,
121 // ESC (0x1B) for ISO 2022 escape sequences, and high bytes (0x80+)
122 // for multi-byte CJK encodings (EUC-KR, GB2312, ISO-2022-JP, etc.)
123 return (uc >= 0x20 && uc <= 0x7E) ||
124 uc >= 0x80 ||
125 uc == 0x1B ||
126 c == '\r' || c == '\n' || c == '\f' || c == '\t';
127}
128
129} // namespace
130
132 const auto& map = get_vr_map();
133 auto it = map.find(vr);
134 if (it != map.end()) {
135 return *it->second;
136 }
137 // Return UN info for unknown VR types
138 return un_info;
139}
140
141bool validate_value(vr_type vr, std::span<const uint8_t> data) {
142 const auto& info = get_vr_info(vr);
143
144 // For fixed-length VRs, check that size is a multiple of fixed_size (VM >= 1)
145 if (info.is_fixed_length && info.fixed_size > 0) {
146 if (data.size() % info.fixed_size != 0) {
147 return false;
148 }
149 // Fixed-length VRs pass validation if size is correct multiple
150 return true;
151 }
152
153 // Check maximum length for variable-length VRs (0xFFFFFFFE means unlimited)
154 if (info.max_length != 0xFFFFFFFE && info.max_length != 0xFFFFFFFF) {
155 if (data.size() > info.max_length) {
156 return false;
157 }
158 }
159
160 // String VRs need character validation
161 if (is_string_vr(vr)) {
162 std::string_view str_value(reinterpret_cast<const char*>(data.data()),
163 data.size());
164 return is_valid_charset(vr, str_value);
165 }
166
167 return true;
168}
169
170bool validate_string(vr_type vr, std::string_view value) {
171 const auto& info = get_vr_info(vr);
172
173 // Non-string VRs cannot be validated as strings
174 if (!is_string_vr(vr)) {
175 return false;
176 }
177
178 // Check maximum length
179 if (info.max_length != 0xFFFFFFFE) {
180 if (value.size() > info.max_length) {
181 return false;
182 }
183 }
184
185 // Fixed-length string VRs (AS, DA) must match exact length
186 if (info.is_fixed_length && info.fixed_size > 0) {
187 if (value.size() != info.fixed_size) {
188 return false;
189 }
190 }
191
192 // Validate character set
193 return is_valid_charset(vr, value);
194}
195
196std::vector<uint8_t> pad_to_even(vr_type vr, std::span<const uint8_t> data) {
197 std::vector<uint8_t> result(data.begin(), data.end());
198
199 if (result.size() % 2 != 0) {
200 const auto& info = get_vr_info(vr);
201 result.push_back(static_cast<uint8_t>(info.padding_char));
202 }
203
204 return result;
205}
206
207std::string trim_padding(vr_type vr, std::string_view value) {
208 if (value.empty()) {
209 return std::string{};
210 }
211
212 const auto& info = get_vr_info(vr);
213 char pad = info.padding_char;
214
215 // Find the last non-padding character
216 auto end = value.find_last_not_of(pad);
217 if (end == std::string_view::npos) {
218 // All characters are padding
219 return std::string{};
220 }
221
222 return std::string{value.substr(0, end + 1)};
223}
224
225bool is_valid_charset(vr_type vr, std::string_view value) {
226 switch (vr) {
227 case vr_type::CS:
228 // Code String: A-Z, 0-9, space, underscore
229 return std::all_of(value.begin(), value.end(), is_cs_char);
230
231 case vr_type::DA:
232 // Date: YYYYMMDD format, digits only
233 return value.size() == 8 &&
234 std::all_of(value.begin(), value.end(), is_da_char);
235
236 case vr_type::TM:
237 // Time: digits, period, colon
238 return std::all_of(value.begin(), value.end(), is_tm_char);
239
240 case vr_type::UI:
241 // UID: digits and periods only, max 64 chars
242 return value.size() <= 64 &&
243 std::all_of(value.begin(), value.end(), is_ui_char);
244
245 case vr_type::DS:
246 // Decimal String: digits, +, -, ., E, e, space
247 return std::all_of(value.begin(), value.end(), is_ds_char);
248
249 case vr_type::IS:
250 // Integer String: digits, +, -
251 return std::all_of(value.begin(), value.end(), is_is_char);
252
253 case vr_type::AS:
254 // Age String: nnnX format where X is D/W/M/Y
255 if (value.size() != 4) {
256 return false;
257 }
258 return std::all_of(value.begin(), value.end(), is_as_char) &&
259 (value[3] == 'D' || value[3] == 'W' ||
260 value[3] == 'M' || value[3] == 'Y');
261
262 case vr_type::DT:
263 // Date Time: digits, period, +, -
264 return std::all_of(value.begin(), value.end(), is_dt_char);
265
266 case vr_type::AE:
267 case vr_type::LO:
268 case vr_type::SH:
269 case vr_type::PN:
270 // These allow most printable characters but no control chars
271 // except leading/trailing spaces
272 return std::all_of(value.begin(), value.end(), is_printable);
273
274 case vr_type::LT:
275 case vr_type::ST:
276 case vr_type::UT:
277 case vr_type::UC:
278 case vr_type::UR:
279 // Text VRs allow all printable plus CR, LF, FF, TAB
280 return std::all_of(value.begin(), value.end(), is_printable);
281
282 default:
283 // Non-string VRs - charset validation not applicable
284 return true;
285 }
286}
287
288} // namespace kcenon::pacs::encoding
vr_type
DICOM Value Representation (VR) types.
Definition vr_type.h:29
@ OB
Other Byte (variable length)
@ DA
Date (8 chars, format: YYYYMMDD)
@ UN
Unknown (variable length)
@ IS
Integer String (12 chars max)
@ SQ
Sequence of Items (undefined length)
@ UR
Universal Resource Identifier (2^32-2 max)
@ OV
Other 64-bit Very Long (variable length)
@ LO
Long String (64 chars max)
@ DS
Decimal String (16 chars max)
@ DT
Date Time (26 chars max)
@ UL
Unsigned Long (4 bytes)
@ UC
Unlimited Characters (2^32-2 max)
@ UI
Unique Identifier (64 chars max)
@ SL
Signed Long (4 bytes)
@ US
Unsigned Short (2 bytes)
@ OD
Other Double (variable length)
@ PN
Person Name (64 chars max per component group)
@ OF
Other Float (variable length)
@ UT
Unlimited Text (2^32-2 max)
@ CS
Code String (16 chars max, uppercase + digits + space + underscore)
@ OW
Other Word (variable length)
@ AS
Age String (4 chars, format: nnnD/W/M/Y)
@ FD
Floating Point Double (8 bytes)
@ FL
Floating Point Single (4 bytes)
@ TM
Time (14 chars max, format: HHMMSS.FFFFFF)
@ OL
Other Long (variable length)
@ LT
Long Text (10240 chars max)
@ SV
Signed 64-bit Very Long (8 bytes)
@ SS
Signed Short (2 bytes)
@ UV
Unsigned 64-bit Very Long (8 bytes)
@ AE
Application Entity (16 chars max)
@ SH
Short String (16 chars max)
@ ST
Short Text (1024 chars max)
@ AT
Attribute Tag (4 bytes)
constexpr bool is_string_vr(vr_type vr) noexcept
Checks if a VR is a string type.
Definition vr_type.h:175
bool is_valid_charset(vr_type vr, std::string_view value)
Validates that a string uses only allowed characters for its VR.
Definition vr_info.cpp:225
std::vector< uint8_t > pad_to_even(vr_type vr, std::span< const uint8_t > data)
Pads data to even length as required by DICOM.
Definition vr_info.cpp:196
bool validate_string(vr_type vr, std::string_view value)
Validates a string value against VR encoding rules.
Definition vr_info.cpp:170
bool validate_value(vr_type vr, std::span< const uint8_t > data)
Validates binary data against VR encoding rules.
Definition vr_info.cpp:141
std::string trim_padding(vr_type vr, std::string_view value)
Removes trailing padding characters from a string value.
Definition vr_info.cpp:207
const vr_info & get_vr_info(vr_type vr)
Retrieves comprehensive metadata for a VR type.
Definition vr_info.cpp:131
Metadata structure containing comprehensive VR properties.
Definition vr_info.h:32
vr_encoding vr