PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
kcenon::pacs::encoding::explicit_vr_codec Class Reference

Encoder/decoder for Explicit VR Little Endian transfer syntax. More...

#include <explicit_vr_codec.h>

Collaboration diagram for kcenon::pacs::encoding::explicit_vr_codec:
Collaboration graph

Public Types

template<typename T >
using result = kcenon::pacs::Result<T>
 Result type for decode operations using kcenon::pacs::Result<T> pattern.
 

Static Public Member Functions

static std::vector< uint8_t > encode (const core::dicom_dataset &dataset)
 Encode a dataset to bytes using Explicit VR Little Endian.
 
static result< core::dicom_datasetdecode (std::span< const uint8_t > data)
 Decode bytes to a dataset using Explicit VR Little Endian.
 
static std::vector< uint8_t > encode_element (const core::dicom_element &element)
 Encode a single element to bytes.
 
static result< core::dicom_elementdecode_element (std::span< const uint8_t > &data)
 Decode a single element from bytes.
 

Static Private Member Functions

static void encode_sequence (std::vector< uint8_t > &buffer, const core::dicom_element &element)
 
static void encode_sequence_item (std::vector< uint8_t > &buffer, const core::dicom_dataset &item)
 
static result< core::dicom_elementdecode_undefined_length (core::dicom_tag tag, vr_type vr, std::span< const uint8_t > &data)
 
static result< core::dicom_datasetdecode_sequence_item (std::span< const uint8_t > &data)
 

Detailed Description

Encoder/decoder for Explicit VR Little Endian transfer syntax.

Explicit VR encoding has two formats depending on VR:

Standard VRs (16-bit length): ┌─────────────────────────────────────────────────────┐ │ Data Element │ ├───────────┬───────────┬────────┬──────────┬─────────┤ │ Group │ Element │ VR │ Length │ Value │ │ (2 bytes) │ (2 bytes) │(2 char)│ (2 bytes)│ │ │ LE │ LE │ ASCII │ LE │ │ └───────────┴───────────┴────────┴──────────┴─────────┘ VRs: AE, AS, AT, CS, DA, DS, DT, FL, FD, IS, LO, LT, PN, SH, SL, SS, ST, TM, UI, UL, US

Extended VRs (32-bit length): ┌──────────────────────────────────────────────────────────────┐ │ Data Element │ ├───────────┬───────────┬────────┬──────────┬──────────┬───────┤ │ Group │ Element │ VR │ Reserved │ Length │ Value │ │ (2 bytes) │ (2 bytes) │(2 char)│ (2 bytes)│ (4 bytes)│ │ │ LE │ LE │ ASCII │ 0x0000 │ LE │ │ └───────────┴───────────┴────────┴──────────┴──────────┴───────┘ VRs: OB, OD, OF, OL, OV, OW, SQ, SV, UC, UN, UR, UT, UV

See also
DICOM PS3.5 Section 7.1.2

Definition at line 61 of file explicit_vr_codec.h.

Member Typedef Documentation

◆ result

Result type for decode operations using kcenon::pacs::Result<T> pattern.

Definition at line 67 of file explicit_vr_codec.h.

Member Function Documentation

◆ decode()

explicit_vr_codec::result< core::dicom_dataset > kcenon::pacs::encoding::explicit_vr_codec::decode ( std::span< const uint8_t > data)
staticnodiscard

Decode bytes to a dataset using Explicit VR Little Endian.

Parameters
dataThe bytes to decode
Returns
Result containing the decoded dataset or an error

Definition at line 188 of file explicit_vr_codec.cpp.

