Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JPEG 2000 decoding support #425

Merged
merged 1 commit into from
Oct 30, 2023
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
89 changes: 89 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ resolver = "2"
[profile.dev.package."jpeg-decoder"]
opt-level = 2

# optimize JPEG 2000 decoder to run tests faster
[profile.dev.package.jpeg2k]
opt-level = 2

# optimize flate2 to run tests faster
[profile.dev.package."flate2"]
opt-level = 2
13 changes: 12 additions & 1 deletion pixeldata/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,21 @@ default = ["rayon", "native"]
ndarray = ["dep:ndarray"]
image = ["dep:image"]

native = ["dicom-transfer-syntax-registry/native"]
# Rust native image codec implementations
native = ["dicom-transfer-syntax-registry/native", "jpeg"]
# native JPEG codec implementation
jpeg = ["dicom-transfer-syntax-registry/jpeg"]
# native RLE lossless codec implementation
rle = ["dicom-transfer-syntax-registry/rle"]
# JPEG 2000 decoding via OpenJPEG static linking
openjpeg-sys = ["dicom-transfer-syntax-registry/openjpeg-sys"]

# replace pixel data decoding to use GDCM
gdcm = ["gdcm-rs"]
# use Rayon for image decoding
rayon = ["dep:rayon", "image?/jpeg_rayon", "dicom-transfer-syntax-registry/rayon"]

# enable command line tools
cli = ["dep:clap", "dep:tracing-subscriber"]

