openslide_rs/properties/
openslide.rs

1//! Openslide properties
2//!
3
4use regex::Regex;
5
6lazy_static! {
7    static ref REGEX_LEVEL_PROPERTIES: Regex =
8        Regex::new(r"level\[([0-9]+)]\.([a-zA-Z]+(?:-[a-zA-Z]+)?)").unwrap();
9}
10
11pub const OPENSLIDE_PROPERTY_NAME_COMMENT: &str = "openslide.comment";
12pub const OPENSLIDE_PROPERTY_NAME_VENDOR: &str = "openslide.vendor";
13pub const OPENSLIDE_PROPERTY_NAME_QUICKHASH1: &str = "openslide.quickhash-1";
14pub const OPENSLIDE_PROPERTY_NAME_BACKGROUND_COLOR: &str = "openslide.background-color";
15pub const OPENSLIDE_PROPERTY_NAME_OBJECTIVE_POWER: &str = "openslide.objective-power";
16pub const OPENSLIDE_PROPERTY_NAME_MPP_X: &str = "openslide.mpp-x";
17pub const OPENSLIDE_PROPERTY_NAME_MPP_Y: &str = "openslide.mpp-y";
18pub const OPENSLIDE_PROPERTY_NAME_BOUNDS_X: &str = "openslide.bounds-x";
19pub const OPENSLIDE_PROPERTY_NAME_BOUNDS_Y: &str = "openslide.bounds-y";
20pub const OPENSLIDE_PROPERTY_NAME_BOUNDS_WIDTH: &str = "openslide.bounds-width";
21pub const OPENSLIDE_PROPERTY_NAME_BOUNDS_HEIGHT: &str = "openslide.bounds-height";
22pub const OPENSLIDE_PROPERTY_LEVEL_COUNT: &str = "openslide.level-count";
23pub const OPENSLIDE_PROPERTY_NAME_ICC_SIZE: &str = "openslide.icc-size";
24
25const OPENSLIDE_PROPERTY_LEVEL_DOWNSAMPLE: &str = "downsample";
26const OPENSLIDE_PROPERTY_LEVEL_HEIGHT: &str = "height";
27const OPENSLIDE_PROPERTY_LEVEL_WIDTH: &str = "width";
28const OPENSLIDE_PROPERTY_LEVEL_TILE_HEIGHT: &str = "tile-height";
29const OPENSLIDE_PROPERTY_LEVEL_TILE_WIDTH: &str = "tile-width";
30
31/// Properties defined for every level
32#[derive(Clone, Debug, Default)]
33pub struct LevelProperties {
34    pub downsample: Option<f32>,
35    pub height: Option<u32>,
36    pub width: Option<u32>,
37    pub tile_height: Option<u32>,
38    pub tile_width: Option<u32>,
39}
40
41/// Common properties that are available under the name `openslide.<property>` in the `HashMap`
42/// returned from the `OpenSlide::get_properties()` method.
43#[derive(Clone, Debug, Default)]
44pub struct OpenSlide {
45    pub vendor: Option<String>,
46    pub quickhash_1: Option<String>,
47    pub mpp_x: Option<f32>,
48    pub mpp_y: Option<f32>,
49    pub objective_power: Option<u32>,
50    pub comment: Option<String>,
51    pub level_count: Option<u32>,
52    pub bounds_x: Option<u32>,
53    pub bounds_y: Option<u32>,
54    pub bounds_width: Option<u32>,
55    pub bounds_height: Option<u32>,
56    pub icc_profile_size: Option<u32>,
57    pub background_color: Option<String>,
58    pub levels: Vec<LevelProperties>,
59}
60
61impl OpenSlide {
62    /// Initialises the `OpenSlide` properties.
63    ///
64    /// This needs a property map in order to compute the number of levels. This is needed because
65    /// of the properties that are listed as `openslide.level[<level>].<property>`.
66    pub(crate) fn new(property_iter: impl Iterator<Item = (String, String)>) -> Self {
67        let mut openslide_property = OpenSlide::default();
68
69        property_iter
70            .filter(|(name, _)| name.starts_with("openslide."))
71            .for_each(|(name, value)| openslide_property.parse_property_name(&name, &value));
72        openslide_property
73    }
74
75    fn parse_property_name(&mut self, name: &str, value: &str) {
76        match name {
77            OPENSLIDE_PROPERTY_NAME_VENDOR => self.vendor = value.parse().ok(),
78            OPENSLIDE_PROPERTY_NAME_QUICKHASH1 => self.quickhash_1 = value.parse().ok(),
79            OPENSLIDE_PROPERTY_NAME_MPP_X => self.mpp_x = value.parse().ok(),
80            OPENSLIDE_PROPERTY_NAME_MPP_Y => self.mpp_y = value.parse().ok(),
81            OPENSLIDE_PROPERTY_NAME_OBJECTIVE_POWER => self.objective_power = value.parse().ok(),
82            OPENSLIDE_PROPERTY_NAME_COMMENT => self.comment = Some(value.to_string()),
83            OPENSLIDE_PROPERTY_LEVEL_COUNT => self.level_count = value.parse().ok(),
84            OPENSLIDE_PROPERTY_NAME_BOUNDS_X => self.bounds_x = value.parse().ok(),
85            OPENSLIDE_PROPERTY_NAME_BOUNDS_Y => self.bounds_y = value.parse().ok(),
86            OPENSLIDE_PROPERTY_NAME_ICC_SIZE => self.icc_profile_size = value.parse().ok(),
87            OPENSLIDE_PROPERTY_NAME_BOUNDS_WIDTH => self.bounds_width = value.parse().ok(),
88            OPENSLIDE_PROPERTY_NAME_BOUNDS_HEIGHT => self.bounds_height = value.parse().ok(),
89            OPENSLIDE_PROPERTY_NAME_BACKGROUND_COLOR => {
90                self.background_color = Some(value.to_string());
91            }
92            _ => {
93                if let Some(cap) = REGEX_LEVEL_PROPERTIES.captures(name) {
94                    let level: usize = cap[1].parse().unwrap(); // safe unwrap because of Regex matching
95                    let property = &cap[2]; // safe unwrap because of Regex matching
96
97                    self.parse_property_levels(level, property, value);
98                }
99            }
100        }
101    }
102
103    fn parse_property_levels(&mut self, level: usize, name: &str, value: &str) {
104        if self.levels.len() < level + 1 {
105            self.levels.resize(level + 1, LevelProperties::default());
106        }
107
108        let level = &mut self.levels[level];
109        match name {
110            OPENSLIDE_PROPERTY_LEVEL_DOWNSAMPLE => level.downsample = value.parse().ok(),
111            OPENSLIDE_PROPERTY_LEVEL_HEIGHT => level.height = value.parse().ok(),
112            OPENSLIDE_PROPERTY_LEVEL_WIDTH => level.width = value.parse().ok(),
113            OPENSLIDE_PROPERTY_LEVEL_TILE_HEIGHT => level.tile_height = value.parse().ok(),
114            OPENSLIDE_PROPERTY_LEVEL_TILE_WIDTH => level.tile_width = value.parse().ok(),
115            _ => {}
116        }
117    }
118}