189 {
190 core::dicom_dataset dataset;
191
192 while (!data.empty()) {
193 // Peek at tag to check for sequence delimiters
194 if (data.size() >= 4) {
195 uint16_t group = read_le16(data.data());
196 uint16_t elem = read_le16(data.data() + 2);
197 core::dicom_tag tag{group, elem};
198
199 // Stop at sequence delimiter
200 if (is_sequence_delimiter(tag)) {
201 break;
202 }
203 // Stop at item delimiter
204 if (is_item_delimiter(tag)) {
205 break;
206 }
207 }
208
209 auto result = decode_element(data);
210 if (!result.is_ok()) {
212 result.error().code,
213 result.error().message);
214 }
215
216 dataset.insert(std::move(result.value()));
217 }
218
219 return dataset;
220}
kcenon::pacs::Result< T > result
Result type for decode operations using kcenon::pacs::Result<T> pattern.
static result< core::dicom_element > decode_element(std::span< const uint8_t > &data)
Decode a single element from bytes.
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

References decode_element(), kcenon::pacs::core::dicom_dataset::insert(), and kcenon::pacs::pacs_error().

Referenced by kcenon::pacs::network::dimse::dimse_message::decode(), and decode_sequence_item().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ decode_element()

explicit_vr_codec::result< core::dicom_element > kcenon::pacs::encoding::explicit_vr_codec::decode_element ( std::span< const uint8_t > & data)
staticnodiscard

Decode a single element from bytes.

The span reference is advanced past the decoded element.

Parameters
dataReference to the data span (will be modified)
Returns
Result containing the decoded element or an error

Definition at line 222 of file explicit_vr_codec.cpp.

223 {
224 // Need at least 8 bytes for standard format: tag (4) + VR (2) + length (2)
225 if (data.size() < 8) {
226 return make_codec_error<core::dicom_element>(
228 "Insufficient data to decode element");
229 }
230
231 // Read tag
232 uint16_t group = read_le16(data.data());
233 uint16_t elem = read_le16(data.data() + 2);
234 core::dicom_tag tag{group, elem};
235
236 // Special handling for sequence delimiter/item tags (implicit VR format)
237 if (is_special_tag(tag)) {
238 data = data.subspan(8);
239
240 // For delimiter tags, return a placeholder element
241 return core::dicom_element(tag, vr_type::UN);
242 }
243
244 // Read VR (2 ASCII characters)
245 char vr_chars[2];
246 vr_chars[0] = static_cast<char>(data[4]);
247 vr_chars[1] = static_cast<char>(data[5]);
248 std::string_view vr_str(vr_chars, 2);
249
250 auto vr_opt = from_string(vr_str);
251 if (!vr_opt) {
252 return make_codec_error<core::dicom_element>(
254 "Unknown VR type");
255 }
256 vr_type vr = *vr_opt;
257
258 uint32_t length;
259 size_t header_size;
260
261 // Determine length format based on VR
263 // Extended format: need 12 bytes total
264 if (data.size() < 12) {
265 return make_codec_error<core::dicom_element>(
267 "Insufficient data for extended VR format");
268 }
269 // Skip reserved 2 bytes, then read 4-byte length
270 length = read_le32(data.data() + 8);
271 header_size = 12;
272 } else {
273 // Standard format: 2-byte length
274 length = read_le16(data.data() + 6);
275 header_size = 8;
276 }
277
278 data = data.subspan(header_size);
279
280 // Handle undefined length (sequences and encapsulated data)
281 if (length == UNDEFINED_LENGTH) {
282 return decode_undefined_length(tag, vr, data);
283 }
284
285 // Check if we have enough data
286 if (data.size() < length) {
287 return make_codec_error<core::dicom_element>(
289 "Insufficient data for element value");
290 }
291
292 // Read value
293 auto value_data = data.subspan(0, length);
294 data = data.subspan(length);
295
296 return core::dicom_element(tag, vr, value_data);
297}
static result< core::dicom_element > decode_undefined_length(core::dicom_tag tag, vr_type vr, std::span< const uint8_t > &data)
@ UN
Unknown (variable length)
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 bool has_explicit_32bit_length(vr_type vr) noexcept
Checks if a VR requires 32-bit length field in Explicit VR encoding.
Definition vr_type.h:235
constexpr int insufficient_data
Definition result.h:82
constexpr int unknown_vr
Definition result.h:84
@ length
Linear distance measurement.
vr_encoding vr

