Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions transfer-syntax-registry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,18 @@ inventory-registry = ['dicom-encoding/inventory-registry']
deflate = ["dep:flate2"]
# natively implemented image encodings
native = ["jpeg", "rle"]
# native implementations that work on Windows
# (DEPRECATED) native implementations that work on Windows
native_windows = ["jpeg", "rle"]
# native JPEG support
jpeg = ["jpeg-decoder", "jpeg-encoder"]
# native JPEG XL support
jpegxl = ["dep:jxl-oxide", "dep:zune-jpegxl", "dep:zune-core"]


# native JPEG XL decoding via jxl-oxide
jxl-oxide = ["dep:jxl-oxide"]
# native JPEG XL encoding via zune-jpegxl
zune-jpegxl = ["dep:zune-jpegxl", "dep:zune-core"]

jpegxl = ["jxl-oxide", "zune-jpegxl"]

# JPEG 2000 support via the OpenJPEG Rust port,
# works on Linux and a few other platforms
Expand Down
19 changes: 13 additions & 6 deletions transfer-syntax-registry/src/adapters/jpegxl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,28 @@

use dicom_core::ops::{AttributeAction, AttributeOp};
use dicom_core::Tag;
use dicom_encoding::adapters::{
decode_error, encode_error, DecodeResult, PixelDataObject, PixelDataReader, PixelDataWriter,
};
use dicom_encoding::adapters::PixelDataObject;
#[cfg(feature = "jxl-oxide")]
use dicom_encoding::adapters::{decode_error, DecodeResult, PixelDataReader};
#[cfg(feature = "zune-jpegxl")]
use dicom_encoding::adapters::{encode_error, PixelDataWriter};

use dicom_encoding::snafu::prelude::*;
#[cfg(feature = "jxl-oxide")]
use jxl_oxide::JxlImage;
use zune_core::bit_depth::BitDepth;
use zune_core::colorspace::ColorSpace;
use zune_core::options::EncoderOptions;
#[cfg(feature = "zune-jpegxl")]
use zune_core::{bit_depth::BitDepth, colorspace::ColorSpace, options::EncoderOptions};

/// Base pixel data adapter (decoder and encoder) for transfer syntaxes based on JPEG XL.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct JpegXlAdapter;

/// Pixel data encoder specifically for JPEG XL lossless compression.
#[cfg(feature = "zune-jpegxl")]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct JpegXlLosslessEncoder;

