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

PIMPL implementation for jpeg2000_codec. More...

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

Public Member Functions

 impl (bool lossless, float compression_ratio, int resolution_levels)
 
bool is_lossless_mode () const noexcept
 
float compression_ratio () const noexcept
 
int resolution_levels () const noexcept
 
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 Attributes

bool lossless_
 
float compression_ratio_
 
int resolution_levels_
 

Detailed Description

PIMPL implementation for jpeg2000_codec.

Definition at line 214 of file jpeg2000_codec.cpp.

Constructor & Destructor Documentation

◆ impl()

kcenon::pacs::encoding::compression::jpeg2000_codec::impl::impl ( bool lossless,
float compression_ratio,
int resolution_levels )
inlineexplicit

Member Function Documentation

◆ compression_ratio()

float kcenon::pacs::encoding::compression::jpeg2000_codec::impl::compression_ratio ( ) const
inlinenodiscardnoexcept

Definition at line 225 of file jpeg2000_codec.cpp.

225 {
226 return compression_ratio_;
227 }

References compression_ratio_.

Referenced by kcenon::pacs::encoding::compression::jpeg2000_codec::compression_ratio().

Here is the caller graph for this function:

◆ decode()

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

Definition at line 419 of file jpeg2000_codec.cpp.

420 {
421#ifndef PACS_WITH_JPEG2000_CODEC
422 (void)compressed_data;
423 (void)params;
425 "JPEG 2000 codec not available: OpenJPEG library not found at build time");
426#else
427 if (compressed_data.empty()) {
429 }
430
431 // Detect format
432 OPJ_CODEC_FORMAT format = detect_j2k_format(compressed_data);
433
434 // Create decoder
435 opj_codec_t* codec = opj_create_decompress(format);
436 if (!codec) {
438 }
439
440 // Set up error handling
441 std::string error_msg;
442 opj_set_error_handler(codec, opj_error_callback, &error_msg);
443 opj_set_warning_handler(codec, opj_warning_callback, nullptr);
444 opj_set_info_handler(codec, opj_info_callback, nullptr);
445
446 // Set decoding parameters
447 opj_dparameters_t parameters;
448 opj_set_default_decoder_parameters(&parameters);
449
450 if (!opj_setup_decoder(codec, &parameters)) {
451 opj_destroy_codec(codec);
452 return kcenon::pacs::pacs_error<compression_result>(kcenon::pacs::error_codes::decompression_error, "Failed to setup OpenJPEG decoder: " + error_msg);
453 }
454
455 // Create input stream
456 opj_memory_stream input_stream;
457 input_stream.data = compressed_data.data();
458 input_stream.size = compressed_data.size();
459 input_stream.offset = 0;
460
461 opj_stream_t* stream = opj_stream_default_create(OPJ_TRUE);
462 if (!stream) {
463 opj_destroy_codec(codec);
465 }
466
467 opj_stream_set_user_data(stream, &input_stream, nullptr);
468 opj_stream_set_user_data_length(stream, compressed_data.size());
469 opj_stream_set_read_function(stream, opj_memory_stream_read);
470 opj_stream_set_skip_function(stream, opj_memory_stream_skip);
471 opj_stream_set_seek_function(stream, opj_memory_stream_seek);
472
473 // Read header
474 opj_image_t* image = nullptr;
475 if (!opj_read_header(stream, codec, &image)) {
476 opj_stream_destroy(stream);
477 opj_destroy_codec(codec);
478 return kcenon::pacs::pacs_error<compression_result>(kcenon::pacs::error_codes::decompression_error, "Failed to read JPEG 2000 header: " + error_msg);
479 }
480
481 // Decode
482 if (!opj_decode(codec, stream, image)) {
483 opj_image_destroy(image);
484 opj_stream_destroy(stream);
485 opj_destroy_codec(codec);
486 return kcenon::pacs::pacs_error<compression_result>(kcenon::pacs::error_codes::decompression_error, "Failed to decode JPEG 2000 data: " + error_msg);
487 }
488
489 if (!opj_end_decompress(codec, stream)) {
490 opj_image_destroy(image);
491 opj_stream_destroy(stream);
492 opj_destroy_codec(codec);
493 return kcenon::pacs::pacs_error<compression_result>(kcenon::pacs::error_codes::decompression_error, "Failed to finalize JPEG 2000 decoding: " + error_msg);
494 }
495
496 // Extract image parameters from decoded data
497 image_params output_params = params;
498 output_params.width = static_cast<uint16_t>(image->x1 - image->x0);
499 output_params.height = static_cast<uint16_t>(image->y1 - image->y0);
500 output_params.samples_per_pixel = static_cast<uint16_t>(image->numcomps);
501
502 if (image->numcomps > 0) {
503 output_params.bits_stored = static_cast<uint16_t>(image->comps[0].prec);
504 output_params.bits_allocated = (output_params.bits_stored <= 8) ? 8 : 16;
505 output_params.high_bit = output_params.bits_stored - 1;
506 output_params.pixel_representation = image->comps[0].sgnd ? 1 : 0;
507 }
508
509 // Determine photometric interpretation
510 if (image->numcomps == 1) {
511 output_params.photometric = photometric_interpretation::monochrome2;
512 } else if (image->numcomps == 3) {
513 output_params.photometric = (image->color_space == OPJ_CLRSPC_SRGB)
515 : photometric_interpretation::ycbcr_full;
516 }
517
518 // Validate dimensions if provided
519 if (params.width > 0 && params.width != output_params.width) {
520 opj_image_destroy(image);
521 opj_stream_destroy(stream);
522 opj_destroy_codec(codec);
524 std::to_string(params.width) + ", got " +
525 std::to_string(output_params.width));
526 }
527 if (params.height > 0 && params.height != output_params.height) {
528 opj_image_destroy(image);
529 opj_stream_destroy(stream);
530 opj_destroy_codec(codec);
532 std::to_string(params.height) + ", got " +
533 std::to_string(output_params.height));
534 }
535
536 // Allocate output buffer
537 size_t pixel_count = static_cast<size_t>(output_params.width) * output_params.height;
538 size_t bytes_per_sample = (output_params.bits_allocated + 7) / 8;
539 size_t total_size = pixel_count * output_params.samples_per_pixel * bytes_per_sample;
540
541 std::vector<uint8_t> output_data(total_size);
542
543 // Copy decoded data to output buffer (interleaved format)
544 for (OPJ_UINT32 c = 0; c < image->numcomps; ++c) {
545 const OPJ_INT32* comp_data = image->comps[c].data;
546
547 for (size_t i = 0; i < pixel_count; ++i) {
548 size_t dst_idx = (i * output_params.samples_per_pixel + c) * bytes_per_sample;
549 OPJ_INT32 value = comp_data[i];
550
551 if (bytes_per_sample == 1) {
552 output_data[dst_idx] = static_cast<uint8_t>(value & 0xFF);
553 } else {
554 // 16-bit little-endian
555 output_data[dst_idx] = static_cast<uint8_t>(value & 0xFF);
556 output_data[dst_idx + 1] = static_cast<uint8_t>((value >> 8) & 0xFF);
557 }
558 }
559 }
560
561 // Cleanup
562 opj_image_destroy(image);
563 opj_stream_destroy(stream);
564 opj_destroy_codec(codec);
565
566 output_params.planar_configuration = 0; // Output is always interleaved
567 return kcenon::pacs::ok<compression_result>(compression_result{std::move(output_data), output_params});
568#endif // PACS_WITH_JPEG2000_CODEC
569 }
@ monochrome2
Minimum pixel value displayed as black.
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 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::monochrome2, 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::compression::rgb, kcenon::pacs::encoding::compression::image_params::samples_per_pixel, kcenon::pacs::encoding::compression::image_params::width, and kcenon::pacs::encoding::compression::ycbcr_full.

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

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

