Skip to content

Commit

Permalink
feat: Add support for Openslide 4.x
Browse files Browse the repository at this point in the history
  • Loading branch information
AzHicham committed Dec 9, 2023
1 parent 89bfe54 commit 803f6e5
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 2 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ license = "MIT/Apache-2.0"
default = ["deepzoom"]
deepzoom = ["image"]
image = ["dep:image", "dep:fast_image_resize"]
openslide4 = []

[dependencies]
libc = "0.2"
Expand Down
99 changes: 98 additions & 1 deletion src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use openslide_sys::sys;

/// wrapper around OpenSlideT, this is usefull for implementing Send and Sync
#[derive(Debug)]
pub(crate) struct OpenSlideWrapper(pub *mut sys::openslide_t);
pub(crate) struct OpenSlideWrapper(pub(crate) *mut sys::openslide_t);

impl Deref for OpenSlideWrapper {
type Target = *mut sys::openslide_t;
Expand All @@ -24,6 +24,22 @@ impl Deref for OpenSlideWrapper {
unsafe impl Send for OpenSlideWrapper {}
unsafe impl Sync for OpenSlideWrapper {}

#[cfg(feature = "openslide4")]
#[derive(Debug)]
pub(crate) struct CacheWrapper(pub(crate) *mut sys::openslide_cache_t);

#[cfg(feature = "openslide4")]
impl Deref for CacheWrapper {
type Target = *mut sys::openslide_cache_t;

fn deref(&self) -> &Self::Target {
&self.0
}
}

#[cfg(feature = "openslide4")]
unsafe impl Send for CacheWrapper {}

pub fn get_version() -> Result<String> {
let version = unsafe { sys::openslide_get_version() };
if !version.is_null() {
Expand Down Expand Up @@ -256,3 +272,84 @@ pub fn get_error(osr: *mut sys::openslide_t) -> Result<()> {
};
value
}

#[cfg(feature = "openslide4")]
pub fn get_icc_profile_size(osr: *mut sys::openslide_t) -> Result<i64> {
let size = unsafe { sys::openslide_get_icc_profile_size(osr) };
// TODO: check if size == 0 => no ICC profile
if size == -1 {
get_error(osr)?;
return Err(OpenSlideError::CoreError(
"Cannot get ICC profile size".to_string(),
));
}
Ok(size)
}

#[cfg(feature = "openslide4")]
pub fn read_icc_profile(osr: *mut sys::openslide_t) -> Result<Vec<u8>> {
let size = get_icc_profile_size(osr)?;
let mut buffer: Vec<u8> = Vec::with_capacity(size as usize);
let p_buffer = buffer.as_mut_ptr() as *mut std::os::raw::c_void;
unsafe {
sys::openslide_read_icc_profile(osr, p_buffer);
get_error(osr)?;
buffer.set_len(size as usize);
}
Ok(buffer)
}

#[cfg(feature = "openslide4")]
pub fn get_associated_image_icc_profile_size(
osr: *mut sys::openslide_t,
name: &str,
) -> Result<i64> {
let c_name = ffi::CString::new(name)?;
let size =
unsafe { sys::openslide_get_associated_image_icc_profile_size(osr, c_name.as_ptr()) };
// TODO: check if size == 0 => no ICC profile
if size == -1 {
get_error(osr)?;
return Err(OpenSlideError::CoreError(
"Cannot get ICC profile size".to_string(),
));
}
Ok(size)
}

#[cfg(feature = "openslide4")]
pub fn read_associated_image_icc_profile(
osr: *mut sys::openslide_t,
name: &str,
) -> Result<Vec<u8>> {
let c_name = ffi::CString::new(name)?;
let size = get_associated_image_icc_profile_size(osr, name)?;
let mut buffer: Vec<u8> = Vec::with_capacity(size as usize);
let p_buffer = buffer.as_mut_ptr() as *mut std::os::raw::c_void;
unsafe {
sys::openslide_read_associated_image_icc_profile(osr, c_name.as_ptr(), p_buffer);
get_error(osr)?;
buffer.set_len(size as usize);
}
Ok(buffer)
}

#[cfg(feature = "openslide4")]
pub fn cache_create(capacity: usize) -> Result<*mut sys::openslide_cache_t> {
let cache = unsafe { sys::openslide_cache_create(capacity) };
if cache.is_null() {
Err(OpenSlideError::CoreError("Cannot create cache".to_string()))
} else {
Ok(cache)
}
}

#[cfg(feature = "openslide4")]
pub fn set_cache(osr: *mut sys::openslide_t, cache: *mut sys::openslide_cache_t) {
unsafe { sys::openslide_set_cache(osr, cache) };
}

#[cfg(feature = "openslide4")]
pub fn cache_release(cache: *mut sys::openslide_cache_t) {
unsafe { sys::openslide_cache_release(cache) };
}
18 changes: 18 additions & 0 deletions src/cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use crate::{bindings, Result};

#[derive(Debug)]
pub(crate) struct Cache(pub(crate) bindings::CacheWrapper);

impl Drop for Cache {
fn drop(&mut self) {
bindings::cache_release(*self.0);
}
}

impl Cache {
/// Create a new cache with the given capacity
pub fn new(capacity: usize) -> Result<Cache> {
let cache = bindings::CacheWrapper(bindings::cache_create(capacity)?);
Ok(Cache(cache))
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use {
};

mod bindings;
#[cfg(feature = "openslide4")]
mod cache;
#[cfg(feature = "deepzoom")]
pub mod deepzoom;
pub mod errors;
Expand Down
3 changes: 3 additions & 0 deletions src/properties/openslide.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub const OPENSLIDE_PROPERTY_NAME_BOUNDS_Y: &str = "openslide.bounds-y";
pub const OPENSLIDE_PROPERTY_NAME_BOUNDS_WIDTH: &str = "openslide.bounds-width";
pub const OPENSLIDE_PROPERTY_NAME_BOUNDS_HEIGHT: &str = "openslide.bounds-height";
pub const OPENSLIDE_PROPERTY_LEVEL_COUNT: &str = "openslide.level-count";
pub const OPENSLIDE_PROPERTY_NAME_ICC_SIZE: &str = "openslide.icc-size";

const OPENSLIDE_PROPERTY_LEVEL_DOWNSAMPLE: &str = "downsample";
const OPENSLIDE_PROPERTY_LEVEL_HEIGHT: &str = "height";
Expand Down Expand Up @@ -52,6 +53,7 @@ pub struct OpenSlide {
pub bounds_y: Option<u32>,
pub bounds_width: Option<u32>,
pub bounds_height: Option<u32>,
pub icc_profile_size: Option<u32>,
pub background_color: Option<String>,
pub levels: Vec<LevelProperties>,
}
Expand Down Expand Up @@ -81,6 +83,7 @@ impl OpenSlide {
OPENSLIDE_PROPERTY_LEVEL_COUNT => self.level_count = value.parse().ok(),
OPENSLIDE_PROPERTY_NAME_BOUNDS_X => self.bounds_x = value.parse().ok(),
OPENSLIDE_PROPERTY_NAME_BOUNDS_Y => self.bounds_y = value.parse().ok(),
OPENSLIDE_PROPERTY_NAME_ICC_SIZE => self.icc_profile_size = value.parse().ok(),
OPENSLIDE_PROPERTY_NAME_BOUNDS_WIDTH => self.bounds_width = value.parse().ok(),
OPENSLIDE_PROPERTY_NAME_BOUNDS_HEIGHT => self.bounds_height = value.parse().ok(),
OPENSLIDE_PROPERTY_NAME_BACKGROUND_COLOR => {
Expand Down
28 changes: 27 additions & 1 deletion src/wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ use {
image::{RgbImage, RgbaImage},
};

#[cfg(feature = "openslide4")]
use crate::cache::Cache;

impl Drop for OpenSlide {
fn drop(&mut self) {
bindings::close(*self.osr)
Expand All @@ -27,7 +30,8 @@ impl OpenSlide {
/// This function can be expensive; avoid calling it unnecessarily. For example, a tile server
/// should not create a new object on every tile request. Instead, it should maintain a cache
/// of OpenSlide objects and reuse them when possible.
pub fn new(path: &Path) -> Result<OpenSlide> {
pub fn new<T: AsRef<Path>>(path: T) -> Result<OpenSlide> {
let path = path.as_ref();
if !path.exists() {
return Err(OpenSlideError::MissingFile(path.display().to_string()));
}
Expand All @@ -53,6 +57,18 @@ impl OpenSlide {
})
}

#[cfg(feature = "openslide4")]
pub fn new_with_cache<T: AsRef<Path>>(path: T, capacity: usize) -> Result<OpenSlide> {
let osr = OpenSlide::new(path)?;
osr.set_cache(Cache::new(capacity)?);
Ok(osr)
}

#[cfg(feature = "openslide4")]
fn set_cache(&self, cache: Cache) {
bindings::set_cache(*self.osr, *cache.0)
}

/// Quickly determine whether a whole slide image is recognized.
pub fn detect_vendor(path: &Path) -> Result<String> {
if !path.exists() {
Expand Down Expand Up @@ -298,6 +314,16 @@ impl OpenSlide {

Ok(image)
}

#[cfg(feature = "openslide4")]
pub fn icc_profile(&self) -> Result<Vec<u8>> {
bindings::read_icc_profile(*self.osr)
}

#[cfg(feature = "openslide4")]
pub fn associated_image_icc_profile(&self, name: &str) -> Result<Vec<u8>> {
bindings::read_associated_image_icc_profile(*self.osr, name)
}
}

#[cfg(feature = "deepzoom")]
Expand Down
29 changes: 29 additions & 0 deletions tests/openslide.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ use fixture::{boxes_tiff, missing_file, small_svs, unopenable_tiff, unsupported_
use openslide_rs::{Address, OpenSlide, Region, Size};
use rstest::rstest;
use std::path::Path;
use version_compare::Version;

mod fixture;

#[rstest]
fn test_version() {
let version = OpenSlide::get_version().expect("Failed to get version");
Version::from(&version).expect("Failed to parse version");
}

#[rstest]
#[should_panic(expected = "MissingFile(\"missing_file\")")]
#[case(missing_file())]
Expand Down Expand Up @@ -259,3 +266,25 @@ fn test_error_thumbnail(#[case] filename: &Path) {
let size = Size { w: 100, h: 0 };
slide.thumbnail_rgb(&size).unwrap();
}

#[rstest]
#[cfg(feature = "openslide4")]
#[case(small_svs())]
fn test_icc_profile(#[case] filename: &Path) {
let slide = OpenSlide::new(filename).unwrap();

let icc_profile = slide.icc_profile().unwrap();
dbg!(icc_profile.len());
dbg!(icc_profile);
}

#[rstest]
#[cfg(feature = "openslide4")]
#[case(small_svs())]
fn test_associated_image_icc_profile(#[case] filename: &Path) {
let slide = OpenSlide::new(filename).unwrap();

let icc_profile = slide.associated_image_icc_profile("thumbnail").unwrap();
dbg!(icc_profile.len());
dbg!(icc_profile);
}

1 comment on commit 803f6e5

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

openslide-rs Benchmark

Benchmark suite Current: 803f6e5 Previous: f4f03f3 Ratio
deepzoom_read_image_256 1173545 ns/iter (± 6896) 1171932 ns/iter (± 10596) 1.00
deepzoom_read_image_256_arc 1172990 ns/iter (± 50788) 1173551 ns/iter (± 11095) 1.00
deepzoom_read_image_512 4701604 ns/iter (± 40618) 4701862 ns/iter (± 28691) 1.00
deepzoom_read_image_512_arc 4717565 ns/iter (± 392903) 4700822 ns/iter (± 27526) 1.00
openslide_read_image_256 1137509 ns/iter (± 15691) 1135693 ns/iter (± 9663) 1.00
openslide_read_image_512 4726024 ns/iter (± 269493) 4715786 ns/iter (± 48427) 1.00
openslide_read_region_256 1084990 ns/iter (± 11702) 1085155 ns/iter (± 4602) 1.00
openslide_read_region_512 4510669 ns/iter (± 23880) 4509799 ns/iter (± 66607) 1.00

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.