#[cfg(feature = "jxl-oxide")]
impl PixelDataReader for JpegXlAdapter {
/// Decode a single frame in JPEG XL from a DICOM object.
fn decode_frame(
Expand Down Expand Up @@ -152,6 +157,7 @@ impl PixelDataReader for JpegXlAdapter {
}
}

#[cfg(feature = "zune-jpegxl")]
impl PixelDataWriter for JpegXlAdapter {
fn encode_frame(
&self,
Expand Down Expand Up @@ -281,6 +287,7 @@ impl PixelDataWriter for JpegXlAdapter {
}
}

#[cfg(feature = "zune-jpegxl")]
impl PixelDataWriter for JpegXlLosslessEncoder {
fn encode_frame(
&self,
Expand Down
31 changes: 17 additions & 14 deletions transfer-syntax-registry/src/adapters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,38 @@
//! Additional support for certain transfer syntaxes
//! can be added via Cargo features.
//!
//! - [`jpeg`](jpeg) provides native JPEG decoding
//! - [`jpeg`] provides native JPEG decoding
//! (baseline and lossless)
//! and encoding (baseline).
//! Requires the `jpeg` feature,
//! enabled by default.
//! - [`jpeg2k`](jpeg2k) contains JPEG 2000 support,
//! - [`jpeg2k`] contains JPEG 2000 support,
//! which is currently available through [OpenJPEG].
//! The `openjp2` feature provides native JPEG 2000 decoding
//! Use feature `openjpeg-sys`
//! to statically link to the OpenJPEG reference implementation,
//! thus providing JPEG 2000 decoding.
//! Alternatively, feature `openjp2` provides native JPEG 2000 decoding
//! via the [Rust port of OpenJPEG][OpenJPEG-rs],
//! which works on Linux and Mac OS, but not on Windows.
//! Alternatively, enable the `openjpeg-sys` feature
//! to statically link to the OpenJPEG reference implementation.
//! `openjp2` is enabled by the feature `native`.
//! To build on Windows, enable `native_windows` instead.
//! - [`jpegxl`](jpegxl) provides JPEG XL decoding and encoding,
//! through `jxl-oxide` and `zune-jpegxl`, respectively.
//! - [`rle_lossless`](rle_lossless) provides native RLE lossless decoding.
//! which is maintained separately.
//! - [`jpegxl`] contains JPEG XL support.
//! `jxl-oxide` enables decoding via [jxl-oxide],
//! and `zune-jpegxl` adds lossless encoding via [zune-jpegxl].
//! Currently, the `jpegxl` feature enables both.
//! - [`rle_lossless`] provides native RLE lossless decoding.
//! Requires the `rle` feature,
//! enabled by default.
//!
//! [OpenJPEG]: https://github.com/uclouvain/openjpeg
//! [OpenJPEG-rs]: https://crates.io/crates/openjp2
//! [jxl-oxide]: https://crates.io/crates/jxl-oxide
//! [zune-jpegxl]: https://crates.io/crates/zune-jpegxl
#[cfg(feature = "jpeg")]
pub mod jpeg;
#[cfg(any(feature = "openjp2", feature = "openjpeg-sys"))]
pub mod jpeg2k;
#[cfg(feature = "charls")]
pub mod jpegls;
#[cfg(feature = "jpegxl")]
#[cfg(any(feature = "jxl-oxide", feature = "zune-jpegxl"))]
pub mod jpegxl;
#[cfg(feature = "rle")]
pub mod rle_lossless;
Expand Down Expand Up @@ -59,6 +62,6 @@ pub mod rle {}
pub mod jpegls {}

/// **Note:** This module is a stub.
/// Enable the `jpegxl` feature to use this module.
#[cfg(not(feature = "jpegxl"))]
/// Enable the features `jxl-oxide` or `zune-jpegxl` to use this module.
#[cfg(not(any(feature = "jxl-oxide", feature = "zune-jpegxl")))]
pub mod jpegxl {}
9 changes: 4 additions & 5 deletions transfer-syntax-registry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@
//! | High-Throughput JPEG 2000 with RPCL Options (Lossless Only) | Cargo feature `openjp2` or `openjpeg-sys` | x |
//! | High-Throughput JPEG 2000 | Cargo feature `openjp2` or `openjpeg-sys` | x |
//! | JPIP HTJ2K Referenced Deflate | Cargo feature `deflate` | ✓ |
//! | JPEG XL Lossless | Cargo feature `jpegxl` | ✓ |
//! | JPEG XL Recompression | Cargo feature `jpegxl` | x |
//! | JPEG XL | Cargo feature `jpegxl` | ✓ |
//! | JPEG XL Lossless | Cargo feature `jxl-oxide` | ✓ (Cargo feature `zune-jpegxl`) |
//! | JPEG XL Recompression | Cargo feature `jxl-oxide` | x |
//! | JPEG XL | Cargo feature `jxl-oxide` | ✓ (Cargo feature `zune-jpegxl`) |
//! | RLE Lossless | Cargo feature `rle` | x |
//!
//! Cargo features behind `native` (`jpeg`, `rle`) are added by default.
Expand All @@ -92,8 +92,7 @@
//! - `openjp2` provides a binding to a computer-translated Rust port of OpenJPEG.
//! Due to the nature of this crate,
//! it might not work on all modern platforms.
//! - `jpegxl` adds JPEG XL support using `jxl-oxide` for decoding
//! and `zune-jpegxl` for encoding.
//! - `jxl-oxide` and `zune-jpegxl` offer JPEG XL decoding and encoding.
//!
//! Transfer syntaxes which are not supported,
//! either due to being unable to read the data set
Expand Down
25 changes: 16 additions & 9 deletions transfer-syntax-registry/tests/jpegxl.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Test suite for JPEG XL pixel data reading and writing
#![cfg(feature = "jpegxl")]
#![cfg(any(feature = "jxl-oxide", feature = "zune-jpegxl"))]

mod adapters;

Expand All @@ -11,12 +11,14 @@ use std::{

use adapters::TestDataObject;
use dicom_core::value::PixelFragmentSequence;
use dicom_encoding::{
adapters::{EncodeOptions, PixelDataReader, PixelDataWriter},
Codec,
};
use dicom_transfer_syntax_registry::entries::{JPEG_XL, JPEG_XL_LOSSLESS};
use dicom_encoding::Codec;

#[cfg(feature = "jxl-oxide")]
use dicom_encoding::adapters::PixelDataReader;
#[cfg(feature = "zune-jpegxl")]
use dicom_encoding::adapters::{EncodeOptions, PixelDataWriter};

#[cfg(feature = "jxl-oxide")]
fn read_data_piece(test_file: impl AsRef<Path>, offset: u64, length: usize) -> Vec<u8> {
let mut file = File::open(test_file).unwrap();
// single fragment found in file data offset 0x6b6, 3314 bytes
Expand All @@ -26,6 +28,7 @@ fn read_data_piece(test_file: impl AsRef<Path>, offset: u64, length: usize) -> V
buf
}

#[cfg(feature = "jxl-oxide")]
fn check_rgb_pixel(pixels: &[u8], columns: u16, x: u16, y: u16, expected_pixel: [u8; 3]) {
let i = (y as usize * columns as usize + x as usize) * 3;
let got = [pixels[i], pixels[i + 1], pixels[i + 2]];
Expand All @@ -35,6 +38,7 @@ fn check_rgb_pixel(pixels: &[u8], columns: u16, x: u16, y: u16, expected_pixel:
);
}

#[cfg(feature = "jxl-oxide")]
fn check_rgb_pixel_approx(pixels: &[u8], columns: u16, x: u16, y: u16, pixel: [u8; 3], margin: u8) {
let i = (y as usize * columns as usize + x as usize) * 3;

Expand All @@ -59,6 +63,7 @@ fn check_rgb_pixel_approx(pixels: &[u8], columns: u16, x: u16, y: u16, pixel: [u
);
}

#[cfg(feature = "jxl-oxide")]
#[test]
#[ignore]
fn read_jpeg_xl_1() {
Expand Down Expand Up @@ -86,7 +91,7 @@ fn read_jpeg_xl_1() {

// instantiate JpegAdapter and call decode_frame

let Codec::EncapsulatedPixelData(Some(adapter), _) = JPEG_XL.codec() else {
let Codec::EncapsulatedPixelData(Some(adapter), _) = dicom_transfer_syntax_registry::entries::JPEG_XL.codec() else {
panic!("JPEG XL pixel data reader not found")
};

Expand Down Expand Up @@ -114,6 +119,7 @@ fn read_jpeg_xl_1() {
check_rgb_pixel_approx(&dest, 100, 16, 49, [4, 4, 226], err_margin);
}

#[cfg(feature = "jxl-oxide")]
#[test]
#[ignore]
fn read_jpeg_xl_lossless_1() {
Expand Down Expand Up @@ -141,7 +147,7 @@ fn read_jpeg_xl_lossless_1() {

// instantiate JpegAdapter and call decode_frame

let Codec::EncapsulatedPixelData(Some(adapter), _) = JPEG_XL.codec() else {
let Codec::EncapsulatedPixelData(Some(adapter), _) = dicom_transfer_syntax_registry::entries::JPEG_XL.codec() else {
panic!("JPEG XL pixel data reader not found")
};

Expand All @@ -168,6 +174,7 @@ fn read_jpeg_xl_lossless_1() {
}

/// writing to JPEG XL lossless and back should yield exactly the same pixel data
#[cfg(all(feature = "jxl-oxide", feature = "zune-jpegxl"))]
#[test]
fn write_and_read_jpeg_xl() {
let rows: u16 = 128;
Expand Down Expand Up @@ -219,7 +226,7 @@ fn write_and_read_jpeg_xl() {

// fetch adapters for JPEG XL lossless

let Codec::EncapsulatedPixelData(Some(reader), Some(writer)) = JPEG_XL_LOSSLESS.codec() else {
let Codec::EncapsulatedPixelData(Some(reader), Some(writer)) = dicom_transfer_syntax_registry::entries::JPEG_XL_LOSSLESS.codec() else {
panic!("JPEG XL pixel data adapters not found")
};

Expand Down