PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
container_adapter.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 <charconv>
14#include <sstream>
15
17
18// =============================================================================
19// VR to Container Value Mapping
20// =============================================================================
21
23 -> container_module::optimized_value {
24 container_module::optimized_value result;
25 const auto vr = element.vr();
26 const auto tag = element.tag();
27
28 // Use tag string as the value name
29 result.name = make_element_key(tag, vr);
30
31 // Handle empty elements
32 // For string VRs, keep as empty string; for others, treat as null
33 if (element.is_empty()) {
35 result.type = container_module::value_types::string_value;
36 result.data = std::string{};
37 } else {
38 result.type = container_module::value_types::null_value;
39 result.data = std::monostate{};
40 }
41 return result;
42 }
43
44 // Handle sequence type specially
45 if (element.is_sequence()) {
46 result.type = container_module::value_types::container_value;
47 result.data = sequence_to_container(element);
48 return result;
49 }
50
51 // Map VR to appropriate container type
53 result.type = container_module::value_types::string_value;
54 result.data = element.as_string().unwrap_or("");
55 } else if (encoding::is_numeric_vr(vr)) {
56 // Map numeric VRs to appropriate types
57 // Use fallback to bytes if numeric conversion fails
58 bool converted = false;
59 switch (vr) {
60 case encoding::vr_type::SS: { // Signed Short (2 bytes)
61 auto val = element.as_numeric<short>();
62 if (val.is_ok()) {
63 result.type = container_module::value_types::short_value;
64 result.data = val.value();
65 converted = true;
66 }
67 break;
68 }
69 case encoding::vr_type::US: { // Unsigned Short (2 bytes)
70 auto val = element.as_numeric<unsigned short>();
71 if (val.is_ok()) {
72 result.type = container_module::value_types::ushort_value;
73 result.data = val.value();
74 converted = true;
75 }
76 break;
77 }
78 case encoding::vr_type::SL: { // Signed Long (4 bytes)
79 auto val = element.as_numeric<int>();
80 if (val.is_ok()) {
81 result.type = container_module::value_types::int_value;
82 result.data = val.value();
83 converted = true;
84 }
85 break;
86 }
87 case encoding::vr_type::UL: { // Unsigned Long (4 bytes)
88 auto val = element.as_numeric<unsigned int>();
89 if (val.is_ok()) {
90 result.type = container_module::value_types::uint_value;
91 result.data = val.value();
92 converted = true;
93 }
94 break;
95 }
96 case encoding::vr_type::SV: { // Signed 64-bit
97 auto val = element.as_numeric<long long>();
98 if (val.is_ok()) {
99 result.type = container_module::value_types::llong_value;
100 result.data = val.value();
101 converted = true;
102 }
103 break;
104 }
105 case encoding::vr_type::UV: { // Unsigned 64-bit
106 auto val = element.as_numeric<unsigned long long>();
107 if (val.is_ok()) {
108 result.type = container_module::value_types::ullong_value;
109 result.data = val.value();
110 converted = true;
111 }
112 break;
113 }
114 case encoding::vr_type::FL: { // Float
115 auto val = element.as_numeric<float>();
116 if (val.is_ok()) {
117 result.type = container_module::value_types::float_value;
118 result.data = val.value();
119 converted = true;
120 }
121 break;
122 }
123 case encoding::vr_type::FD: { // Double
124 auto val = element.as_numeric<double>();
125 if (val.is_ok()) {
126 result.type = container_module::value_types::double_value;
127 result.data = val.value();
128 converted = true;
129 }
130 break;
131 }
132 default:
133 break;
134 }
135 if (!converted) {
136 // Fallback to bytes for unhandled or failed numeric types
137 result.type = container_module::value_types::bytes_value;
138 auto raw = element.raw_data();
139 result.data = std::vector<uint8_t>(raw.begin(), raw.end());
140 }
141 } else if (encoding::is_binary_vr(vr)) {
142 result.type = container_module::value_types::bytes_value;
143 auto raw = element.raw_data();
144 result.data = std::vector<uint8_t>(raw.begin(), raw.end());
145 } else if (vr == encoding::vr_type::AT) {
146 // Attribute Tag - store as 32-bit unsigned
147 auto val = element.as_numeric<unsigned int>();
148 if (val.is_ok()) {
149 result.type = container_module::value_types::uint_value;
150 result.data = val.value();
151 } else {
152 result.type = container_module::value_types::bytes_value;
153 auto raw = element.raw_data();
154 result.data = std::vector<uint8_t>(raw.begin(), raw.end());
155 }
156 } else {
157 // Default: store as bytes
158 result.type = container_module::value_types::bytes_value;
159 auto raw = element.raw_data();
160 result.data = std::vector<uint8_t>(raw.begin(), raw.end());
161 }
162
163 return result;
164}
165
167 core::dicom_tag tag,
169 const container_module::optimized_value& val) -> core::dicom_element {
170 // Handle null/empty values
171 if (val.type == container_module::value_types::null_value) {
172 return core::dicom_element{tag, vr};
173 }
174
175 // Handle sequence type
176 if (vr == encoding::vr_type::SQ) {
177 auto elem = core::dicom_element{tag, vr};
178 if (std::holds_alternative<std::shared_ptr<container_module::value_container>>(
179 val.data)) {
180 auto container = std::get<std::shared_ptr<container_module::value_container>>(val.data);
181 if (container) {
182 auto items = container_to_sequence(*container);
183 for (auto& item : items) {
184 elem.sequence_items().push_back(std::move(item));
185 }
186 }
187 }
188 return elem;
189 }
190
191 // Handle string VRs
193 std::string str_val;
194 if (std::holds_alternative<std::string>(val.data)) {
195 str_val = std::get<std::string>(val.data);
196 } else {
197 str_val = container_module::variant_helpers::to_string(val.data, val.type);
198 }
199 return core::dicom_element::from_string(tag, vr, str_val);
200 }
201
202 // Handle numeric VRs
204 switch (vr) {
206 if (std::holds_alternative<short>(val.data)) {
208 std::get<short>(val.data));
209 }
210 break;
212 if (std::holds_alternative<unsigned short>(val.data)) {
214 std::get<unsigned short>(val.data));
215 }
216 break;
218 if (std::holds_alternative<int>(val.data)) {
220 std::get<int>(val.data));
221 }
222 break;
224 if (std::holds_alternative<unsigned int>(val.data)) {
226 std::get<unsigned int>(val.data));
227 }
228 break;
230 if (std::holds_alternative<long long>(val.data)) {
232 std::get<long long>(val.data));
233 }
234 break;
236 if (std::holds_alternative<unsigned long long>(val.data)) {
238 std::get<unsigned long long>(val.data));
239 }
240 break;
242 if (std::holds_alternative<float>(val.data)) {
244 std::get<float>(val.data));
245 }
246 break;
248 if (std::holds_alternative<double>(val.data)) {
250 std::get<double>(val.data));
251 }
252 break;
253 default:
254 break;
255 }
256 }
257
258 // Handle binary VRs or fallback
259 if (std::holds_alternative<std::vector<uint8_t>>(val.data)) {
260 auto& bytes = std::get<std::vector<uint8_t>>(val.data);
261 return core::dicom_element{tag, vr, bytes};
262 }
263
264 // Return empty element as last resort
265 return core::dicom_element{tag, vr};
266}
267
268// =============================================================================
269// Dataset Serialization
270// =============================================================================
271
273 -> std::shared_ptr<container_module::value_container> {
274 auto container = std::make_shared<container_module::value_container>();
275
276 // Add metadata
277 container->set(kVersionKey, std::string(kProtocolVersion));
278 container->set(kElementCountKey, static_cast<unsigned int>(dataset.size()));
279
280 // Serialize each element
281 for (const auto& [tag, element] : dataset) {
282 auto value = to_container_value(element);
283 container->set(value);
284 }
285
286 return container;
287}
288
290 const container_module::value_container& container)
292 core::dicom_dataset dataset;
293
294 // Process each value in the container
295 for (const auto& val : container) {
296 // Skip metadata keys
297 if (val.name.starts_with("_")) {
298 continue;
299 }
300
301 // Parse element key to get tag and VR
302 auto parsed = parse_element_key(val.name);
303 if (!parsed) {
304 continue; // Skip invalid keys
305 }
306
307 auto [tag, vr] = *parsed;
308
309 // Convert value back to DICOM element
310 try {
311 auto element = from_container_value(tag, vr, val);
312 dataset.insert(std::move(element));
313 } catch (const std::exception& e) {
315 kcenon::common::error_info{
316 kcenon::pacs::compat::format("Failed to convert element {}: {}", val.name, e.what())
317 });
318 }
319 }
320
321 return Result<core::dicom_dataset>::ok(std::move(dataset));
322}
323
324// =============================================================================
325// Binary Serialization
326// =============================================================================
327
329 -> std::vector<uint8_t> {
330 auto container = serialize_dataset(dataset);
331 auto result = container->serialize(
332 container_module::value_container::serialization_format::binary);
333 if (result.is_err()) {
334 return {};
335 }
336 return result.value();
337}
338
339auto container_adapter::from_binary(std::span<const uint8_t> data)
341 container_module::value_container container;
342
343 auto deserialize_result = container.deserialize(data);
344 if (deserialize_result.is_err()) {
346 kcenon::common::error_info{
347 "Failed to deserialize binary data to container"
348 });
349 }
350
351 return deserialize_dataset(container);
352}
353
354// =============================================================================
355// Utility Functions
356// =============================================================================
357
359 -> container_module::value_types {
361 return container_module::value_types::string_value;
362 }
363
364 switch (vr) {
366 return container_module::value_types::short_value;
368 return container_module::value_types::ushort_value;
370 return container_module::value_types::int_value;
373 return container_module::value_types::uint_value;
375 return container_module::value_types::llong_value;
377 return container_module::value_types::ullong_value;
379 return container_module::value_types::float_value;
381 return container_module::value_types::double_value;
383 return container_module::value_types::container_value;
384 default:
385 return container_module::value_types::bytes_value;
386 }
387}
388
389// =============================================================================
390// Private Helper Functions
391// =============================================================================
392
395 -> std::string {
396 return kcenon::pacs::compat::format("{:04X},{:04X}:{}", tag.group(), tag.element(),
398}
399
400auto container_adapter::parse_element_key(std::string_view key)
401 -> std::optional<std::pair<core::dicom_tag, encoding::vr_type>> {
402 // Expected format: "GGGG,EEEE:VR"
403 if (key.size() < 12) { // Minimum: "0000,0000:XX"
404 return std::nullopt;
405 }
406
407 // Find the colon separator
408 auto colon_pos = key.find(':');
409 if (colon_pos == std::string_view::npos || colon_pos < 9) {
410 return std::nullopt;
411 }
412
413 // Parse group (first 4 hex chars)
414 uint16_t group = 0;
415 auto group_str = key.substr(0, 4);
416 auto group_result = std::from_chars(group_str.data(),
417 group_str.data() + group_str.size(),
418 group, 16);
419 if (group_result.ec != std::errc{}) {
420 return std::nullopt;
421 }
422
423 // Parse element (chars 5-8, after comma)
424 uint16_t element = 0;
425 auto elem_str = key.substr(5, 4);
426 auto elem_result = std::from_chars(elem_str.data(),
427 elem_str.data() + elem_str.size(),
428 element, 16);
429 if (elem_result.ec != std::errc{}) {
430 return std::nullopt;
431 }
432
433 // Parse VR (2 chars after colon)
434 auto vr_str = key.substr(colon_pos + 1, 2);
435 auto vr_opt = encoding::from_string(vr_str);
436 if (!vr_opt) {
437 return std::nullopt;
438 }
439
440 return std::make_pair(core::dicom_tag{group, element}, *vr_opt);
441}
442
444 -> std::shared_ptr<container_module::value_container> {
445 auto container = std::make_shared<container_module::value_container>();
446
447 const auto& items = element.sequence_items();
448 container->set("_item_count", static_cast<unsigned int>(items.size()));
449
450 size_t item_index = 0;
451 for (const auto& item : items) {
452 auto item_container = serialize_dataset(item);
453 container_module::optimized_value val;
454 val.name = kcenon::pacs::compat::format("item_{}", item_index++);
455 val.type = container_module::value_types::container_value;
456 val.data = item_container;
457 container->set(val);
458 }
459
460 return container;
461}
462
464 const container_module::value_container& container)
465 -> std::vector<core::dicom_dataset> {
466 std::vector<core::dicom_dataset> items;
467
468 for (const auto& val : container) {
469 if (!val.name.starts_with("item_")) {
470 continue;
471 }
472
473 if (std::holds_alternative<std::shared_ptr<container_module::value_container>>(
474 val.data)) {
475 auto item_container =
476 std::get<std::shared_ptr<container_module::value_container>>(val.data);
477 if (item_container) {
478 auto result = deserialize_dataset(*item_container);
479 if (result.is_ok()) {
480 items.push_back(std::move(result.value()));
481 }
482 }
483 }
484 }
485
486 return items;
487}
488
489} // namespace kcenon::pacs::integration
void insert(dicom_element element)
Insert or replace an element in the dataset.
static auto from_string(dicom_tag tag, encoding::vr_type vr, std::string_view value) -> dicom_element
Create an element from a string value.
static auto from_numeric(dicom_tag tag, encoding::vr_type vr, T value) -> dicom_element
Create an element from a numeric value.
static auto serialize_dataset(const core::dicom_dataset &dataset) -> std::shared_ptr< container_module::value_container >
Serialize a DICOM dataset to a value_container.
static auto from_binary(std::span< const uint8_t > data) -> Result< core::dicom_dataset >
Deserialize binary data back to a DICOM dataset.
static auto from_container_value(core::dicom_tag tag, encoding::vr_type vr, const container_module::optimized_value &val) -> core::dicom_element
Convert a container value back to a DICOM element.
static auto sequence_to_container(const core::dicom_element &element) -> std::shared_ptr< container_module::value_container >
Convert sequence items to container array.
static auto to_binary(const core::dicom_dataset &dataset) -> std::vector< uint8_t >
Serialize a DICOM dataset to binary format.
static auto to_container_value(const core::dicom_element &element) -> container_module::optimized_value
Convert a DICOM element to a container value.
static auto parse_element_key(std::string_view key) -> std::optional< std::pair< core::dicom_tag, encoding::vr_type > >
Parse an element key back to tag and VR.
static auto get_container_type(encoding::vr_type vr) noexcept -> container_module::value_types
Get the container value type for a DICOM VR.
static auto make_element_key(core::dicom_tag tag, encoding::vr_type vr) -> std::string
Create a key string for an element in the container.
static auto container_to_sequence(const container_module::value_container &container) -> std::vector< core::dicom_dataset >
Convert container array back to sequence items.
static auto deserialize_dataset(const container_module::value_container &container) -> Result< core::dicom_dataset >
Deserialize a value_container back to a DICOM dataset.
Adapter for mapping DICOM VR types to container_system values.
Compatibility header providing kcenon::pacs::compat::format as an alias for std::format.
constexpr bool is_numeric_vr(vr_type vr) noexcept
Checks if a VR is a numeric type.
Definition vr_type.h:214
constexpr bool is_binary_vr(vr_type vr) noexcept
Checks if a VR is a binary/raw byte type.
Definition vr_type.h:196
vr_type
DICOM Value Representation (VR) types.
Definition vr_type.h:29
@ SQ
Sequence of Items (undefined length)
@ UL
Unsigned Long (4 bytes)
@ SL
Signed Long (4 bytes)
@ US
Unsigned Short (2 bytes)
@ FD
Floating Point Double (8 bytes)
@ FL
Floating Point Single (4 bytes)
@ SV
Signed 64-bit Very Long (8 bytes)
@ SS
Signed Short (2 bytes)
@ UV
Unsigned 64-bit Very Long (8 bytes)
@ 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
constexpr std::optional< vr_type > from_string(std::string_view str) noexcept
Parses a two-character string to a vr_type.
Definition vr_type.h:132
constexpr std::string_view to_string(vr_type vr) noexcept
Converts a vr_type to its two-character string representation.
Definition vr_type.h:83
vr_encoding vr