◆ encode()

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

Definition at line 233 of file jpeg2000_codec.cpp.

235 {
236#ifndef PACS_WITH_JPEG2000_CODEC
237 (void)pixel_data;
238 (void)params;
239 (void)options;
241 "JPEG 2000 codec not available: OpenJPEG library not found at build time");
242#else
243 // Determine if we should use lossless mode
244 bool use_lossless = lossless_ || options.lossless;
245
246 // Create image component parameters
247 std::vector<opj_image_cmptparm_t> cmptparm(params.samples_per_pixel);
248 for (uint16_t i = 0; i < params.samples_per_pixel; ++i) {
249 std::memset(&cmptparm[i], 0, sizeof(opj_image_cmptparm_t));
250 cmptparm[i].prec = params.bits_stored;
251 cmptparm[i].bpp = params.bits_stored;
252 cmptparm[i].sgnd = params.is_signed() ? 1 : 0;
253 cmptparm[i].dx = 1;
254 cmptparm[i].dy = 1;
255 cmptparm[i].w = params.width;
256 cmptparm[i].h = params.height;
257 }
258
259 // Determine color space
260 OPJ_COLOR_SPACE color_space = OPJ_CLRSPC_GRAY;
261 if (params.samples_per_pixel == 3) {
262 color_space = (params.photometric == photometric_interpretation::rgb)
263 ? OPJ_CLRSPC_SRGB
264 : OPJ_CLRSPC_SYCC;
265 }
266
267 // Create image
268 opj_image_t* image = opj_image_create(
269 params.samples_per_pixel,
270 cmptparm.data(),
271 color_space);
272
273 if (!image) {
274 return kcenon::pacs::pacs_error<compression_result>(kcenon::pacs::error_codes::decompression_error, "Failed to create OpenJPEG image structure");
275 }
276
277 // Set image offset and reference grid
278 image->x0 = 0;
279 image->y0 = 0;
280 image->x1 = params.width;
281 image->y1 = params.height;
282
283 // Copy pixel data to image components
284 size_t bytes_per_sample = (params.bits_allocated + 7) / 8;
285 size_t pixel_count = static_cast<size_t>(params.width) * params.height;
286
287 for (uint16_t c = 0; c < params.samples_per_pixel; ++c) {
288 OPJ_INT32* comp_data = image->comps[c].data;
289
290 for (size_t i = 0; i < pixel_count; ++i) {
291 size_t src_idx;
292 if (params.planar_configuration == 0) {
293 // Interleaved: R0G0B0R1G1B1...
294 src_idx = (i * params.samples_per_pixel + c) * bytes_per_sample;
295 } else {
296 // Planar: RRR...GGG...BBB...
297 src_idx = (c * pixel_count + i) * bytes_per_sample;
298 }
299
300 if (src_idx + bytes_per_sample > pixel_data.size()) {
301 opj_image_destroy(image);
303 }
304
305 OPJ_INT32 value = 0;
306 if (bytes_per_sample == 1) {
307 value = params.is_signed()
308 ? static_cast<OPJ_INT32>(static_cast<int8_t>(pixel_data[src_idx]))
309 : static_cast<OPJ_INT32>(pixel_data[src_idx]);
310 } else {
311 // 16-bit (little-endian)
312 uint16_t raw = static_cast<uint16_t>(pixel_data[src_idx]) |
313 (static_cast<uint16_t>(pixel_data[src_idx + 1]) << 8);
314 value = params.is_signed()
315 ? static_cast<OPJ_INT32>(static_cast<int16_t>(raw))
316 : static_cast<OPJ_INT32>(raw);
317 }
318 comp_data[i] = value;
319 }
320 }
321
322 // Create encoder
323 opj_codec_t* codec = opj_create_compress(OPJ_CODEC_J2K);
324 if (!codec) {
325 opj_image_destroy(image);
327 }
328
329 // Set up error handling
330 std::string error_msg;
331 opj_set_error_handler(codec, opj_error_callback, &error_msg);
332 opj_set_warning_handler(codec, opj_warning_callback, nullptr);
333 opj_set_info_handler(codec, opj_info_callback, nullptr);
334
335 // Set encoding parameters
336 opj_cparameters_t parameters;
337 opj_set_default_encoder_parameters(&parameters);
338
339 parameters.tcp_numlayers = 1;
340 parameters.cp_disto_alloc = 1;
341 parameters.numresolution = resolution_levels_;
342
343 if (use_lossless) {
344 // Lossless: use reversible 5/3 wavelet
345 parameters.irreversible = 0;
346 parameters.tcp_rates[0] = 0; // 0 = lossless
347 } else {
348 // Lossy: use irreversible 9/7 wavelet
349 parameters.irreversible = 1;
350
351 // Convert quality (1-100) to compression ratio
352 float ratio = compression_ratio_;
353 if (options.quality > 0 && options.quality <= 100) {
354 // Map quality 100->2 (near lossless), quality 1->100 (high compression)
355 ratio = 2.0f + (100.0f - static_cast<float>(options.quality)) * 0.98f;
356 }
357 parameters.tcp_rates[0] = ratio;
358 }
359
360 // Setup encoder with parameters
361 if (!opj_setup_encoder(codec, &parameters, image)) {
362 opj_destroy_codec(codec);
363 opj_image_destroy(image);
364 return kcenon::pacs::pacs_error<compression_result>(kcenon::pacs::error_codes::decompression_error, "Failed to setup OpenJPEG encoder: " + error_msg);
365 }
366
367 // Create output stream
368 opj_output_buffer output_buf;
369 output_buf.offset = 0;
370 output_buf.data.reserve(pixel_data.size() / 2); // Estimate compressed size
371
372 opj_stream_t* stream = opj_stream_default_create(OPJ_FALSE);
373 if (!stream) {
374 opj_destroy_codec(codec);
375 opj_image_destroy(image);
377 }
378
379 opj_stream_set_user_data(stream, &output_buf, nullptr);
380 opj_stream_set_user_data_length(stream, 0);
381 opj_stream_set_write_function(stream, opj_output_buffer_write);
382 opj_stream_set_skip_function(stream, opj_output_buffer_skip);
383 opj_stream_set_seek_function(stream, opj_output_buffer_seek);
384
385 // Encode
386 if (!opj_start_compress(codec, image, stream)) {
387 opj_stream_destroy(stream);
388 opj_destroy_codec(codec);
389 opj_image_destroy(image);
390 return kcenon::pacs::pacs_error<compression_result>(kcenon::pacs::error_codes::decompression_error, "Failed to start JPEG 2000 encoding: " + error_msg);
391 }
392
393 if (!opj_encode(codec, stream)) {
394 opj_stream_destroy(stream);
395 opj_destroy_codec(codec);
396 opj_image_destroy(image);
397 return kcenon::pacs::pacs_error<compression_result>(kcenon::pacs::error_codes::decompression_error, "Failed to encode JPEG 2000 data: " + error_msg);
398 }
399
400 if (!opj_end_compress(codec, stream)) {
401 opj_stream_destroy(stream);
402 opj_destroy_codec(codec);
403 opj_image_destroy(image);
404 return kcenon::pacs::pacs_error<compression_result>(kcenon::pacs::error_codes::decompression_error, "Failed to finalize JPEG 2000 encoding: " + error_msg);
405 }
406
407 // Cleanup
408 opj_stream_destroy(stream);
409 opj_destroy_codec(codec);
410 opj_image_destroy(image);
411
412 // Trim output buffer to actual size
413 output_buf.data.resize(output_buf.offset);
414
415 return kcenon::pacs::ok<compression_result>(compression_result{std::move(output_buf.data), params});
416#endif // PACS_WITH_JPEG2000_CODEC
417 }
constexpr dicom_tag pixel_data
Pixel Data.