References decode_undefined_length(), kcenon::pacs::encoding::from_string(), kcenon::pacs::encoding::has_explicit_32bit_length(), kcenon::pacs::error_codes::insufficient_data, kcenon::pacs::encoding::UN, kcenon::pacs::error_codes::unknown_vr, and vr.

Referenced by decode(), and decode_sequence_item().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ decode_sequence_item()

explicit_vr_codec::result< core::dicom_dataset > kcenon::pacs::encoding::explicit_vr_codec::decode_sequence_item ( std::span< const uint8_t > & data)
staticprivate

Definition at line 387 of file explicit_vr_codec.cpp.

388 {
389 // Read Item tag and length (implicit VR format for item tags)
390 if (data.size() < 8) {
391 return make_codec_error<core::dicom_dataset>(
393 "Insufficient data for sequence item");
394 }
395
396 uint16_t group = read_le16(data.data());
397 uint16_t elem = read_le16(data.data() + 2);
398 core::dicom_tag tag{group, elem};
399
400 if (!is_item_tag(tag)) {
401 return make_codec_error<core::dicom_dataset>(
403 "Expected Item tag for sequence item");
404 }
405
406 uint32_t item_length = read_le32(data.data() + 4);
407 data = data.subspan(8);
408
409 if (item_length == UNDEFINED_LENGTH) {
410 // Decode until Item Delimitation Item
411 core::dicom_dataset item;
412
413 while (!data.empty()) {
414 if (data.size() < 4) {
415 return make_codec_error<core::dicom_dataset>(
417 "Insufficient data for item delimiter check");
418 }
419
420 uint16_t elem_group = read_le16(data.data());
421 uint16_t elem_elem = read_le16(data.data() + 2);
422 core::dicom_tag elem_tag{elem_group, elem_elem};
423
424 if (is_item_delimiter(elem_tag)) {
425 // Skip delimiter
426 data = data.subspan(8);
427 break;
428 }
429
430 auto elem_result = decode_element(data);
431 if (!elem_result.is_ok()) {
432 return make_codec_error<core::dicom_dataset>(
433 elem_result.error().code,
434 elem_result.error().message);
435 }
436
437 item.insert(std::move(elem_result.value()));
438 }
439
440 return item;
441 }
442
443 // Explicit item length
444 if (data.size() < item_length) {
445 return make_codec_error<core::dicom_dataset>(
447 "Insufficient data for item content");
448 }
449
450 auto item_data = data.subspan(0, item_length);
451 data = data.subspan(item_length);
452
453 return decode(item_data);
454}
static result< core::dicom_dataset > decode(std::span< const uint8_t > data)
Decode bytes to a dataset using Explicit VR Little Endian.
constexpr dicom_tag item
Item.
constexpr int invalid_sequence
Definition result.h:83

References decode(), decode_element(), kcenon::pacs::error_codes::insufficient_data, and kcenon::pacs::error_codes::invalid_sequence.

Referenced by decode_undefined_length().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ decode_undefined_length()

explicit_vr_codec::result< core::dicom_element > kcenon::pacs::encoding::explicit_vr_codec::decode_undefined_length ( core::dicom_tag tag,
vr_type vr,
std::span< const uint8_t > & data )
staticprivate

Definition at line 299 of file explicit_vr_codec.cpp.

