#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TileSize {
pub width: u32,
pub height: u32,
}
impl TileSize {
pub fn new(width: u32, height: u32) -> TileSize {
TileSize { width, height }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Dimensions {
pub width: u32,
pub height: u32,
}
impl Dimensions {
pub fn new(width: u32, height: u32) -> Dimensions {
Dimensions { width, height }
}
pub fn from_tuple(dimensions: (u32, u32)) -> Dimensions {
Dimensions {
width: dimensions.0,
height: dimensions.1,
}
}
pub fn pixels(&self) -> u64 {
self.width as u64 * self.height as u64
}
pub fn aspect_ratio(&self) -> f32 {
self.width as f32 / self.height as f32
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Level {
level: u32,
dimensions: Dimensions,
tile_size: TileSize,
downsample_factor: f64,
}
impl Level {
pub fn new(
level: u32,
dimensions: Dimensions,
tile_size: TileSize,
downsample_factor: f64,
) -> Self {
Self {
level,
dimensions,
tile_size,
downsample_factor,
}
}
pub fn tiles_x(&self) -> u32 {
self.dimensions.width.div_ceil(self.tile_size.width)
}
pub fn tiles_y(&self) -> u32 {
self.dimensions.height.div_ceil(self.tile_size.height)
}
pub fn level(&self) -> u32 {
self.level
}
pub fn dimensions(&self) -> &Dimensions {
&self.dimensions
}
pub fn tile_size(&self) -> &TileSize {
&self.tile_size
}
pub fn downsample_factor(&self) -> f64 {
self.downsample_factor
}
pub fn valid_tile_dimensions(&self, tile_x: u32, tile_y: u32) -> (u32, u32) {
let valid_width = self
.dimensions
.width
.saturating_sub(tile_x * self.tile_size.width)
.min(self.tile_size.width);
let valid_height = self
.dimensions
.height
.saturating_sub(tile_y * self.tile_size.height)
.min(self.tile_size.height);
(valid_width, valid_height)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Metadata {
levels: Vec<Level>,
objective_power: Option<f32>,
microns_per_pixel: Option<f64>,
}
impl Metadata {
pub fn new(
levels: Vec<Level>,
objective_power: Option<f32>,
microns_per_pixel: Option<f64>,
) -> Self {
Self {
levels,
objective_power,
microns_per_pixel,
}
}
pub fn level_count(&self) -> usize {
self.levels.len()
}
pub fn base_level(&self) -> &Level {
&self.levels[0]
}
pub fn levels(&self) -> &[Level] {
&self.levels
}
pub fn objective_power(&self) -> Option<f32> {
self.objective_power
}
pub fn microns_per_pixel(&self) -> Option<f64> {
self.microns_per_pixel
}
pub fn level(&self, index: usize) -> Option<&Level> {
self.levels.get(index)
}
pub fn best_level_for_target_mpp(&self, target_mpp: f64) -> Option<usize> {
let base_mpp = self.microns_per_pixel()?;
let mpp_at = |level: &Level| base_mpp * level.downsample_factor();
let best_level = self
.levels()
.iter()
.enumerate()
.filter(|(_, level)| mpp_at(level) <= target_mpp)
.max_by(|(_, a), (_, b)| mpp_at(a).total_cmp(&mpp_at(b)))
.map(|(index, _)| index);
if best_level.is_some() {
best_level
} else {
self.levels()
.iter()
.enumerate()
.min_by(|(_, a), (_, b)| mpp_at(a).total_cmp(&mpp_at(b)))
.map(|(index, _)| index)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn level(index: u32, downsample_factor: f64) -> Level {
Level::new(
index,
Dimensions::new(100, 100),
TileSize::new(30, 30),
downsample_factor,
)
}
#[test]
fn tiles_x_and_y_round_up_to_cover_the_level() {
let exact = Level::new(0, Dimensions::new(90, 60), TileSize::new(30, 30), 1.0);
assert_eq!(exact.tiles_x(), 3);
assert_eq!(exact.tiles_y(), 2);
let remainder = Level::new(0, Dimensions::new(100, 100), TileSize::new(30, 30), 1.0);
assert_eq!(remainder.tiles_x(), 4);
assert_eq!(remainder.tiles_y(), 4);
}
#[test]
fn valid_tile_dimensions_are_full_size_in_the_interior() {
let level = Level::new(0, Dimensions::new(100, 100), TileSize::new(30, 30), 1.0);
assert_eq!(level.valid_tile_dimensions(0, 0), (30, 30));
}
#[test]
fn valid_tile_dimensions_are_clipped_at_the_boundary() {
let level = Level::new(0, Dimensions::new(100, 100), TileSize::new(30, 30), 1.0);
assert_eq!(level.valid_tile_dimensions(3, 3), (10, 10));
}
#[test]
fn best_level_for_target_mpp_is_none_without_a_base_mpp() {
let metadata = Metadata::new(vec![level(0, 1.0)], None, None);
assert_eq!(metadata.best_level_for_target_mpp(0.5), None);
}
#[test]
fn best_level_for_target_mpp_picks_the_coarsest_qualifying_level() {
let metadata = Metadata::new(
vec![level(0, 1.0), level(1, 2.0), level(2, 4.0)],
Some(20.0),
Some(0.25),
);
assert_eq!(metadata.best_level_for_target_mpp(0.6), Some(1));
}
#[test]
fn best_level_for_target_mpp_falls_back_to_the_finest_level() {
let metadata = Metadata::new(
vec![level(0, 1.0), level(1, 2.0), level(2, 4.0)],
Some(20.0),
Some(0.25),
);
assert_eq!(metadata.best_level_for_target_mpp(0.1), Some(0));
}
}