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

refactor!: Improve API #117

Merged
merged 3 commits into from
Dec 9, 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
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 @@
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()))

Check warning on line 33 in src/bindings.rs

View check run for this annotation

Codecov / codecov/patch

src/bindings.rs#L33

Added line #L33 was not covered by tests
}
}

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
Loading