301 {
302 // If this is a sequence (SQ), decode sequence items
303 if (vr == vr_type::SQ) {
304 core::dicom_element seq_element(tag, vr_type::SQ);
305
306 while (!data.empty()) {
307 // Check for sequence delimitation
308 if (data.size() < 8) {
309 return make_codec_error<core::dicom_element>(
311 "Insufficient data for sequence delimiter");
312 }
313
314 uint16_t item_group = read_le16(data.data());
315 uint16_t item_elem = read_le16(data.data() + 2);
316 core::dicom_tag item_tag{item_group, item_elem};
317
318 // Check for sequence delimitation item
319 if (is_sequence_delimiter(item_tag)) {
320 // Skip the delimiter (8 bytes: tag + length)
321 data = data.subspan(8);
322 break;
323 }
324
325 // Must be an Item tag
326 if (!is_item_tag(item_tag)) {
327 return make_codec_error<core::dicom_element>(
329 "Expected Item tag in sequence");
330 }
331
332 // Decode the sequence item
333 auto item_result = decode_sequence_item(data);
334 if (!item_result.is_ok()) {
335 return make_codec_error<core::dicom_element>(
336 item_result.error().code,
337 item_result.error().message);
338 }
339
340 seq_element.sequence_items().push_back(std::move(item_result.value()));
341 }
342
343 return seq_element;
344 }
345
346 // For other undefined-length elements (like encapsulated pixel data),
347 // read until we find a sequence delimitation item
348 std::vector<uint8_t> accumulated_data;
349
350 while (!data.empty()) {
351 if (data.size() < 8) {
352 return make_codec_error<core::dicom_element>(
354 "Insufficient data for encapsulated data");
355 }
356
357 uint16_t item_group = read_le16(data.data());
358 uint16_t item_elem = read_le16(data.data() + 2);
359 core::dicom_tag item_tag{item_group, item_elem};
360
361 // Check for sequence delimitation item
362 if (is_sequence_delimiter(item_tag)) {
363 data = data.subspan(8);
364 break;
365 }
366
367 // For encapsulated data, read item tag + length + data
368 if (is_item_tag(item_tag)) {
369 uint32_t item_length = read_le32(data.data() + 4);
370 data = data.subspan(8);
371
372 if (item_length != UNDEFINED_LENGTH && data.size() >= item_length) {
373 accumulated_data.insert(accumulated_data.end(),
374 data.begin(), data.begin() + item_length);
375 data = data.subspan(item_length);
376 }
377 } else {
378 return make_codec_error<core::dicom_element>(
380 "Invalid tag in encapsulated data");
381 }
382 }
383
384 return core::dicom_element(tag, vr, accumulated_data);
385}
static result< core::dicom_dataset > decode_sequence_item(std::span< const uint8_t > &data)
@ SQ
Sequence of Items (undefined length)

References decode_sequence_item(), kcenon::pacs::error_codes::insufficient_data, kcenon::pacs::error_codes::invalid_sequence, kcenon::pacs::core::dicom_element::sequence_items(), kcenon::pacs::encoding::SQ, and vr.

Referenced by decode_element().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ encode()

std::vector< uint8_t > kcenon::pacs::encoding::explicit_vr_codec::encode ( const core::dicom_dataset & dataset)
staticnodiscard

Encode a dataset to bytes using Explicit VR Little Endian.

Parameters
datasetThe dataset to encode
Returns
Encoded byte vector

Definition at line 95 of file explicit_vr_codec.cpp.

96 {
97 std::vector<uint8_t> buffer;
98 buffer.reserve(4096); // Initial capacity
99
100 for (const auto& [tag, element] : dataset) {
101 auto encoded = encode_element(element);
102 buffer.insert(buffer.end(), encoded.begin(), encoded.end());
103 }
104
105 return buffer;
106}
static std::vector< uint8_t > encode_element(const core::dicom_element &element)
Encode a single element to bytes.

References encode_element().

Referenced by kcenon::pacs::network::dimse::dimse_message::encode(), and encode_sequence_item().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ encode_element()

std::vector< uint8_t > kcenon::pacs::encoding::explicit_vr_codec::encode_element ( const core::dicom_element & element)
staticnodiscard

Encode a single element to bytes.

Parameters
elementThe element to encode
Returns
Encoded byte vector

Definition at line 108 of file explicit_vr_codec.cpp.

