models.rs

7.8 kB · rust · 232 lines

1use mrlycore::cell::Cell;2use mrlycore::colors::Color;3use mrlycore::enums::Mode;4use mrlycore::errors::{value_error, Result};5use mrlycore::tensor::{Dtype, Tensor};6use std::collections::HashMap;78/// The two-dimensional cell.9pub type Cell2d = CellNd<2>;10/// The three-dimensional cell.11pub type Cell3d = CellNd<3>;1213/// Returns the narrowest unsigned dtype that holds the peak value.14///15/// ```16/// use mrlycore::tensor::Dtype;17/// assert_eq!(mrlymath::dim::models::dtype_for(255), Dtype::U8);18/// assert_eq!(mrlymath::dim::models::dtype_for(256), Dtype::U16);19/// assert_eq!(mrlymath::dim::models::dtype_for(70000), Dtype::U32);20/// ```21pub fn dtype_for(peak: i64) -> Dtype {22    if peak <= Dtype::U8.max() {23        Dtype::U824    } else if peak <= Dtype::U16.max() {25        Dtype::U1626    } else {27        Dtype::U3228    }29}3031/// Returns the narrowest count dtype that fits the mask's popcount.32pub fn counting_dtype(mask: &Tensor) -> Dtype {33    dtype_for(mask.sum() as i64)34}3536/// A cell whose tensor is pinned to N dimensions.37#[derive(Clone, Debug, PartialEq, Eq)]38pub struct CellNd<const N: usize> {39    /// The wrapped cell.40    pub cell: Cell,41}4243impl<const N: usize> CellNd<N> {44    /// Builds a cell from an N-dimensional tensor of types.45    pub fn new(types: Tensor) -> CellNd<N> {46        assert_eq!(types.shape.len(), N, "CellNd requires a {N}d tensor");47        CellNd {48            cell: Cell::new(types),49        }50    }51    /// Returns the size of axis 1.52    pub fn width(&self) -> usize {53        self.cell.types.shape[1]54    }55    /// Returns the size of axis 0.56    pub fn height(&self) -> usize {57        self.cell.types.shape[0]58    }59    /// Returns the tensor of types.60    pub fn types(&self) -> &Tensor {61        &self.cell.types62    }63    /// Swaps filled and empty sites.64    pub fn invert(self) -> CellNd<N> {65        CellNd {66            cell: self.cell.invert(),67        }68    }69    /// Inverts the cell.70    pub fn anti(self) -> CellNd<N> {71        self.invert()72    }73    /// Wraps the cell in count layers of the given value on every side.74    pub fn pad(self, count: usize, value: u8) -> CellNd<N> {75        CellNd {76            cell: self.cell.pad(count, value),77        }78    }79    /// Deepens the cell into its level-fold fractal, or an error below level one.80    pub fn fractal(self, level: usize) -> Result<CellNd<N>> {81        Ok(CellNd {82            cell: self.cell.fractal(level)?,83        })84    }85    /// Tags each site with its ring distance from the center.86    pub fn layers(self) -> CellNd<N> {87        CellNd {88            cell: self.cell.layers(Dtype::U8),89        }90    }91    /// Tags each site with its count of masked neighbors matching the target, wrapping on request.92    pub fn neighbors(self, mask: &Tensor, target: u8, wrap: bool) -> Result<CellNd<N>> {93        let dtype = counting_dtype(mask);94        Ok(CellNd {95            cell: self.cell.neighbors(mask, target, wrap, dtype)?,96        })97    }98    /// Maps each site to one at or above the threshold, zero below.99    pub fn binarize(self, threshold: u8) -> CellNd<N> {100        CellNd {101            cell: self.cell.binarize(threshold),102        }103    }104    /// Binarizes the cell at the threshold Otsu's method picks.105    pub fn binarize_otsu(self) -> CellNd<N> {106        CellNd {107            cell: self.cell.binarize_otsu(),108        }109    }110    /// Rounds each site to the mean of its masked neighborhood, wrapping on request.111    pub fn blur(self, mask: &Tensor, wrap: bool) -> Result<CellNd<N>> {112        Ok(CellNd {113            cell: self.cell.blur(mask, wrap)?,114        })115    }116    /// Writes the value wherever the tiled mask is nonzero.117    pub fn perforate(self, mask: &Tensor, value: u8) -> Result<CellNd<N>> {118        Ok(CellNd {119            cell: self.cell.perforate(mask, value)?,120        })121    }122    /// Returns the Kronecker product of the two cells.123    pub fn combine(&self, other: &CellNd<N>) -> CellNd<N> {124        CellNd {125            cell: self.cell.combine(&other.cell),126        }127    }128    /// Colors each site by its type through the mapping in the given mode.129    pub fn paint(self, mapping: &HashMap<u8, Vec<Color>>, mode: Mode) -> CellNd<N> {130        CellNd {131            cell: self.cell.paint(mapping, mode),132        }133    }134}135136impl CellNd<2> {137    /// Rotates the cell k quarter turns in the plane.138    pub fn rotate(self, k: usize) -> Cell2d {139        CellNd {140            cell: self.cell.rotate(k, (0, 1)),141        }142    }143    /// Repeats the cell into a width-by-height array of copies.144    pub fn tile(self, width: usize, height: usize) -> Cell2d {145        CellNd {146            cell: self.cell.tile(&[height, width]),147        }148    }149}150151impl CellNd<3> {152    /// Returns the size of axis 2.153    pub fn depth(&self) -> usize {154        self.cell.types.shape[2]155    }156    /// Rotates the cell k quarter turns about the given pair of axes.157    pub fn rotate(self, k: usize, axes: (usize, usize)) -> Cell3d {158        CellNd {159            cell: self.cell.rotate(k, axes),160        }161    }162    /// Turns the cell into one of the 24 cube orientations, or an error past the table.163    pub fn orient(self, index: usize) -> Result<Cell3d> {164        let table = crate::three::orientations();165        match table.get(index) {166            Some(&(a, b, c)) => Ok(self.rotate(a, (1, 2)).rotate(b, (0, 2)).rotate(c, (0, 1))),167            None => value_error(format!("orientation index {index} out of range (0..23).")),168        }169    }170    /// Repeats the cell into a width-by-height-by-depth array of copies.171    pub fn tile(self, width: usize, height: usize, depth: usize) -> Cell3d {172        CellNd {173            cell: self.cell.tile(&[height, width, depth]),174        }175    }176}177178#[cfg(test)]179mod tests {180    use super::*;181    use mrlycore::atoms;182    #[test]183    fn binarize_wrapper_thresholds_pointwise() {184        let cell = Cell2d::new(atoms::carpet_2d(3));185        let binarized = cell.clone().binarize(1);186        assert_eq!(binarized.types(), cell.types());187    }188    #[test]189    fn blur_wrapper_preserves_shape() {190        let cell = Cell2d::new(atoms::carpet_2d(3));191        let mask = Tensor::full(vec![3, 3], 1);192        let blurred = cell.clone().blur(&mask, true).unwrap();193        assert_eq!(blurred.types().shape, cell.types().shape);194    }195    #[test]196    fn perforate_wrapper_zero_mask_is_identity() {197        let cell = Cell2d::new(atoms::carpet_2d(3));198        let mask = Tensor::new(cell.types().shape.clone());199        let perforated = cell.clone().perforate(&mask, 5).unwrap();200        assert_eq!(perforated.types(), cell.types());201    }202    #[test]203    fn blur_wrapper_preserves_shape_3d() {204        let cell = Cell3d::new(atoms::carpet_3d(3));205        let mask = Tensor::full(vec![3, 3, 3], 1);206        let blurred = cell.clone().blur(&mask, true).unwrap();207        assert_eq!(blurred.types().shape, cell.types().shape);208    }209    #[test]210    fn perforate_wrapper_zero_mask_is_identity_3d() {211        let cell = Cell3d::new(atoms::carpet_3d(3));212        let mask = Tensor::new(cell.types().shape.clone());213        let perforated = cell.clone().perforate(&mask, 5).unwrap();214        assert_eq!(perforated.types(), cell.types());215    }216    #[test]217    fn counting_dtype_widens_with_the_mask() {218        assert_eq!(counting_dtype(&Tensor::full(vec![3, 3], 1)), Dtype::U8);219        assert_eq!(counting_dtype(&Tensor::full(vec![15, 15], 1)), Dtype::U8);220        assert_eq!(counting_dtype(&Tensor::full(vec![17, 17], 1)), Dtype::U16);221        assert_eq!(counting_dtype(&Tensor::full(vec![63, 63], 1)), Dtype::U16);222    }223    #[test]224    fn neighbors_wrapper_survives_a_wide_mask() {225        let mut mask = Tensor::full(vec![17, 17], 1);226        mask.set(&[8, 8], 0);227        let grid = Cell2d::new(Tensor::full(vec![21, 21], 1));228        let counted = grid.neighbors(&mask, 1, true).unwrap();229        let tags = counted.cell.tags.as_ref().unwrap();230        assert_eq!(tags.at(0), 17 * 17 - 1);231    }232}