References kcenon::pacs::encoding::compression::image_params::bits_allocated, kcenon::pacs::encoding::compression::image_params::bits_stored, compression_ratio_, kcenon::pacs::error_codes::decompression_error, kcenon::pacs::encoding::compression::image_params::height, kcenon::pacs::encoding::compression::image_params::is_signed(), lossless_, kcenon::pacs::pacs_error(), kcenon::pacs::encoding::compression::image_params::photometric, kcenon::pacs::encoding::compression::image_params::planar_configuration, resolution_levels_, kcenon::pacs::encoding::compression::rgb, kcenon::pacs::encoding::compression::image_params::samples_per_pixel, and kcenon::pacs::encoding::compression::image_params::width.

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

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

◆ is_lossless_mode()

bool kcenon::pacs::encoding::compression::jpeg2000_codec::impl::is_lossless_mode ( ) const
inlinenodiscardnoexcept

◆ resolution_levels()

int kcenon::pacs::encoding::compression::jpeg2000_codec::impl::resolution_levels ( ) const
inlinenodiscardnoexcept

Definition at line 229 of file jpeg2000_codec.cpp.

229 {
230 return resolution_levels_;
231 }

References resolution_levels_.

Referenced by kcenon::pacs::encoding::compression::jpeg2000_codec::resolution_levels().

Here is the caller graph for this function:

Member Data Documentation

◆ compression_ratio_

float kcenon::pacs::encoding::compression::jpeg2000_codec::impl::compression_ratio_
private

Definition at line 573 of file jpeg2000_codec.cpp.

Referenced by compression_ratio(), and encode().

◆ lossless_

bool kcenon::pacs::encoding::compression::jpeg2000_codec::impl::lossless_
private

Definition at line 572 of file jpeg2000_codec.cpp.

Referenced by encode(), and is_lossless_mode().

◆ resolution_levels_

int kcenon::pacs::encoding::compression::jpeg2000_codec::impl::resolution_levels_
private

Definition at line 574 of file jpeg2000_codec.cpp.

Referenced by encode(), and resolution_levels().


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