diff --git a/Cargo.lock b/Cargo.lock index dcdfb0d..b7dab2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -383,6 +383,7 @@ dependencies = [ "regex", "rstest", "thiserror", + "version-compare", ] [[package]] @@ -597,6 +598,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + [[package]] name = "which" version = "4.4.2" diff --git a/Cargo.toml b/Cargo.toml index 1f2bef8..3971ddf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/benches/bench_read.rs b/benches/bench_read.rs index ada2dbc..a3e905c 100644 --- a/benches/bench_read.rs +++ b/benches/bench_read.rs @@ -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(); @@ -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 = + 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 = + 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 = 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 = DeepZoomGenerator::new(slide, 511, 0, false).unwrap(); bench.iter(|| dz.get_tile_rgb(12, Address { x: 0, y: 0 })); } @@ -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); diff --git a/src/bindings.rs b/src/bindings.rs index e70661b..5a4ca42 100644 --- a/src/bindings.rs +++ b/src/bindings.rs @@ -24,6 +24,16 @@ impl Deref for OpenSlideWrapper { unsafe impl Send for OpenSlideWrapper {} unsafe impl Sync for OpenSlideWrapper {} +pub fn get_version() -> Result { + 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 { let c_filename = ffi::CString::new(filename)?; unsafe { diff --git a/src/deepzoom.rs b/src/deepzoom.rs index 786adb5..0c849a5 100644 --- a/src/deepzoom.rs +++ b/src/deepzoom.rs @@ -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> { - let nb_level = slide.get_level_count()?; +impl> DeepZoomGenerator { + pub fn new(slide: B, tile_size: u32, overlap: u32, limit_bounds: bool) -> Result { + 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 @@ -32,11 +28,11 @@ 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, @@ -44,7 +40,7 @@ impl<'a, T: Slide> DeepZoomGenerator<'a, T> { ); let slide_level_dimensions: Result> = (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 _, @@ -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> = (0..nb_level) - .map(|level| slide.get_level_dimensions(level)) + .map(|level| slide.borrow().get_level_dimensions(level)) .collect(); (slide_level_dimensions?, l0_offset) }; @@ -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> = 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> = (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?; @@ -119,6 +115,7 @@ impl<'a, T: Slide> DeepZoomGenerator<'a, T> { Ok(DeepZoomGenerator { slide, + _phantom: Default::default(), tile_size, overlap, l0_offset, @@ -151,7 +148,7 @@ impl<'a, T: Slide> DeepZoomGenerator<'a, T> { pub fn get_tile_rgba(&self, level: u32, location: Address) -> Result { let (region, final_size) = self.get_tile_info(level, location)?; - let image = self.slide.read_image_rgba(®ion)?; + let image = self.slide.borrow().read_image_rgba(®ion)?; let size = Size { w: image.width(), @@ -167,7 +164,7 @@ impl<'a, T: Slide> DeepZoomGenerator<'a, T> { pub fn get_tile_rgb(&self, level: u32, location: Address) -> Result { let (region, final_size) = self.get_tile_info(level, location)?; - let image = self.slide.read_image_rgb(®ion)?; + let image = self.slide.borrow().read_image_rgb(®ion)?; let size = Size { w: image.width(), @@ -267,3 +264,10 @@ impl<'a, T: Slide> DeepZoomGenerator<'a, T> { Ok((region, z_size)) } } + +pub struct Bounds { + pub x: Option, + pub y: Option, + pub width: Option, + pub height: Option, +} diff --git a/src/lib.rs b/src/lib.rs index d64c68a..be2f7ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; @@ -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> { + slide: B, + _phantom: PhantomData, level_count: usize, level_tiles: Vec, diff --git a/src/traits.rs b/src/traits.rs index a21b6f1..a29991e 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -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; @@ -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; - /// Get dimensions of all available levels - fn get_all_level_dimensions(&self) -> Result>; - /// Get the downsampling factor of a given level. fn get_level_downsample(&self, level: u32) -> Result; - /// Get all downsampling factors for all available levels. - fn get_all_level_downsample(&self) -> Result>; - /// Get the best level to use for displaying the given downsample factor. fn get_best_level_for_downsample(&self, downsample: f64) -> Result; - /// Get the list of all available properties. - fn get_property_names(&self) -> Vec; - - /// Get the value of a single property. - fn get_property_value(&self, name: &str) -> Result; - - /// 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>; - - /// Get the list name of all available associated image. - fn get_associated_image_names(&self) -> Result>; - - /// 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)>; - - /// Get the size of an associated image - fn get_associated_image_dimensions(&self, name: &str) -> Result; - /// 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 @@ -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; - - /// 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; - - /// 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; - - /// 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; - - /// 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; } diff --git a/src/wrapper.rs b/src/wrapper.rs index 5e6b34e..c1dc020 100644 --- a/src/wrapper.rs +++ b/src/wrapper.rs @@ -1,8 +1,6 @@ use crate::{bindings, errors::OpenSlideError, OpenSlide, Properties, Region, Result, Size}; use std::path::Path; -use crate::traits::Slide; - #[cfg(feature = "image")] use { crate::{ @@ -19,6 +17,11 @@ impl Drop for OpenSlide { } impl OpenSlide { + /// Get the version of the OpenSlide library. + pub fn get_version() -> Result { + bindings::get_version() + } + /// This method tries to open the slide at the given filename location. /// /// This function can be expensive; avoid calling it unnecessarily. For example, a tile server @@ -58,15 +61,13 @@ impl OpenSlide { let filename = path.display().to_string(); bindings::detect_vendor(&filename) } -} -impl Slide for OpenSlide { - fn properties(&self) -> &Properties { + pub fn properties(&self) -> &Properties { &self.properties } /// Get the number of levels in the whole slide image. - fn get_level_count(&self) -> Result { + pub fn get_level_count(&self) -> Result { let level_count = bindings::get_level_count(*self.osr)?; let level_count: u32 = level_count.try_into()?; Ok(level_count) @@ -76,7 +77,7 @@ impl Slide for OpenSlide { /// /// This method returns the Size { width, height } number of pixels of the whole slide image at the /// specified level. Returns an error if the level is invalid - fn get_level_dimensions(&self, level: u32) -> Result { + pub fn get_level_dimensions(&self, level: u32) -> Result { let level: i32 = level.try_into()?; let (width, height) = bindings::get_level_dimensions(*self.osr, level)?; Ok(Size { @@ -86,7 +87,7 @@ impl Slide for OpenSlide { } /// Get dimensions of all available levels - fn get_all_level_dimensions(&self) -> Result> { + pub fn get_all_level_dimensions(&self) -> Result> { let nb_levels = self.get_level_count()?; let mut res = Vec::with_capacity(nb_levels as usize); for level in 0..nb_levels { @@ -101,13 +102,13 @@ impl Slide for OpenSlide { } /// Get the downsampling factor of a given level. - fn get_level_downsample(&self, level: u32) -> Result { + pub fn get_level_downsample(&self, level: u32) -> Result { let level: i32 = level.try_into()?; bindings::get_level_downsample(*self.osr, level) } /// Get all downsampling factors for all available levels. - fn get_all_level_downsample(&self) -> Result> { + pub fn get_all_level_downsample(&self) -> Result> { let nb_levels = self.get_level_count()?; let mut res = Vec::with_capacity(nb_levels as usize); for level in 0..nb_levels { @@ -118,17 +119,17 @@ impl Slide for OpenSlide { } /// Get the best level to use for displaying the given downsample factor. - fn get_best_level_for_downsample(&self, downsample: f64) -> Result { + pub fn get_best_level_for_downsample(&self, downsample: f64) -> Result { Ok(bindings::get_best_level_for_downsample(*self.osr, downsample)? as u32) } /// Get the list of all available properties. - fn get_property_names(&self) -> Vec { + pub fn get_property_names(&self) -> Vec { bindings::get_property_names(*self.osr).unwrap_or_else(|_| vec![]) } /// Get the value of a single property. - fn get_property_value(&self, name: &str) -> Result { + pub fn get_property_value(&self, name: &str) -> Result { bindings::get_property_value(*self.osr, name) } @@ -142,7 +143,7 @@ impl Slide for OpenSlide { /// 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> { + pub fn read_region(&self, region: &Region) -> Result> { bindings::read_region( *self.osr, region.address.x as i64, @@ -154,7 +155,7 @@ impl Slide for OpenSlide { } /// Get the list name of all available associated image. - fn get_associated_image_names(&self) -> Result> { + pub fn get_associated_image_names(&self) -> Result> { bindings::get_associated_image_names(*self.osr) } @@ -166,7 +167,7 @@ impl Slide for OpenSlide { /// 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)> { + pub fn read_associated_buffer(&self, name: &str) -> Result<(Size, Vec)> { let ((width, height), buffer) = bindings::read_associated_image(*self.osr, name)?; let size = Size { w: width.try_into()?, @@ -176,7 +177,7 @@ impl Slide for OpenSlide { } /// Get the size of an associated image - fn get_associated_image_dimensions(&self, name: &str) -> Result { + pub fn get_associated_image_dimensions(&self, name: &str) -> Result { let (width, height) = bindings::get_associated_image_dimensions(*self.osr, name)?; Ok(Size { w: width.try_into()?, @@ -193,7 +194,7 @@ impl Slide for OpenSlide { /// level: At which level to grab the region from /// size: (width, height) in pixels of the outputted region #[cfg(feature = "image")] - fn read_image_rgba(&self, region: &Region) -> Result { + pub fn read_image_rgba(&self, region: &Region) -> Result { let buffer = self.read_region(region)?; let size = region.size; let mut image = RgbaImage::from_vec(size.w, size.h, buffer).unwrap(); // Should be safe because buffer is big enough @@ -210,7 +211,7 @@ impl Slide for OpenSlide { /// level: At which level to grab the region from /// size: (width, height) in pixels of the outputted region #[cfg(feature = "image")] - fn read_image_rgb(&self, region: &Region) -> Result { + pub fn read_image_rgb(&self, region: &Region) -> Result { let buffer = self.read_region(region)?; let size = region.size; let image = RgbaImage::from_vec(size.w, size.h, buffer).unwrap(); // Should be safe because buffer is big enough @@ -224,7 +225,7 @@ impl Slide for OpenSlide { /// Args: /// name: name of the associated image we want to read #[cfg(feature = "image")] - fn read_associated_image_rgba(&self, name: &str) -> Result { + pub fn read_associated_image_rgba(&self, name: &str) -> Result { let (size, buffer) = self.read_associated_buffer(name)?; let mut image = RgbaImage::from_vec(size.w, size.h, buffer).unwrap(); // Should be safe because buffer is big enough _bgra_to_rgba_inplace(&mut image); @@ -238,7 +239,7 @@ impl Slide for OpenSlide { /// Args: /// name: name of the associated image we want to read #[cfg(feature = "image")] - fn read_associated_image_rgb(&self, name: &str) -> Result { + pub fn read_associated_image_rgb(&self, name: &str) -> Result { let (size, buffer) = self.read_associated_buffer(name)?; let image = RgbaImage::from_vec(size.w, size.h, buffer).unwrap(); // Should be safe because buffer is big enough Ok(_bgra_to_rgb(&image)) @@ -248,7 +249,7 @@ impl Slide for OpenSlide { /// Args: /// size: (width, height) in pixels of the thumbnail #[cfg(feature = "image")] - fn thumbnail_rgba(&self, size: &Size) -> Result { + pub fn thumbnail_rgba(&self, size: &Size) -> Result { let dimension_level0 = self.get_level_dimensions(0)?; let downsample = ( @@ -275,7 +276,7 @@ impl Slide for OpenSlide { /// Args: /// size: (width, height) in pixels of the thumbnail #[cfg(feature = "image")] - fn thumbnail_rgb(&self, size: &Size) -> Result { + pub fn thumbnail_rgb(&self, size: &Size) -> Result { let dimension_level0 = self.get_level_dimensions(0)?; let downsample = ( @@ -298,3 +299,43 @@ impl Slide for OpenSlide { Ok(image) } } + +#[cfg(feature = "deepzoom")] +use {crate::deepzoom::Bounds, crate::traits::Slide}; + +#[cfg(feature = "deepzoom")] +impl Slide for OpenSlide { + fn get_bounds(&self) -> Bounds { + let properties = &self.properties().openslide_properties; + Bounds { + x: properties.bounds_x, + y: properties.bounds_y, + width: properties.bounds_width, + height: properties.bounds_height, + } + } + + fn get_level_count(&self) -> Result { + self.get_level_count() + } + + fn get_level_dimensions(&self, level: u32) -> Result { + self.get_level_dimensions(level) + } + + fn get_level_downsample(&self, level: u32) -> Result { + self.get_level_downsample(level) + } + + fn get_best_level_for_downsample(&self, downsample: f64) -> Result { + self.get_best_level_for_downsample(downsample) + } + + fn read_image_rgba(&self, region: &Region) -> Result { + self.read_image_rgba(region) + } + + fn read_image_rgb(&self, region: &Region) -> Result { + self.read_image_rgb(region) + } +} diff --git a/tests/deepzoom.rs b/tests/deepzoom.rs index 0690cf4..0f53366 100644 --- a/tests/deepzoom.rs +++ b/tests/deepzoom.rs @@ -5,7 +5,7 @@ mod deepzoom { use openslide_rs::{Address, DeepZoomGenerator, OpenSlide, Size}; use rstest::rstest; - use std::path::Path; + use std::{path::Path, sync::Arc}; use super::fixture::boxes_tiff; @@ -13,7 +13,74 @@ mod deepzoom { #[case(boxes_tiff())] fn test_slide_no_limit_bounds(#[case] filename: &Path) { let slide = OpenSlide::new(filename).unwrap(); - let dz = DeepZoomGenerator::new(&slide, 254, 1, false).unwrap(); + let dz: DeepZoomGenerator = + DeepZoomGenerator::new(&slide, 254, 1, false).unwrap(); + + assert_eq!(dz.level_count(), 10); + assert_eq!(dz.tile_count(), 11); + assert_eq!( + dz.level_tiles(), + &[ + Size { w: 1, h: 1 }, + Size { w: 1, h: 1 }, + Size { w: 1, h: 1 }, + Size { w: 1, h: 1 }, + Size { w: 1, h: 1 }, + Size { w: 1, h: 1 }, + Size { w: 1, h: 1 }, + Size { w: 1, h: 1 }, + Size { w: 1, h: 1 }, + Size { w: 2, h: 1 } + ] + ); + + assert_eq!( + dz.level_dimensions(), + &[ + Size { w: 1, h: 1 }, + Size { w: 2, h: 1 }, + Size { w: 3, h: 2 }, + Size { w: 5, h: 4 }, + Size { w: 10, h: 8 }, + Size { w: 19, h: 16 }, + Size { w: 38, h: 32 }, + Size { w: 75, h: 63 }, + Size { w: 150, h: 125 }, + Size { w: 300, h: 250 } + ] + ); + + let image = dz.get_tile_rgba(9, Address { x: 1, y: 0 }).unwrap(); + assert_eq!(image.width(), 47); + assert_eq!(image.height(), 250); + assert_eq!(image.len(), 47 * 250 * 4); + + let image = dz.get_tile_rgb(9, Address { x: 1, y: 0 }).unwrap(); + assert_eq!(image.width(), 47); + assert_eq!(image.height(), 250); + assert_eq!(image.len(), 47 * 250 * 3); + + // In this case deepzoom will resize the image outputed by openslide + let image = dz.get_tile_rgba(5, Address { x: 0, y: 0 }).unwrap(); + assert_eq!(image.width(), 19); + assert_eq!(image.height(), 16); + assert_eq!(image.len(), 19 * 16 * 4); + + let image = dz.get_tile_rgba(0, Address { x: 1, y: 0 }); + assert!(image.is_err()); + + let image = dz.get_tile_rgba(10, Address { x: 0, y: 0 }); + assert!(image.is_err()); + } + + #[rstest] + #[case(boxes_tiff())] + fn test_slide_no_limit_bounds_arc(#[case] filename: &Path) { + let slide = Arc::new(OpenSlide::new(filename).unwrap()); + let dz: DeepZoomGenerator = + DeepZoomGenerator::new(slide.clone(), 254, 1, false).unwrap(); + + drop(slide); assert_eq!(dz.level_count(), 10); assert_eq!(dz.tile_count(), 11); @@ -76,7 +143,8 @@ mod deepzoom { #[case(boxes_tiff())] fn test_slide_with_limit_bounds(#[case] filename: &Path) { let slide = OpenSlide::new(filename).unwrap(); - let dz = DeepZoomGenerator::new(&slide, 254, 1, true).unwrap(); + let dz: DeepZoomGenerator = + DeepZoomGenerator::new(&slide, 254, 1, true).unwrap(); assert_eq!(dz.level_count(), 10); assert_eq!(dz.tile_count(), 11); diff --git a/tests/openslide.rs b/tests/openslide.rs index be4d2b0..a203f9a 100644 --- a/tests/openslide.rs +++ b/tests/openslide.rs @@ -1,11 +1,10 @@ use assert_approx_eq::assert_approx_eq; +use fixture::{boxes_tiff, missing_file, small_svs, unopenable_tiff, unsupported_file}; use openslide_rs::{Address, OpenSlide, Region, Size}; use rstest::rstest; use std::path::Path; mod fixture; -use fixture::{boxes_tiff, missing_file, small_svs, unopenable_tiff, unsupported_file}; -use openslide_rs::traits::Slide; #[rstest] #[should_panic(expected = "MissingFile(\"missing_file\")")] diff --git a/tests/properties.rs b/tests/properties.rs index b6fbf11..5bcb374 100644 --- a/tests/properties.rs +++ b/tests/properties.rs @@ -1,10 +1,10 @@ -use openslide_rs::{traits::Slide, OpenSlide}; +use fixture::{boxes_tiff, default, hamamatsu, leica, mirax, trestle}; +use openslide_rs::{properties::VendorProperties, OpenSlide}; use rstest::rstest; use std::path::Path; +use version_compare::Version; mod fixture; -use fixture::{boxes_tiff, default, hamamatsu, leica, mirax, trestle}; -use openslide_rs::properties::VendorProperties; #[rstest] #[case(boxes_tiff())] @@ -13,50 +13,57 @@ fn test_slide_properties(#[case] filename: &Path) { println!("{slide:?}"); - assert_eq!( - slide.get_property_names(), - vec![ - "openslide.level-count", - "openslide.level[0].downsample", - "openslide.level[0].height", - "openslide.level[0].tile-height", - "openslide.level[0].tile-width", - "openslide.level[0].width", - "openslide.level[1].downsample", - "openslide.level[1].height", - "openslide.level[1].tile-height", - "openslide.level[1].tile-width", - "openslide.level[1].width", - "openslide.level[2].downsample", - "openslide.level[2].height", - "openslide.level[2].tile-height", - "openslide.level[2].tile-width", - "openslide.level[2].width", - "openslide.level[3].downsample", - "openslide.level[3].height", - "openslide.level[3].tile-height", - "openslide.level[3].tile-width", - "openslide.level[3].width", + let base_expected_result = vec![ + "openslide.level-count", + "openslide.level[0].downsample", + "openslide.level[0].height", + "openslide.level[0].tile-height", + "openslide.level[0].tile-width", + "openslide.level[0].width", + "openslide.level[1].downsample", + "openslide.level[1].height", + "openslide.level[1].tile-height", + "openslide.level[1].tile-width", + "openslide.level[1].width", + "openslide.level[2].downsample", + "openslide.level[2].height", + "openslide.level[2].tile-height", + "openslide.level[2].tile-width", + "openslide.level[2].width", + "openslide.level[3].downsample", + "openslide.level[3].height", + "openslide.level[3].tile-height", + "openslide.level[3].tile-width", + "openslide.level[3].width", + ]; + + let raw_version = OpenSlide::get_version().unwrap(); + let version = Version::from(&raw_version).unwrap(); + + dbg!(&version); + if version < Version::from("4.0.0").unwrap() { + let mut expected_result = base_expected_result; + expected_result.extend(vec![ "openslide.quickhash-1", "openslide.vendor", "tiff.ResolutionUnit", "tiff.XResolution", - "tiff.YResolution" - ] - ); - - assert_eq!( - slide.get_property_value("tiff.YResolution").unwrap(), - "28.340000157438311" - ); - assert_eq!( - slide.get_property_value("tiff.XResolution").unwrap(), - "28.340000157438311" - ); - assert_eq!( - slide.get_property_value("tiff.YResolution").unwrap(), - "28.340000157438311" - ); + "tiff.YResolution", + ]); + assert_eq!(slide.get_property_names(), expected_result); + } else { + let mut expected_result = base_expected_result; + expected_result.extend(vec![ + "openslide.mpp-x", + "openslide.mpp-y", + "openslide.quickhash-1", + "openslide.vendor", + "tiff.ResolutionUnit", + "tiff.XResolution", + "tiff.YResolution", + ]); + assert_eq!(slide.get_property_names(), expected_result); + } } #[rstest] @@ -85,8 +92,19 @@ fn test_tiff_properties(#[case] filename: &Path) { properties.openslide_properties.quickhash_1, Some("c08b056490bac8bcb329d9b8fb175888083d4097952a55fee99997758c728c36".to_string()) ); - assert_eq!(properties.openslide_properties.mpp_x, None); - assert_eq!(properties.openslide_properties.mpp_y, None); + + let raw_version = OpenSlide::get_version().unwrap(); + let version = Version::from(&raw_version).unwrap(); + + dbg!(&version); + if version < Version::from("4.0.0").unwrap() { + assert_eq!(properties.openslide_properties.mpp_x, None); + assert_eq!(properties.openslide_properties.mpp_y, None); + } else { + assert_eq!(properties.openslide_properties.mpp_x, Some(352.85815)); + assert_eq!(properties.openslide_properties.mpp_y, Some(352.85815)); + } + assert_eq!(properties.openslide_properties.level_count, Some(4)); assert_eq!( properties.openslide_properties.levels[0].downsample,