Skip to content

Commit

Permalink
refactor!: Improve API (#117)
Browse files Browse the repository at this point in the history
* refactor: Improve Interface

* refactor: Improve DeepZoom interface

* fix: tests for Openslide 4
  • Loading branch information
AzHicham authored Dec 9, 2023
1 parent e9860df commit 89bfe54
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 172 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ fast_image_resize = { version = "2.7", optional = true}
rstest = "0.18"
assert_approx_eq = "1.1"
bencher = "0.1"
version-compare = "0.1"

[[bench]]
name = "bench_read"
Expand Down
26 changes: 22 additions & 4 deletions benches/bench_read.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use bencher::{benchmark_group, benchmark_main, Bencher};
use openslide_rs::{traits::Slide, Address, DeepZoomGenerator, OpenSlide, Region, Size};
use std::path::Path;
use std::{path::Path, sync::Arc};

fn openslide_read_region_256(bench: &mut Bencher) {
let slide = OpenSlide::new(Path::new("tests/assets/default.svs")).unwrap();
Expand Down Expand Up @@ -52,14 +52,30 @@ fn openslide_read_image_512(bench: &mut Bencher) {

fn deepzoom_read_image_256(bench: &mut Bencher) {
let slide = OpenSlide::new(Path::new("tests/assets/default.svs")).unwrap();
let dz = DeepZoomGenerator::new(&slide, 257, 0, false).unwrap();
let dz: DeepZoomGenerator<OpenSlide, _> =
DeepZoomGenerator::new(&slide, 257, 0, false).unwrap();

bench.iter(|| dz.get_tile_rgb(12, Address { x: 0, y: 0 }));
}

fn deepzoom_read_image_512(bench: &mut Bencher) {
let slide = OpenSlide::new(Path::new("tests/assets/default.svs")).unwrap();
let dz = DeepZoomGenerator::new(&slide, 511, 0, false).unwrap();
let dz: DeepZoomGenerator<OpenSlide, _> =
DeepZoomGenerator::new(&slide, 511, 0, false).unwrap();

bench.iter(|| dz.get_tile_rgb(12, Address { x: 0, y: 0 }));
}

fn deepzoom_read_image_256_arc(bench: &mut Bencher) {
let slide = Arc::new(OpenSlide::new(Path::new("tests/assets/default.svs")).unwrap());
let dz: DeepZoomGenerator<OpenSlide, _> = DeepZoomGenerator::new(slide, 257, 0, false).unwrap();

bench.iter(|| dz.get_tile_rgb(12, Address { x: 0, y: 0 }));
}

fn deepzoom_read_image_512_arc(bench: &mut Bencher) {
let slide = Arc::new(OpenSlide::new(Path::new("tests/assets/default.svs")).unwrap());
let dz: DeepZoomGenerator<OpenSlide, _> = DeepZoomGenerator::new(slide, 511, 0, false).unwrap();

bench.iter(|| dz.get_tile_rgb(12, Address { x: 0, y: 0 }));
}
Expand All @@ -77,6 +93,8 @@ benchmark_group!(
benchmark_group!(
deepzoom_image,
deepzoom_read_image_256,
deepzoom_read_image_512
deepzoom_read_image_512,
deepzoom_read_image_256_arc,
deepzoom_read_image_512_arc
);
benchmark_main!(benches_region, benches_image, deepzoom_image);
10 changes: 10 additions & 0 deletions src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ impl Deref for OpenSlideWrapper {
unsafe impl Send for OpenSlideWrapper {}
unsafe impl Sync for OpenSlideWrapper {}

pub fn get_version() -> Result<String> {
let version = unsafe { sys::openslide_get_version() };
if !version.is_null() {
let vendor = unsafe { ffi::CStr::from_ptr(version).to_string_lossy().into_owned() };
Ok(vendor)
} else {
Err(OpenSlideError::CoreError("Cannot get version".to_string()))
}
}

pub fn detect_vendor(filename: &str) -> Result<String> {
let c_filename = ffi::CString::new(filename)?;
unsafe {
Expand Down
44 changes: 24 additions & 20 deletions src/deepzoom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,16 @@ use crate::{
Address, DeepZoomGenerator, Region, Result, Size,
};
use image::{RgbImage, RgbaImage};
use std::borrow::Borrow;

impl<'a, T: Slide> DeepZoomGenerator<'a, T> {
pub fn new(
slide: &'a T,
tile_size: u32,
overlap: u32,
limit_bounds: bool,
) -> Result<DeepZoomGenerator<T>> {
let nb_level = slide.get_level_count()?;
impl<S: Slide, B: Borrow<S>> DeepZoomGenerator<S, B> {
pub fn new(slide: B, tile_size: u32, overlap: u32, limit_bounds: bool) -> Result<Self> {
let nb_level = slide.borrow().get_level_count()?;

let (slide_level_dimensions, l0_offset) = if limit_bounds {
let os_property = &slide.properties().openslide_properties;
let bounds_x = os_property.bounds_x.unwrap_or(0);
let bounds_y = os_property.bounds_y.unwrap_or(0);
let bounds = slide.borrow().get_bounds();
let bounds_x = bounds.x.unwrap_or(0);
let bounds_y = bounds.y.unwrap_or(0);

// Level 0 coordinate offset

Expand All @@ -32,19 +28,19 @@ impl<'a, T: Slide> DeepZoomGenerator<'a, T> {
};

// Slide level dimensions scale factor in each axis
let slide_dimensions = slide.get_level_dimensions(0)?;
let slide_dimensions = slide.borrow().get_level_dimensions(0)?;
let slide_dimensions = &slide_dimensions;

let bounds_width = os_property.bounds_width.unwrap_or(slide_dimensions.w);
let bounds_height = os_property.bounds_height.unwrap_or(slide_dimensions.h);
let bounds_width = bounds.width.unwrap_or(slide_dimensions.w);
let bounds_height = bounds.height.unwrap_or(slide_dimensions.h);

let size_scale = (
bounds_width as f32 / slide_dimensions.w as f32,
bounds_height as f32 / slide_dimensions.h as f32,
);

let slide_level_dimensions: Result<Vec<Size>> = (0..nb_level)
.map(|level| match slide.get_level_dimensions(level) {
.map(|level| match slide.borrow().get_level_dimensions(level) {
Ok(size) => Ok(Size {
w: (size.w as f32 * size_scale.0).ceil() as _,
h: (size.h as f32 * size_scale.1).ceil() as _,
Expand All @@ -56,7 +52,7 @@ impl<'a, T: Slide> DeepZoomGenerator<'a, T> {
} else {
let l0_offset = Address { x: 0, y: 0 };
let slide_level_dimensions: Result<Vec<Size>> = (0..nb_level)
.map(|level| slide.get_level_dimensions(level))
.map(|level| slide.borrow().get_level_dimensions(level))
.collect();
(slide_level_dimensions?, l0_offset)
};
Expand Down Expand Up @@ -100,13 +96,13 @@ impl<'a, T: Slide> DeepZoomGenerator<'a, T> {
// Preferred slide levels for each Deep Zoom level
let slide_from_dz_level: Result<Vec<u32>> = l0_z_downsamples
.iter()
.map(|downsample| slide.get_best_level_for_downsample(*downsample))
.map(|downsample| slide.borrow().get_best_level_for_downsample(*downsample))
.collect();
let slide_from_dz_level = slide_from_dz_level?;

// Piecewise downsamples
let l0_l_downsamples: Result<Vec<f64>> = (0..nb_level)
.map(|level| slide.get_level_downsample(level))
.map(|level| slide.borrow().get_level_downsample(level))
.collect();
let l0_l_downsamples = l0_l_downsamples?;

Expand All @@ -119,6 +115,7 @@ impl<'a, T: Slide> DeepZoomGenerator<'a, T> {

Ok(DeepZoomGenerator {
slide,
_phantom: Default::default(),
tile_size,
overlap,
l0_offset,
Expand Down Expand Up @@ -151,7 +148,7 @@ impl<'a, T: Slide> DeepZoomGenerator<'a, T> {
pub fn get_tile_rgba(&self, level: u32, location: Address) -> Result<RgbaImage> {
let (region, final_size) = self.get_tile_info(level, location)?;

let image = self.slide.read_image_rgba(&region)?;
let image = self.slide.borrow().read_image_rgba(&region)?;

let size = Size {
w: image.width(),
Expand All @@ -167,7 +164,7 @@ impl<'a, T: Slide> DeepZoomGenerator<'a, T> {

pub fn get_tile_rgb(&self, level: u32, location: Address) -> Result<RgbImage> {
let (region, final_size) = self.get_tile_info(level, location)?;
let image = self.slide.read_image_rgb(&region)?;
let image = self.slide.borrow().read_image_rgb(&region)?;

let size = Size {
w: image.width(),
Expand Down Expand Up @@ -267,3 +264,10 @@ impl<'a, T: Slide> DeepZoomGenerator<'a, T> {
Ok((region, z_size))
}
}

pub struct Bounds {
pub x: Option<u32>,
pub y: Option<u32>,
pub width: Option<u32>,
pub height: Option<u32>,
}
11 changes: 8 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ extern crate lazy_static;
use crate::properties::Properties;

#[cfg(feature = "deepzoom")]
use crate::traits::Slide;
use {
crate::traits::Slide,
std::{borrow::Borrow, marker::PhantomData},
};

mod bindings;
#[cfg(feature = "deepzoom")]
pub mod deepzoom;
pub mod errors;
pub mod properties;
#[cfg(feature = "deepzoom")]
pub mod traits;
mod utils;
mod wrapper;
Expand All @@ -33,8 +37,9 @@ pub struct OpenSlide {
/// Generates Deep Zoom tiles and metadata.
#[cfg(feature = "deepzoom")]
#[derive(Debug)]
pub struct DeepZoomGenerator<'a, T: Slide> {
slide: &'a T,
pub struct DeepZoomGenerator<S: Slide, B: Borrow<S>> {
slide: B,
_phantom: PhantomData<S>,

level_count: usize,
level_tiles: Vec<Size>,
Expand Down
74 changes: 2 additions & 72 deletions src/traits.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::{properties::Properties, Region, Result, Size};
use crate::{deepzoom::Bounds, Region, Result, Size};
#[cfg(feature = "image")]
use image::{RgbImage, RgbaImage};

pub trait Slide {
/// Get properties of the whole slide image through Properties struct.
fn properties(&self) -> &Properties;
fn get_bounds(&self) -> Bounds;

/// Get the number of levels in the whole slide image.
fn get_level_count(&self) -> Result<u32>;
Expand All @@ -15,52 +15,12 @@ pub trait Slide {
/// specified level. Returns an error if the level is invalid
fn get_level_dimensions(&self, level: u32) -> Result<Size>;

/// Get dimensions of all available levels
fn get_all_level_dimensions(&self) -> Result<Vec<Size>>;

/// Get the downsampling factor of a given level.
fn get_level_downsample(&self, level: u32) -> Result<f64>;

/// Get all downsampling factors for all available levels.
fn get_all_level_downsample(&self) -> Result<Vec<f64>>;

/// Get the best level to use for displaying the given downsample factor.
fn get_best_level_for_downsample(&self, downsample: f64) -> Result<u32>;

/// Get the list of all available properties.
fn get_property_names(&self) -> Vec<String>;

/// Get the value of a single property.
fn get_property_value(&self, name: &str) -> Result<String>;

/// Copy pre-multiplied ARGB data from a whole slide image.
///
/// This function reads and decompresses a region of a whole slide image into a Vec
///
/// Args:
/// offset: (x, y) coordinate (increasing downwards/to the right) of top left pixel position
/// level: At which level to grab the region from
/// size: (width, height) in pixels of the outputted region
///
/// Size of output Vec is Width * Height * 4 (RGBA pixels)
fn read_region(&self, region: &Region) -> Result<Vec<u8>>;

/// Get the list name of all available associated image.
fn get_associated_image_names(&self) -> Result<Vec<String>>;

/// Copy pre-multiplied ARGB data from a whole slide image.
///
/// This function reads and decompresses an associated image into an Vec
///
/// Args:
/// name: name of the associated image we want to read
///
/// Size of output Vec is width * height * 4 (RGBA pixels)
fn read_associated_buffer(&self, name: &str) -> Result<(Size, Vec<u8>)>;

/// Get the size of an associated image
fn get_associated_image_dimensions(&self, name: &str) -> Result<Size>;

/// Copy pre-multiplied ARGB data from a whole slide image.
///
/// This function reads and decompresses a region of a whole slide image into an RgbImage
Expand All @@ -82,34 +42,4 @@ pub trait Slide {
/// size: (width, height) in pixels of the outputted region
#[cfg(feature = "image")]
fn read_image_rgb(&self, region: &Region) -> Result<RgbImage>;

/// Copy pre-multiplied ARGB data from an associated image.
///
/// This function reads and decompresses an associated image into an RgbaImage
///
/// Args:
/// name: name of the associated image we want to read
#[cfg(feature = "image")]
fn read_associated_image_rgba(&self, name: &str) -> Result<RgbaImage>;

/// Copy pre-multiplied ARGB data from an associated image.
///
/// This function reads and decompresses an associated image into an RgbaImage
///
/// Args:
/// name: name of the associated image we want to read
#[cfg(feature = "image")]
fn read_associated_image_rgb(&self, name: &str) -> Result<RgbImage>;

/// Get a RGBA image thumbnail of desired size of the whole slide image.
/// Args:
/// size: (width, height) in pixels of the thumbnail
#[cfg(feature = "image")]
fn thumbnail_rgba(&self, size: &Size) -> Result<RgbaImage>;

/// Get a RGB image thumbnail of desired size of the whole slide image.
/// Args:
/// size: (width, height) in pixels of the thumbnail
#[cfg(feature = "image")]
fn thumbnail_rgb(&self, size: &Size) -> Result<RgbImage>;
}
Loading

1 comment on commit 89bfe54

@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: 89bfe54 Previous: f4f03f3 Ratio
deepzoom_read_image_256 1174361 ns/iter (± 8071) 1175396 ns/iter (± 8424) 1.00
deepzoom_read_image_256_arc 1173280 ns/iter (± 7298) 1177040 ns/iter (± 9174) 1.00
deepzoom_read_image_512 4708994 ns/iter (± 37114) 4721882 ns/iter (± 61808) 1.00
deepzoom_read_image_512_arc 4709408 ns/iter (± 67507) 4720731 ns/iter (± 66893) 1.00
openslide_read_image_256 1137540 ns/iter (± 9160) 1139817 ns/iter (± 13815) 1.00
openslide_read_image_512 4734901 ns/iter (± 80415) 4730409 ns/iter (± 54830) 1.00
openslide_read_region_256 1087664 ns/iter (± 9737) 1089906 ns/iter (± 9332) 1.00
openslide_read_region_512 4517678 ns/iter (± 40387) 4526849 ns/iter (± 27220) 1.00

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

Please sign in to comment.