[package.metadata.docs.rs]
Expand Down
38 changes: 19 additions & 19 deletions pixeldata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2443,30 +2443,30 @@ mod tests {

#[cfg(feature = "image")]
#[rstest]
// jpeg2000 encoding not supported
#[should_panic(expected = "UnsupportedTransferSyntax { ts: \"1.2.840.10008.1.2.4.91\"")]
#[case("pydicom/693_J2KI.dcm", 1)]
#[should_panic(expected = "UnsupportedTransferSyntax { ts: \"1.2.840.10008.1.2.4.90\"")]
#[case("pydicom/693_J2KR.dcm", 1)]
// jpeg2000 encoding
#[cfg_attr(any(feature = "openjp2", feature = "openjpeg-sys"), case("pydicom/emri_small_jpeg_2k_lossless.dcm", 10))]
#[cfg_attr(any(feature = "openjp2", feature = "openjpeg-sys"), case("pydicom/693_J2KI.dcm", 1))]
#[cfg_attr(any(feature = "openjp2", feature = "openjpeg-sys"), case("pydicom/693_J2KR.dcm", 1))]
#[cfg_attr(any(feature = "openjp2", feature = "openjpeg-sys"), case("pydicom/JPEG2000.dcm", 1))]
//
// jpeg-ls encoding not supported
#[should_panic(expected = "UnsupportedTransferSyntax { ts: \"1.2.840.10008.1.2.4.80\"")]
#[case("pydicom/emri_small_jpeg_ls_lossless.dcm", 10)]
#[should_panic(expected = "UnsupportedTransferSyntax { ts: \"1.2.840.10008.1.2.4.80\"")]
#[case("pydicom/MR_small_jpeg_ls_lossless.dcm", 1)]
//
// sample precicion of 12 not supported
// sample precision of 12 not supported yet
#[should_panic(expected = "Unsupported(SamplePrecision(12))")]
#[case("pydicom/JPEG-lossy.dcm", 1)]
//
// works fine
#[case("pydicom/color3d_jpeg_baseline.dcm", 120)]
// JPEG baseline (8bit)
#[cfg_attr(feature = "jpeg", case("pydicom/color3d_jpeg_baseline.dcm", 120))]
#[cfg_attr(feature = "jpeg", case("pydicom/SC_rgb_jpeg_lossy_gdcm.dcm", 1))]
#[cfg_attr(feature = "jpeg", case("pydicom/SC_rgb_jpeg_gdcm.dcm", 1))]
//
// works fine
#[case("pydicom/JPEG-LL.dcm", 1)]
#[case("pydicom/JPGLosslessP14SV1_1s_1f_8b.dcm", 1)]
#[case("pydicom/SC_rgb_jpeg_gdcm.dcm", 1)]
#[case("pydicom/SC_rgb_jpeg_lossy_gdcm.dcm", 1)]
// JPEG lossless
#[cfg_attr(feature = "jpeg", case("pydicom/JPEG-LL.dcm", 1))]
#[cfg_attr(feature = "jpeg", case("pydicom/JPGLosslessP14SV1_1s_1f_8b.dcm", 1))]

fn test_parse_jpeg_encoded_dicom_pixel_data(#[case] value: &str, #[case] frames: u32) {
use std::fs;
Expand All @@ -2476,15 +2476,15 @@ mod tests {
println!("Parsing pixel data for {}", test_file.display());
let obj = open_file(test_file).unwrap();
let pixel_data = obj.decode_pixel_data().unwrap();
assert_eq!(pixel_data.number_of_frames(), frames);
assert_eq!(pixel_data.number_of_frames(), frames, "number of frames mismatch");

let output_dir = Path::new(
"../target/dicom_test_files/_out/test_parse_jpeg_encoded_dicom_pixel_data",
);
fs::create_dir_all(output_dir).unwrap();

for i in 0..pixel_data.number_of_frames().min(MAX_TEST_FRAMES) {
let image = pixel_data.to_dynamic_image(i).unwrap();
let image = pixel_data.to_dynamic_image(i).expect("failed to retrieve the frame requested");
let image_path = output_dir.join(format!(
"{}-{}.png",
Path::new(value).file_stem().unwrap().to_str().unwrap(),
Expand All @@ -2496,10 +2496,10 @@ mod tests {

#[cfg(feature = "image")]
#[rstest]
#[case("pydicom/color3d_jpeg_baseline.dcm", 0)]
#[case("pydicom/color3d_jpeg_baseline.dcm", 1)]
#[case("pydicom/color3d_jpeg_baseline.dcm", 78)]
#[case("pydicom/color3d_jpeg_baseline.dcm", 119)]
#[cfg_attr(feature = "jpeg", case("pydicom/color3d_jpeg_baseline.dcm", 0))]
#[cfg_attr(feature = "jpeg", case("pydicom/color3d_jpeg_baseline.dcm", 1))]
#[cfg_attr(feature = "jpeg", case("pydicom/color3d_jpeg_baseline.dcm", 78))]
#[cfg_attr(feature = "jpeg", case("pydicom/color3d_jpeg_baseline.dcm", 119))]
#[case("pydicom/SC_rgb_rle_2frame.dcm", 0)]
#[case("pydicom/SC_rgb_rle_2frame.dcm", 1)]
#[case("pydicom/JPEG2000_UNC.dcm", 0)]
Expand Down
4 changes: 2 additions & 2 deletions toimage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ keywords = ["cli", "dicom", "image", "image-conversion"]
readme = "README.md"

[features]
default = ['dicom-object/inventory-registry', 'dicom-object/backtraces']
default = ['dicom-object/inventory-registry', 'dicom-object/backtraces', 'dicom-pixeldata/native']

[dependencies]
clap = { version = "4.0.18", features = ["derive"] }
dicom-core = { version = "0.6.1", path = "../core" }
dicom-dictionary-std = { version = "0.6.0", path = "../dictionary-std" }
dicom-object = { path = "../object/", version = "0.6.1" }
dicom-pixeldata = { path = "../pixeldata/", version = "0.2.0", features = ["image"] }
dicom-pixeldata = { path = "../pixeldata/", version = "0.2.0", default-features = false, features = ["image", "rayon"] }
snafu = { version = "0.7.3", features = ["rust_1_61"] }
tracing = "0.1.34"
tracing-subscriber = "0.3.11"
16 changes: 15 additions & 1 deletion transfer-syntax-registry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,21 @@ default = ["rayon"]
inventory-registry = ['dicom-encoding/inventory-registry']

# natively implemented image encodings
native = ["jpeg", "rle"]
native = ["jpeg", "openjp2", "rle"]
# native JPEG support
jpeg = ["jpeg-decoder", "jpeg-encoder"]
# native JPEG 2000 support via the OpenJPEG Rust port
openjp2 = ["dep:jpeg2k", "jpeg2k/openjp2"]
# native RLE lossless support
rle = []
# enable Rayon for JPEG decoding
rayon = ["jpeg-decoder?/rayon"]
# build OpenJPEG with multithreading
openjpeg-sys-threads = ["rayon", "jpeg2k?/threads"]

# JPEG 2000 support via the OpenJPEG Rust port,
# overrides `openjp2` if present
openjpeg-sys = ["dep:jpeg2k", "jpeg2k/openjpeg-sys"]

[dependencies]
dicom-core = { path = "../core", version = "0.6.1" }
Expand All @@ -30,6 +39,11 @@ lazy_static = "1.2.0"
byteordered = "0.6"
tracing = "0.1.34"

[dependencies.jpeg2k]
version = "0.6.6"
optional = true
default-features = false

[dependencies.jpeg-decoder]
version = "0.3.0"
optional = true
Expand Down
Loading
Loading