109 {
110 std::vector<uint8_t> buffer;
111
112 // Write tag (4 bytes: group + element, little-endian)
113 write_le16(buffer, element.tag().group());
114 write_le16(buffer, element.tag().element());
115
116 // Write VR (2 ASCII characters)
117 auto vr_str = to_string(element.vr());
118 buffer.push_back(static_cast<uint8_t>(vr_str[0]));
119 buffer.push_back(static_cast<uint8_t>(vr_str[1]));
120
121 // Handle sequences specially
122 if (element.is_sequence()) {
123 encode_sequence(buffer, element);
124 return buffer;
125 }
126
127 // Get padded data
128 auto data = element.raw_data();
129 auto padded_data = pad_to_even(element.vr(), data);
130 uint32_t length = static_cast<uint32_t>(padded_data.size());
131
132 // Write length based on VR type
133 if (has_explicit_32bit_length(element.vr())) {
134 // Extended format: 2 reserved bytes + 4 byte length
135 write_le16(buffer, 0x0000); // Reserved
136 write_le32(buffer, length);
137 } else {
138 // Standard format: 2 byte length
139 write_le16(buffer, static_cast<uint16_t>(length));
140 }
141
142 // Write value
143 buffer.insert(buffer.end(), padded_data.begin(), padded_data.end());
144
145 return buffer;
146}
static void encode_sequence(std::vector< uint8_t > &buffer, const core::dicom_element &element)
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 std::string_view to_string(vr_type vr) noexcept
Converts a vr_type to its two-character string representation.
Definition vr_type.h:83

References encode_sequence(), kcenon::pacs::encoding::has_explicit_32bit_length(), kcenon::pacs::core::dicom_element::is_sequence(), kcenon::pacs::encoding::pad_to_even(), kcenon::pacs::core::dicom_element::raw_data(), kcenon::pacs::core::dicom_element::tag(), kcenon::pacs::encoding::to_string(), and kcenon::pacs::core::dicom_element::vr().

Referenced by encode().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ encode_sequence()

void kcenon::pacs::encoding::explicit_vr_codec::encode_sequence ( std::vector< uint8_t > & buffer,
const core::dicom_element & element )
staticprivate

Definition at line 148 of file explicit_vr_codec.cpp.

149 {
150 // Write reserved bytes for SQ VR (uses 32-bit length)
151 write_le16(buffer, 0x0000);
152
153 // Write undefined length for sequence
154 write_le32(buffer, UNDEFINED_LENGTH);
155
156 // Encode each sequence item
157 const auto& items = element.sequence_items();
158 for (const auto& item : items) {
159 encode_sequence_item(buffer, item);
160 }
161
162 // Write sequence delimitation item (implicit VR encoding for delimiters)
163 write_le16(buffer, ITEM_GROUP);
164 write_le16(buffer, SEQ_DELIM_ELEMENT);
165 write_le32(buffer, 0); // Length is always 0 for delimiter
166}
static void encode_sequence_item(std::vector< uint8_t > &buffer, const core::dicom_dataset &item)

References encode_sequence_item(), and kcenon::pacs::core::dicom_element::sequence_items().

Referenced by encode_element().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ encode_sequence_item()

void kcenon::pacs::encoding::explicit_vr_codec::encode_sequence_item ( std::vector< uint8_t > & buffer,
const core::dicom_dataset & item )
staticprivate

Definition at line 168 of file explicit_vr_codec.cpp.

169 {
170 // Write Item tag (implicit VR encoding for item tags)
171 write_le16(buffer, ITEM_GROUP);
172 write_le16(buffer, ITEM_TAG_ELEMENT);
173
174 // Encode item content first to determine length
175 auto item_content = encode(item);
176
177 // Write item length
178 write_le32(buffer, static_cast<uint32_t>(item_content.size()));
179
180 // Write item content
181 buffer.insert(buffer.end(), item_content.begin(), item_content.end());
182}
static std::vector< uint8_t > encode(const core::dicom_dataset &dataset)
Encode a dataset to bytes using Explicit VR Little Endian.

References encode().

Referenced by encode_sequence().

Here is the call graph for this function:
Here is the caller graph for this function:

The documentation for this class was generated from the following files: