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

PIMPL implementation for rle_codec. More...

Collaboration diagram for kcenon::pacs::encoding::compression::rle_codec::impl:
Collaboration graph

Public Member Functions

 impl ()=default
 
codec_result encode (std::span< const uint8_t > pixel_data, const image_params &params, const compression_options &options) const
 
codec_result decode (std::span< const uint8_t > compressed_data, const image_params &params) const
 

Private Member Functions

bool valid_for_rle (const image_params &params) const noexcept
 
codec_result encode_frame (std::span< const uint8_t > pixel_data, const image_params &params) const
 
codec_result decode_frame (std::span< const uint8_t > compressed_data, const image_params &params) const
 

Detailed Description

PIMPL implementation for rle_codec.

Definition at line 181 of file rle_codec.cpp.

Constructor & Destructor Documentation

◆ impl()

kcenon::pacs::encoding::compression::rle_codec::impl::impl ( )
default

Member Function Documentation

◆ decode()

codec_result kcenon::pacs::encoding::compression::rle_codec::impl::decode ( std::span< const uint8_t > compressed_data,
const image_params & params ) const
inlinenodiscard

Definition at line 213 of file rle_codec.cpp.

215 {
216
217 if (compressed_data.empty()) {
219 }
220
221 if (compressed_data.size() < kRLEHeaderSize) {
223 }
224
225 try {
226 return decode_frame(compressed_data, params);
227 } catch (const std::exception& e) {
228 return kcenon::pacs::pacs_error<compression_result>(kcenon::pacs::error_codes::decompression_error, std::string("RLE decoding failed: ") + e.what());
229 }
230 }
codec_result decode_frame(std::span< const uint8_t > compressed_data, const image_params &params) const
static constexpr size_t kRLEHeaderSize
RLE header size (64 bytes: 16 x 4-byte offsets)
Definition rle_codec.h:58
constexpr int decompression_error
Definition result.h:79
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_frame(), kcenon::pacs::error_codes::decompression_error, kcenon::pacs::encoding::compression::rle_codec::kRLEHeaderSize, and kcenon::pacs::pacs_error().

Referenced by kcenon::pacs::encoding::compression::rle_codec::decode().

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

◆ decode_frame()

codec_result kcenon::pacs::encoding::compression::rle_codec::impl::decode_frame ( std::span< const uint8_t > compressed_data,
const image_params & params ) const
inlinenodiscardprivate

Definition at line 401 of file rle_codec.cpp.

403 {
404
405 const uint8_t* header = compressed_data.data();
406
407 // Read number of segments from header
408 uint32_t num_segments = read_le32(header);
409 if (num_segments == 0 || num_segments > kMaxSegments) {
411 "Invalid RLE segment count: " + std::to_string(num_segments));
412 }
413
414 int expected_segments = calculate_segment_count(params);
415 if (static_cast<int>(num_segments) != expected_segments) {
417 "Segment count mismatch: expected " + std::to_string(expected_segments) +
418 ", got " + std::to_string(num_segments));
419 }
420
421 // Read segment offsets
422 std::vector<uint32_t> offsets(num_segments);
423 for (uint32_t i = 0; i < num_segments; ++i) {
424 offsets[i] = read_le32(header + 4 + i * 4);
425 if (offsets[i] >= compressed_data.size()) {
427 "Invalid segment offset: " + std::to_string(offsets[i]));
428 }
429 }
430
431 // Calculate segment sizes
432 std::vector<size_t> sizes(num_segments);
433 for (uint32_t i = 0; i < num_segments; ++i) {
434 if (i + 1 < num_segments) {
435 sizes[i] = offsets[i + 1] - offsets[i];
436 } else {
437 sizes[i] = compressed_data.size() - offsets[i];
438 }
439 }
440
441 size_t pixels_per_frame = static_cast<size_t>(params.width) * params.height;
442 int bytes_per_sample = (params.bits_allocated + 7) / 8;
443
444 // Decode each segment
445 std::vector<std::vector<uint8_t>> decoded_segments(num_segments);
446 for (uint32_t i = 0; i < num_segments; ++i) {
447 std::span<const uint8_t> segment_data(
448 compressed_data.data() + offsets[i], sizes[i]);
449 auto segment_result = decode_rle_segment(segment_data, pixels_per_frame);
450 if (segment_result.is_err()) {
453 "Segment " + std::to_string(i) + ": " + segment_result.error().message);
454 }
455 decoded_segments[i] = std::move(segment_result.value());
456
457 if (decoded_segments[i].size() != pixels_per_frame) {
459 "Segment " + std::to_string(i) + " decoded size mismatch: expected " +
460 std::to_string(pixels_per_frame) + ", got " +
461 std::to_string(decoded_segments[i].size()));
462 }
463 }
464
465 // Reconstruct pixel data from segments using SIMD optimization
466 size_t output_size = pixels_per_frame * params.samples_per_pixel * bytes_per_sample;
467 std::vector<uint8_t> output(output_size);
468
469 if (bytes_per_sample == 1) {
470 // 8-bit samples
471 if (params.samples_per_pixel == 1) {
472 // Grayscale: direct move
473 output = std::move(decoded_segments[0]);
474 } else if (params.samples_per_pixel == 3) {
475 // Color (RGB): interleave samples using SIMD
477 decoded_segments[0].data(),
478 decoded_segments[1].data(),
479 decoded_segments[2].data(),
480 output.data(),
481 pixels_per_frame);
482 } else {
483 // Other sample counts: scalar fallback
484 for (size_t i = 0; i < pixels_per_frame; ++i) {
485 for (int s = 0; s < params.samples_per_pixel; ++s) {
486 output[i * params.samples_per_pixel + s] = decoded_segments[s][i];
487 }
488 }
489 }
490 } else {
491 // 16-bit samples
492 if (params.samples_per_pixel == 1) {
493 // Grayscale: combine high and low byte segments using SIMD
495 decoded_segments[0].data(), // High byte
496 decoded_segments[1].data(), // Low byte
497 output.data(),
498 pixels_per_frame);
499 } else if (params.samples_per_pixel == 3) {
500 // 16-bit Color: merge each color component, then interleave
501 std::vector<uint8_t> r_plane(pixels_per_frame * 2);
502 std::vector<uint8_t> g_plane(pixels_per_frame * 2);
503 std::vector<uint8_t> b_plane(pixels_per_frame * 2);
504
505 // Merge high/low bytes for each color using SIMD
506 simd::merge_planes_to_16bit(decoded_segments[0].data(),
507 decoded_segments[1].data(),
508 r_plane.data(), pixels_per_frame);
509 simd::merge_planes_to_16bit(decoded_segments[2].data(),
510 decoded_segments[3].data(),
511 g_plane.data(), pixels_per_frame);
512 simd::merge_planes_to_16bit(decoded_segments[4].data(),
513 decoded_segments[5].data(),
514 b_plane.data(), pixels_per_frame);
515
516 // Interleave RGB (scalar for now, 16-bit RGB is rare)
517 for (size_t i = 0; i < pixels_per_frame; ++i) {
518 size_t src_idx = i * 2;
519 size_t dst_idx = i * 6; // 3 samples * 2 bytes
520 output[dst_idx] = r_plane[src_idx];
521 output[dst_idx + 1] = r_plane[src_idx + 1];
522 output[dst_idx + 2] = g_plane[src_idx];
523 output[dst_idx + 3] = g_plane[src_idx + 1];
524 output[dst_idx + 4] = b_plane[src_idx];
525 output[dst_idx + 5] = b_plane[src_idx + 1];
526 }
527 } else {
528 // Other sample counts: scalar fallback
529 for (size_t i = 0; i < pixels_per_frame; ++i) {
530 for (int s = 0; s < params.samples_per_pixel; ++s) {
531 size_t idx = (i * params.samples_per_pixel + s) * 2;
532 output[idx] = decoded_segments[s * 2 + 1][i]; // Low byte
533 output[idx + 1] = decoded_segments[s * 2][i]; // High byte
534 }
535 }
536 }
537 }
538
539 // Build output parameters
540 image_params output_params;
541 output_params.width = params.width;
542 output_params.height = params.height;
543 output_params.bits_allocated = params.bits_allocated;
544 output_params.bits_stored = params.bits_stored > 0 ? params.bits_stored : params.bits_allocated;
545 output_params.high_bit = output_params.bits_stored - 1;
546 output_params.samples_per_pixel = params.samples_per_pixel;
547 output_params.planar_configuration = 0; // Always interleaved output
548 output_params.pixel_representation = params.pixel_representation;
549 output_params.photometric = params.photometric;
550
551 return kcenon::pacs::ok<compression_result>(compression_result{std::move(output), output_params});
552 }
static constexpr int kMaxSegments
Maximum number of RLE segments allowed by DICOM specification.
Definition rle_codec.h:55
void merge_planes_to_16bit(const uint8_t *high, const uint8_t *low, uint8_t *dst, size_t pixel_count) noexcept
Merge high and low byte planes into 16-bit data.
Definition simd_rle.h:837
void planar_to_interleaved_rgb8(const uint8_t *r, const uint8_t *g, const uint8_t *b, uint8_t *dst, size_t pixel_count) noexcept
Convert planar RGB to interleaved format using best available SIMD.
Definition simd_rle.h:763

