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

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

#include <implicit_vr_codec.h>

Collaboration diagram for kcenon::pacs::encoding::implicit_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 Implicit VR Little Endian.
 
static result< core::dicom_datasetdecode (std::span< const uint8_t > data)
 Decode bytes to a dataset using Implicit 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 Implicit VR Little Endian transfer syntax.

Implicit VR encoding format: ┌─────────────────────────────────────────┐ │ Data Element │ ├───────────┬───────────┬─────────────────┤ │ Group │ Element │ Length │Value│ │ (2 bytes) │ (2 bytes) │ (4 bytes) │ │ │ LE │ LE │ LE │ │ └───────────┴───────────┴───────────┴─────┘

Notes:

  • VR is NOT encoded (determined from dictionary lookup)
  • Length is always 32-bit little-endian
  • Undefined length (0xFFFFFFFF) is used for sequences
See also
DICOM PS3.5 Section 7.1.1

Definition at line 53 of file implicit_vr_codec.h.

Member Typedef Documentation

◆ result

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

Definition at line 59 of file implicit_vr_codec.h.

Member Function Documentation

◆ decode()

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

Decode bytes to a dataset using Implicit VR Little Endian.

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

Definition at line 168 of file implicit_vr_codec.cpp.

169 {
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}
static result< core::dicom_element > decode_element(std::span< const uint8_t > &data)
Decode a single element from bytes.
kcenon::pacs::Result< T > result
Result type for decode operations using kcenon::pacs::Result<T> pattern.
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()

implicit_vr_codec::result< core::dicom_element > kcenon::pacs::encoding::implicit_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 202 of file implicit_vr_codec.cpp.

203 {
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}
static auto instance() -> dicom_dictionary &
Get the singleton instance.
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 int insufficient_data
Definition result.h:82
@ length
Linear distance measurement.
vr_encoding vr

References decode_undefined_length(), kcenon::pacs::core::dicom_dictionary::instance(), kcenon::pacs::error_codes::insufficient_data, kcenon::pacs::encoding::UN, 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()

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

Definition at line 343 of file implicit_vr_codec.cpp.

344 {
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
367 core::dicom_dataset 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}
static result< core::dicom_dataset > decode(std::span< const uint8_t > data)
Decode bytes to a dataset using Implicit 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()

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

Definition at line 255 of file implicit_vr_codec.cpp.

257 {
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}
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::implicit_vr_codec::encode ( const core::dicom_dataset & dataset)
staticnodiscard

Encode a dataset to bytes using Implicit VR Little Endian.

Parameters
datasetThe dataset to encode
Returns
Encoded byte vector

Definition at line 91 of file implicit_vr_codec.cpp.

92 {
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}
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(), encode_sequence_item(), and kcenon::pacs::network::dimse::dimse_message::update_command_group_length().

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::implicit_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 104 of file implicit_vr_codec.cpp.

105 {
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}
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

References encode_sequence(), 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(), 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::implicit_vr_codec::encode_sequence ( std::vector< uint8_t > & buffer,
const core::dicom_element & element )
staticprivate

Definition at line 131 of file implicit_vr_codec.cpp.

132 {
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}
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::implicit_vr_codec::encode_sequence_item ( std::vector< uint8_t > & buffer,
const core::dicom_dataset & item )
staticprivate

Definition at line 148 of file implicit_vr_codec.cpp.

149 {
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}
static std::vector< uint8_t > encode(const core::dicom_dataset &dataset)
Encode a dataset to bytes using Implicit 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: