use std::path::Path;
use std::collections::HashMap;
use std::fmt::{Display, Debug};
use std::cmp::PartialOrd;
use failure::{err_msg, Error};
use image::{RgbaImage};
use num::{Num, ToPrimitive, Unsigned, Integer};
use num::zero;
use ::{
utils,
bindings,
properties,
};
#[derive(Clone)]
pub struct OpenSlide {
osr: *const bindings::OpenSlideT,
pub properties: properties::Properties,
}
impl Drop for OpenSlide {
fn drop(
&mut self
) {
bindings::close(self.osr);
}
}
impl OpenSlide {
pub fn new(
filename: &Path
) -> Result<OpenSlide, Error> {
if !filename.exists() {
return Err(err_msg(format!("Error: Nonexisting path: {}", filename.display())));
}
let osr = bindings::open(filename.to_str().ok_or(err_msg("Error: Path to &str"))?)?;
let mut property_map = HashMap::<String, String>::new();
for name in bindings::get_property_names(osr)? {
property_map.insert(name.clone(), bindings::get_property_value(osr, &name)?);
}
let properties = properties::Properties::new(&property_map);
Ok(OpenSlide {
osr: osr,
properties: properties,
})
}
pub fn get_level_count(
&self
) -> Result<u32, Error> {
let num_levels = bindings::get_level_count(self.osr)?;
if num_levels < -1 {
Err(err_msg(format!("Error: Number of levels is {}, this is an unknown error from OpenSlide. \
OpenSlide returns -1 if an error occured. \
See OpenSlide C API documentation.", num_levels)))
} else if num_levels == -1 {
Err(err_msg("Error: Number of levels is -1, this is a known error from OpenSlide. \
OpenSlide returns -1 if an error occured. \
See OpenSlide C API documentation."))
} else {
Ok(num_levels as u32)
}
}
pub fn get_level0_dimensions(
&self
) -> Result<(u64, u64), Error> {
let (width, height) = bindings::get_level0_dimensions(self.osr)?;
if width < -1 {
return Err(err_msg(format!("Error: Width is {}, this is an unknown error from OpenSlide. \
OpenSlide returns -1 if an error occured. \
See OpenSlide C API documentation.", width)))
} else if width == -1 {
return Err(err_msg("Error: Width is -1, this is a known error from OpenSlide. \
OpenSlide returns -1 if an error occured. \
See OpenSlide C API documentation."))
}
if height < -1 {
return Err(err_msg(format!("Error: Height is {}, this is an unknown error from OpenSlide. \
OpenSlide returns -1 if an error occured. \
See OpenSlide C API documentation.", width)))
} else if height == -1 {
return Err(err_msg("Error: Height is -1, this is a known error from OpenSlide. \
OpenSlide returns -1 if an error occured. \
See OpenSlide C API documentation."))
}
Ok((width as u64, height as u64))
}
pub fn get_level_dimensions<T: Integer + ToPrimitive + Debug + Display + Clone + Copy>(
&self,
level: T,
) -> Result<(u64, u64), Error> {
let max_num_levels = self.get_level_count()?;
if level.to_u32().ok_or(err_msg("Conversion to primitive error"))? > max_num_levels {
return Err(err_msg(format!("Error: Specified level {} is larger than the max slide level {}",
level, max_num_levels)));
}
let (width, height) = bindings::get_level_dimensions(self.osr, level.to_i32().ok_or(err_msg("Conversion to primitive error"))?)?;
if width < -1 {
return Err(err_msg(format!("Error: Width is {}, this is an unknown error from OpenSlide. \
OpenSlide returns -1 if an error occured. \
See OpenSlide C API documentation.", width)))
} else if width == -1 {
return Err(err_msg("Error: Width is -1, this is a known error from openslide. \
OpenSlide returns -1 if an error occured. \
See OpenSlide C API documentation."))
}
if height < -1 {
return Err(err_msg(format!("Error: Height is {}, this is an unknown error from OpenSlide. \
OpenSlide returns -1 if an error occured. \
See OpenSlide C API documentation.", width)))
} else if height == -1 {
return Err(err_msg("Error: Height is -1, this is a known error from openslide. \
OpenSlide returns -1 if an error occured. \
See OpenSlide C API documentation."))
}
Ok((width as u64, height as u64))
}
pub fn get_level_downsample<T: Integer + Unsigned + ToPrimitive + Debug + Display + Clone + Copy>(
&self,
level: T,
) -> Result<f64, Error> {
let max_num_levels = self.get_level_count()?;
if level.to_u32().ok_or(err_msg("Conversion to primitive error"))? > max_num_levels {
return Err(err_msg(format!("Error: Specified level {} is larger than the max slide level {}",
level, max_num_levels)));
}
let downsample_factor = bindings::get_level_downsample(self.osr, level.to_i32().ok_or(err_msg("Conversion to primitive error"))?)?;
if downsample_factor < 0.0 {
return Err(err_msg(format!("Error: Downsample factor is {}, this is an error from \
OpenSlide. OpenSlide returns -1.0 if an error occured. \
See OpenSlide C API documentation.", downsample_factor)))
}
Ok(downsample_factor)
}
pub fn get_best_level_for_downsample<T: Num + ToPrimitive + PartialOrd + Debug + Display + Clone + Copy>(
&self,
downsample_factor: T,
) -> Result<u32, Error> {
if downsample_factor < zero() {
return Err(err_msg(format!("Error: Only non-negative downsample factor is allowed. \
You specified {}. ", downsample_factor)))
}
let level = bindings::get_best_level_for_downsample(self.osr, downsample_factor.to_f64().ok_or(err_msg("Conversion to primitive error"))?)?;
if level < -1 {
Err(err_msg(format!("Error: Returned level is {}, this is an unknown error from OpenSlide. \
OpenSlide returns -1 if an error occured. \
See OpenSlide C API documentation.", level)))
} else if level == -1 {
Err(err_msg("Error: Returned level is -1, this is a known error from openslide. \
OpenSlide returns -1 if an error occured. \
See OpenSlide C API documentation."))
} else {
Ok(level as u32)
}
}
fn get_feasible_dimensions<T: Integer + Unsigned + ToPrimitive + Debug + Display + Clone + Copy>(
&self,
top_left_lvl0_row: T,
top_left_lvl0_col: T,
level: T,
height: T,
width: T,
) -> Result<(u64, u64), Error> {
let (max_width, max_height) = self.get_level_dimensions(level)?;
let downsample_factor = self.get_level_downsample(level)?;
let tl_row_this_lvl = top_left_lvl0_row.to_f64().ok_or(err_msg("Conversion to primitive error"))? /
downsample_factor;
let tl_col_this_lvl = top_left_lvl0_col.to_f64().ok_or(err_msg("Conversion to primitive error"))? /
downsample_factor;
let new_height = height.to_u64()
.ok_or(err_msg("conversion to primitive error"))?
.min(max_height - tl_row_this_lvl.round() as u64);
let new_width = width.to_u64()
.ok_or(err_msg("Conversion to primitive error"))?
.min(max_width - tl_col_this_lvl.round() as u64);
if new_height < height.to_u64().ok_or(err_msg("conversion to primitive error"))? {
println!("WARNING: Requested region height is changed from {} to {} in order to fit",
height, new_height);
}
if new_width < width.to_u64().ok_or(err_msg("conversion to primitive error"))? {
println!("WARNING: Requested region width is changed from {} to {} in order to fit",
width, new_width);
}
if new_height > max_height {
return Err(err_msg(format!("Requested height {} exceeds maximum {}", height, max_height)))
}
if new_width > max_width {
return Err(err_msg(format!("Requested width {} exceeds maximum {}", width, max_width)))
}
Ok((new_height, new_width))
}
pub fn read_region<T: Integer + Unsigned + ToPrimitive + Debug + Display + Clone + Copy>(
&self,
top_left_lvl0_row: T,
top_left_lvl0_col: T,
level: T,
height: T,
width: T,
) -> Result<RgbaImage, Error> {
let (height, width) = self.get_feasible_dimensions(top_left_lvl0_row,
top_left_lvl0_col,
level,
height,
width)?;
let buffer = bindings::read_region(self.osr,
top_left_lvl0_col.to_i64().ok_or(err_msg("Conversion to primitive error"))?,
top_left_lvl0_row.to_i64().ok_or(err_msg("Conversion to primitive error"))?,
level.to_i32().ok_or(err_msg("Conversion to primitive error"))?,
width.to_i64().ok_or(err_msg("Conversion to primitive error"))?,
height.to_i64().ok_or(err_msg("Conversion to primitive error"))?)?;
let word_repr = utils::WordRepresentation::BigEndian;
utils::decode_buffer(&buffer, height, width, word_repr)
}
pub fn get_properties(
&self
) -> Result<HashMap<String, String>, Error> {
let mut properties = HashMap::<String, String>::new();
for name in bindings::get_property_names(self.osr)? {
properties.insert(name.clone(), bindings::get_property_value(self.osr, &name)?);
}
Ok(properties)
}
}