References kcenon::pacs::encoding::compression::image_params::bits_allocated, kcenon::pacs::encoding::compression::image_params::bits_stored, kcenon::pacs::error_codes::decompression_error, kcenon::pacs::encoding::compression::image_params::height, kcenon::pacs::encoding::compression::image_params::high_bit, kcenon::pacs::encoding::compression::rle_codec::kMaxSegments, kcenon::pacs::encoding::simd::merge_planes_to_16bit(), kcenon::pacs::pacs_error(), kcenon::pacs::encoding::compression::image_params::photometric, kcenon::pacs::encoding::compression::image_params::pixel_representation, kcenon::pacs::encoding::compression::image_params::planar_configuration, kcenon::pacs::encoding::simd::planar_to_interleaved_rgb8(), kcenon::pacs::encoding::compression::image_params::samples_per_pixel, and kcenon::pacs::encoding::compression::image_params::width.

Referenced by decode().

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

◆ encode()

codec_result kcenon::pacs::encoding::compression::rle_codec::impl::encode ( std::span< const uint8_t > pixel_data,
const image_params & params,
const compression_options & options ) const
inlinenodiscard

Definition at line 185 of file rle_codec.cpp.

188 {
189
190 if (pixel_data.empty()) {
192 }
193
194 if (!valid_for_rle(params)) {
196 "Invalid parameters for RLE: requires 8/16-bit, 1-3 samples per pixel");
197 }
198
199 size_t expected_size = params.frame_size_bytes();
200 if (pixel_data.size() != expected_size) {
202 "Pixel data size mismatch: expected " + std::to_string(expected_size) +
203 ", got " + std::to_string(pixel_data.size()));
204 }
205
206 try {
207 return encode_frame(pixel_data, params);
208 } catch (const std::exception& e) {
209 return kcenon::pacs::pacs_error<compression_result>(kcenon::pacs::error_codes::decompression_error, std::string("RLE encoding failed: ") + e.what());
210 }
211 }
bool valid_for_rle(const image_params &params) const noexcept
codec_result encode_frame(std::span< const uint8_t > pixel_data, const image_params &params) const
constexpr dicom_tag pixel_data
Pixel Data.

References kcenon::pacs::error_codes::decompression_error, encode_frame(), kcenon::pacs::encoding::compression::image_params::frame_size_bytes(), kcenon::pacs::pacs_error(), and valid_for_rle().

Referenced by kcenon::pacs::encoding::compression::rle_codec::encode().

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

◆ encode_frame()

codec_result kcenon::pacs::encoding::compression::rle_codec::impl::encode_frame ( std::span< const uint8_t > pixel_data,
const image_params & params ) const
inlinenodiscardprivate

Definition at line 254 of file rle_codec.cpp.

256 {
257
258 int num_segments = calculate_segment_count(params);
259 size_t pixels_per_frame = static_cast<size_t>(params.width) * params.height;
260 int bytes_per_sample = (params.bits_allocated + 7) / 8;
261
262 // Prepare segment data (one vector per segment)
263 std::vector<std::vector<uint8_t>> segments(num_segments);
264 for (auto& seg : segments) {
265 seg.reserve(pixels_per_frame);
266 }
267
268 // Extract segments from pixel data using SIMD optimization
269 // DICOM RLE stores each byte plane separately
270 // For 16-bit: high byte first, then low byte
271 // For color: each color component separately
272
273 if (bytes_per_sample == 1) {
274 // 8-bit samples
275 if (params.samples_per_pixel == 1) {
276 // Grayscale: single segment - direct copy
277 segments[0].assign(pixel_data.begin(), pixel_data.end());
278 } else if (params.samples_per_pixel == 3) {
279 // Color (RGB): 3 segments - use SIMD deinterleaving
280 segments[0].resize(pixels_per_frame);
281 segments[1].resize(pixels_per_frame);
282 segments[2].resize(pixels_per_frame);
284 pixel_data.data(),
285 segments[0].data(),
286 segments[1].data(),
287 segments[2].data(),
288 pixels_per_frame);
289 } else {
290 // Other sample counts: scalar fallback
291 for (size_t i = 0; i < pixels_per_frame; ++i) {
292 for (int s = 0; s < params.samples_per_pixel; ++s) {
293 segments[s].push_back(pixel_data[i * params.samples_per_pixel + s]);
294 }
295 }
296 }
297 } else {
298 // 16-bit samples (little-endian input)
299 // DICOM RLE stores: all high bytes, then all low bytes
300 if (params.samples_per_pixel == 1) {
301 // Grayscale: 2 segments (high byte, low byte) - use SIMD
302 segments[0].resize(pixels_per_frame);
303 segments[1].resize(pixels_per_frame);
305 pixel_data.data(),
306 segments[0].data(), // High byte
307 segments[1].data(), // Low byte
308 pixels_per_frame);
309 } else if (params.samples_per_pixel == 3) {
310 // 16-bit Color: 6 segments - SIMD for each color component
311 // First deinterleave RGB, then split each into high/low
312 std::vector<uint8_t> r_plane(pixels_per_frame * 2);
313 std::vector<uint8_t> g_plane(pixels_per_frame * 2);
314 std::vector<uint8_t> b_plane(pixels_per_frame * 2);
315
316 // Deinterleave 16-bit RGB (scalar for now, 16-bit RGB is rare)
317 for (size_t i = 0; i < pixels_per_frame; ++i) {
318 size_t src_idx = i * 6; // 3 samples * 2 bytes
319 size_t dst_idx = i * 2;
320 r_plane[dst_idx] = pixel_data[src_idx];
321 r_plane[dst_idx + 1] = pixel_data[src_idx + 1];
322 g_plane[dst_idx] = pixel_data[src_idx + 2];
323 g_plane[dst_idx + 1] = pixel_data[src_idx + 3];
324 b_plane[dst_idx] = pixel_data[src_idx + 4];
325 b_plane[dst_idx + 1] = pixel_data[src_idx + 5];
326 }
327
328 // Split each color plane into high/low using SIMD
329 segments[0].resize(pixels_per_frame); // R high
330 segments[1].resize(pixels_per_frame); // R low
331 segments[2].resize(pixels_per_frame); // G high
332 segments[3].resize(pixels_per_frame); // G low
333 segments[4].resize(pixels_per_frame); // B high
334 segments[5].resize(pixels_per_frame); // B low
335
336 simd::split_16bit_to_planes(r_plane.data(), segments[0].data(),
337 segments[1].data(), pixels_per_frame);
338 simd::split_16bit_to_planes(g_plane.data(), segments[2].data(),
339 segments[3].data(), pixels_per_frame);
340 simd::split_16bit_to_planes(b_plane.data(), segments[4].data(),
341 segments[5].data(), pixels_per_frame);
342 } else {
343 // Other sample counts: scalar fallback
344 for (size_t i = 0; i < pixels_per_frame; ++i) {
345 for (int s = 0; s < params.samples_per_pixel; ++s) {
346 size_t idx = (i * params.samples_per_pixel + s) * 2;
347 segments[s * 2].push_back(pixel_data[idx + 1]); // High byte
348 segments[s * 2 + 1].push_back(pixel_data[idx]); // Low byte
349 }
350 }
351 }
352 }
353
354 // Encode each segment
355 std::vector<std::vector<uint8_t>> encoded_segments(num_segments);
356 for (int i = 0; i < num_segments; ++i) {
357 encode_rle_segment(segments[i], encoded_segments[i]);
358 }
359
360 // Build output with RLE header
361 std::vector<uint8_t> output;
362
363 // Calculate total size (header + all segments, each padded to even length)
364 size_t total_size = kRLEHeaderSize;
365 for (const auto& seg : encoded_segments) {
366 total_size += seg.size();
367 if (seg.size() % 2 != 0) {
368 ++total_size; // Padding byte
369 }
370 }
371 output.reserve(total_size);
372
373 // Write header (16 x 4-byte offsets)
374 output.resize(kRLEHeaderSize, 0);
375
376 // First 4 bytes: number of segments
377 write_le32(output.data(), static_cast<uint32_t>(num_segments));
378
379 // Calculate and write segment offsets
380 uint32_t current_offset = kRLEHeaderSize;
381 for (int i = 0; i < num_segments; ++i) {
382 write_le32(output.data() + 4 + i * 4, current_offset);
383 current_offset += static_cast<uint32_t>(encoded_segments[i].size());
384 if (encoded_segments[i].size() % 2 != 0) {
385 ++current_offset; // Account for padding
386 }
387 }
388
389 // Write encoded segments
390 for (const auto& seg : encoded_segments) {
391 output.insert(output.end(), seg.begin(), seg.end());
392 if (seg.size() % 2 != 0) {
393 output.push_back(0); // Padding byte
394 }
395 }
396
397 image_params output_params = params;
398 return kcenon::pacs::ok<compression_result>(compression_result{std::move(output), output_params});
399 }
void interleaved_to_planar_rgb8(const uint8_t *src, uint8_t *r, uint8_t *g, uint8_t *b, size_t pixel_count) noexcept
Convert interleaved RGB to planar format using best available SIMD.
Definition simd_rle.h:725
void split_16bit_to_planes(const uint8_t *src, uint8_t *high, uint8_t *low, size_t pixel_count) noexcept
Split 16-bit data into high and low byte planes.
Definition simd_rle.h:800

References kcenon::pacs::encoding::compression::image_params::bits_allocated, kcenon::pacs::encoding::compression::image_params::height, kcenon::pacs::encoding::simd::interleaved_to_planar_rgb8(), kcenon::pacs::encoding::compression::rle_codec::kRLEHeaderSize, kcenon::pacs::encoding::compression::image_params::samples_per_pixel, kcenon::pacs::encoding::simd::split_16bit_to_planes(), and kcenon::pacs::encoding::compression::image_params::width.

Referenced by encode().

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

◆ valid_for_rle()

bool kcenon::pacs::encoding::compression::rle_codec::impl::valid_for_rle ( const image_params & params) const
inlinenodiscardprivatenoexcept

Definition at line 233 of file rle_codec.cpp.

233 {
234 // RLE supports 8-bit and 16-bit samples
235 if (params.bits_allocated != 8 && params.bits_allocated != 16) {
236 return false;
237 }
238 // 1-3 samples per pixel (grayscale, RGB)
239 if (params.samples_per_pixel < 1 || params.samples_per_pixel > 3) {
240 return false;
241 }
242 // Check segment count limit
243 int num_segments = calculate_segment_count(params);
244 if (num_segments > kMaxSegments) {
245 return false;
246 }
247 // Valid dimensions
248 if (params.width == 0 || params.height == 0) {
249 return false;
250 }
251 return true;
252 }

References kcenon::pacs::encoding::compression::rle_codec::kMaxSegments.

Referenced by encode().

Here is the caller graph for this function:

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