PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
implicit_vr_codec.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
14
15#include <cstring>
16
17namespace kcenon::pacs::encoding {
18
19namespace {
20
21// ============================================================================
22// Byte Order Utilities (Little Endian)
23// ============================================================================
24
25constexpr uint16_t read_le16(const uint8_t* data) {
26 return static_cast<uint16_t>(data[0]) |
27 (static_cast<uint16_t>(data[1]) << 8);
28}
29
30constexpr uint32_t read_le32(const uint8_t* data) {
31 return static_cast<uint32_t>(data[0]) |
32 (static_cast<uint32_t>(data[1]) << 8) |
33 (static_cast<uint32_t>(data[2]) << 16) |
34 (static_cast<uint32_t>(data[3]) << 24);
35}
36
37void write_le16(std::vector<uint8_t>& buffer, uint16_t value) {
38 buffer.push_back(static_cast<uint8_t>(value & 0xFF));
39 buffer.push_back(static_cast<uint8_t>((value >> 8) & 0xFF));
40}
41
42void write_le32(std::vector<uint8_t>& buffer, uint32_t value) {
43 buffer.push_back(static_cast<uint8_t>(value & 0xFF));
44 buffer.push_back(static_cast<uint8_t>((value >> 8) & 0xFF));
45 buffer.push_back(static_cast<uint8_t>((value >> 16) & 0xFF));
46 buffer.push_back(static_cast<uint8_t>((value >> 24) & 0xFF));
47}
48
49// ============================================================================
50// DICOM Special Tags
51// ============================================================================
52
53constexpr uint16_t ITEM_GROUP = 0xFFFE;
54constexpr uint16_t ITEM_TAG_ELEMENT = 0xE000; // Item
55constexpr uint16_t ITEM_DELIM_ELEMENT = 0xE00D; // Item Delimitation Item
56constexpr uint16_t SEQ_DELIM_ELEMENT = 0xE0DD; // Sequence Delimitation Item
57
58constexpr uint32_t UNDEFINED_LENGTH = 0xFFFFFFFF;
59
60constexpr bool is_sequence_delimiter(core::dicom_tag tag) {
61 return tag.group() == ITEM_GROUP && tag.element() == SEQ_DELIM_ELEMENT;
62}
63
64constexpr bool is_item_delimiter(core::dicom_tag tag) {
65 return tag.group() == ITEM_GROUP && tag.element() == ITEM_DELIM_ELEMENT;
66}
67
68constexpr bool is_item_tag(core::dicom_tag tag) {
69 return tag.group() == ITEM_GROUP && tag.element() == ITEM_TAG_ELEMENT;
70}
71
72} // namespace
73
74// ============================================================================
75// Error Helpers
76// ============================================================================
77
78namespace {
79
80template <typename T>
81kcenon::pacs::Result<T> make_codec_error(int code, const std::string& message) {
82 return kcenon::pacs::pacs_error<T>(code, message);
83}
84
85} // namespace
86
87// ============================================================================
88// Dataset Encoding
89// ============================================================================
90
91std::vector<uint8_t> implicit_vr_codec::encode(
92 const core::dicom_dataset& dataset) {
93 std::vector<uint8_t> buffer;
94 buffer.reserve(4096); // Initial capacity
95
96 for (const auto& [tag, element] : dataset) {
97 auto encoded = encode_element(element);
98 buffer.insert(buffer.end(), encoded.begin(), encoded.end());
99 }
100
101 return buffer;
102}
103
105 const core::dicom_element& element) {
106 std::vector<uint8_t> buffer;
107
108 // Write tag (4 bytes: group + element, little-endian)
109 write_le16(buffer, element.tag().group());
110 write_le16(buffer, element.tag().element());
111
112 // Handle sequences specially
113 if (element.is_sequence()) {
114 encode_sequence(buffer, element);
115 return buffer;
116 }
117
118 // Write length (4 bytes, little-endian)
119 // Ensure even length by padding if necessary
120 auto data = element.raw_data();
121 auto padded_data = pad_to_even(element.vr(), data);
122
123 write_le32(buffer, static_cast<uint32_t>(padded_data.size()));
124
125 // Write value
126 buffer.insert(buffer.end(), padded_data.begin(), padded_data.end());
127
128 return buffer;
129}
130
131void implicit_vr_codec::encode_sequence(std::vector<uint8_t>& buffer,
132 const core::dicom_element& element) {
133 // Write undefined length for sequence
134 write_le32(buffer, UNDEFINED_LENGTH);
135
136 // Encode each sequence item
137 const auto& items = element.sequence_items();
138 for (const auto& item : items) {
139 encode_sequence_item(buffer, item);
140 }
141
142 // Write sequence delimitation item
143 write_le16(buffer, ITEM_GROUP);
144 write_le16(buffer, SEQ_DELIM_ELEMENT);
145 write_le32(buffer, 0); // Length is always 0 for delimiter
146}
147
148void implicit_vr_codec::encode_sequence_item(std::vector<uint8_t>& buffer,
149 const core::dicom_dataset& item) {
150 // Write Item tag
151 write_le16(buffer, ITEM_GROUP);
152 write_le16(buffer, ITEM_TAG_ELEMENT);
153
154 // Encode item content first to determine length
155 auto item_content = encode(item);
156
157 // Write item length
158 write_le32(buffer, static_cast<uint32_t>(item_content.size()));
159
160 // Write item content
161 buffer.insert(buffer.end(), item_content.begin(), item_content.end());
162}
163
164// ============================================================================
165// Dataset Decoding
166// ============================================================================
167
169 std::span<const uint8_t> data) {
170 core::dicom_dataset dataset;
171
172 while (!data.empty()) {
173 // Peek at tag to check for sequence delimiters
174 if (data.size() >= 4) {
175 uint16_t group = read_le16(data.data());
176 uint16_t elem = read_le16(data.data() + 2);
177 core::dicom_tag tag{group, elem};
178
179 // Stop at sequence delimiter
180 if (is_sequence_delimiter(tag)) {
181 break;
182 }
183 // Stop at item delimiter
184 if (is_item_delimiter(tag)) {
185 break;
186 }
187 }
188
189 auto result = decode_element(data);
190 if (!result.is_ok()) {
192 result.error().code,
193 result.error().message);
194 }
195
196 dataset.insert(std::move(result.value()));
197 }
198
199 return dataset;
200}
201
203 std::span<const uint8_t>& data) {
204 // Need at least 8 bytes: tag (4) + length (4)
205 if (data.size() < 8) {
206 return make_codec_error<core::dicom_element>(
208 "Insufficient data to decode element");
209 }
210
211 // Read tag
212 uint16_t group = read_le16(data.data());
213 uint16_t elem = read_le16(data.data() + 2);
214 core::dicom_tag tag{group, elem};
215
216 // Read length
217 uint32_t length = read_le32(data.data() + 4);
218 data = data.subspan(8);
219
220 // Look up VR from dictionary
222 auto tag_info = dict.find(tag);
223
224 vr_type vr = vr_type::UN; // Default to Unknown if not found
225 if (tag_info) {
226 vr = static_cast<vr_type>(tag_info->vr);
227 }
228
229 // Special handling for sequence delimiter tags
230 if (tag.group() == ITEM_GROUP) {
231 // These are structure tags, not data elements
232 // Return a placeholder element (caller handles these)
233 return core::dicom_element(tag, vr_type::UN);
234 }
235
236 // Handle undefined length (sequences and encapsulated data)
237 if (length == UNDEFINED_LENGTH) {
238 return decode_undefined_length(tag, vr, data);
239 }
240
241 // Check if we have enough data
242 if (data.size() < length) {
243 return make_codec_error<core::dicom_element>(
245 "Insufficient data for element value");
246 }
247
248 // Read value
249 auto value_data = data.subspan(0, length);
250 data = data.subspan(length);
251
252 return core::dicom_element(tag, vr, value_data);
253}
254
257 std::span<const uint8_t>& data) {
258 // If this is a sequence (SQ), decode sequence items
259 if (vr == vr_type::SQ) {
260 core::dicom_element seq_element(tag, vr_type::SQ);
261
262 while (!data.empty()) {
263 // Check for sequence delimitation
264 if (data.size() < 8) {
265 return make_codec_error<core::dicom_element>(
267 "Insufficient data for sequence delimiter");
268 }
269
270 uint16_t item_group = read_le16(data.data());
271 uint16_t item_elem = read_le16(data.data() + 2);
272 core::dicom_tag item_tag{item_group, item_elem};
273
274 // Check for sequence delimitation item
275 if (is_sequence_delimiter(item_tag)) {
276 // Skip the delimiter (8 bytes: tag + length)
277 data = data.subspan(8);
278 break;
279 }
280
281 // Must be an Item tag
282 if (!is_item_tag(item_tag)) {
283 return make_codec_error<core::dicom_element>(
285 "Expected Item tag in sequence");
286 }
287
288 // Decode the sequence item
289 auto item_result = decode_sequence_item(data);
290 if (!item_result.is_ok()) {
291 return make_codec_error<core::dicom_element>(
292 item_result.error().code,
293 item_result.error().message);
294 }
295
296 seq_element.sequence_items().push_back(std::move(item_result.value()));
297 }
298
299 return seq_element;
300 }
301
302 // For other undefined-length elements (like encapsulated pixel data),
303 // read until we find a sequence delimitation item
304 std::vector<uint8_t> accumulated_data;
305
306 while (!data.empty()) {
307 if (data.size() < 8) {
308 return make_codec_error<core::dicom_element>(
310 "Insufficient data for encapsulated data");
311 }
312
313 uint16_t item_group = read_le16(data.data());
314 uint16_t item_elem = read_le16(data.data() + 2);
315 core::dicom_tag item_tag{item_group, item_elem};
316
317 // Check for sequence delimitation item
318 if (is_sequence_delimiter(item_tag)) {
319 data = data.subspan(8);
320 break;
321 }
322
323 // For encapsulated data, read item tag + length + data
324 if (is_item_tag(item_tag)) {
325 uint32_t item_length = read_le32(data.data() + 4);
326 data = data.subspan(8);
327
328 if (item_length != UNDEFINED_LENGTH && data.size() >= item_length) {
329 accumulated_data.insert(accumulated_data.end(),
330 data.begin(), data.begin() + item_length);
331 data = data.subspan(item_length);
332 }
333 } else {
334 return make_codec_error<core::dicom_element>(
336 "Invalid tag in encapsulated data");
337 }
338 }
339
340 return core::dicom_element(tag, vr, accumulated_data);
341}
342
344 std::span<const uint8_t>& data) {
345 // Read Item tag and length
346 if (data.size() < 8) {
347 return make_codec_error<core::dicom_dataset>(
349 "Insufficient data for sequence item");
350 }
351
352 uint16_t group = read_le16(data.data());
353 uint16_t elem = read_le16(data.data() + 2);
354 core::dicom_tag tag{group, elem};
355
356 if (!is_item_tag(tag)) {
357 return make_codec_error<core::dicom_dataset>(
359 "Expected Item tag for sequence item");
360 }
361
362 uint32_t item_length = read_le32(data.data() + 4);
363 data = data.subspan(8);
364
365 if (item_length == UNDEFINED_LENGTH) {
366 // Decode until Item Delimitation Item
368
369 while (!data.empty()) {
370 if (data.size() < 4) {
371 return make_codec_error<core::dicom_dataset>(
373 "Insufficient data for item delimiter check");
374 }
375
376 uint16_t elem_group = read_le16(data.data());
377 uint16_t elem_elem = read_le16(data.data() + 2);
378 core::dicom_tag elem_tag{elem_group, elem_elem};
379
380 if (is_item_delimiter(elem_tag)) {
381 // Skip delimiter
382 data = data.subspan(8);
383 break;
384 }
385
386 auto elem_result = decode_element(data);
387 if (!elem_result.is_ok()) {
388 return make_codec_error<core::dicom_dataset>(
389 elem_result.error().code,
390 elem_result.error().message);
391 }
392
393 item.insert(std::move(elem_result.value()));
394 }
395
396 return item;
397 }
398
399 // Explicit item length
400 if (data.size() < item_length) {
401 return make_codec_error<core::dicom_dataset>(
403 "Insufficient data for item content");
404 }
405
406 auto item_data = data.subspan(0, item_length);
407 data = data.subspan(item_length);
408
409 return decode(item_data);
410}
411
412} // namespace kcenon::pacs::encoding
void insert(dicom_element element)
Insert or replace an element in the dataset.
static auto instance() -> dicom_dictionary &
Get the singleton instance.
auto is_sequence() const noexcept -> bool
Check if this element is a sequence.
constexpr auto tag() const noexcept -> dicom_tag
Get the element's tag.
auto raw_data() const noexcept -> std::span< const uint8_t >
Get the raw data bytes.
constexpr auto vr() const noexcept -> encoding::vr_type
Get the element's VR.
auto sequence_items() -> std::vector< dicom_dataset > &
Get mutable access to sequence items.
static result< core::dicom_element > decode_element(std::span< const uint8_t > &data)
Decode a single element from bytes.
static result< core::dicom_dataset > decode_sequence_item(std::span< const uint8_t > &data)
static result< core::dicom_element > decode_undefined_length(core::dicom_tag tag, vr_type vr, std::span< const uint8_t > &data)
static void encode_sequence_item(std::vector< uint8_t > &buffer, const core::dicom_dataset &item)
static std::vector< uint8_t > encode_element(const core::dicom_element &element)
Encode a single element to bytes.
static result< core::dicom_dataset > decode(std::span< const uint8_t > data)
Decode bytes to a dataset using Implicit VR Little Endian.
static void encode_sequence(std::vector< uint8_t > &buffer, const core::dicom_element &element)
static std::vector< uint8_t > encode(const core::dicom_dataset &dataset)
Encode a dataset to bytes using Implicit VR Little Endian.
DICOM Data Dictionary for tag metadata lookup.
Encoder/decoder for Implicit VR Little Endian transfer syntax.
vr_type
DICOM Value Representation (VR) types.
Definition vr_type.h:29
@ UN
Unknown (variable length)
@ SQ
Sequence of Items (undefined length)
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
constexpr int insufficient_data
Definition result.h:82
constexpr int invalid_sequence
Definition result.h:83
Result< T > pacs_error(int code, const std::string &message, const std::string &details="")
Create a PACS error result with module context.
Definition result.h:234
std::string_view code
vr_encoding vr