grana-object 0.1.1

Next-generation microscopy image visualization and analysis platform
Documentation
use crate::{Bounds, IntensityTable, Mesh, PackedMesh};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FilamentTopology {
    branches: u32,
    terminals: u32,
    junctions: u32,
    nodes: u32,
}

impl FilamentTopology {
    pub fn new(branches: u32, terminals: u32, junctions: u32, nodes: u32) -> Self {
        assert!(branches > 0, "a filament holds at least one branch");
        assert!(
            terminals <= 2 * branches && junctions < branches,
            "a filament counts its terminals and junctions"
        );
        assert!(nodes > 0, "a filament holds at least one node");
        Self {
            branches,
            terminals,
            junctions,
            nodes,
        }
    }

    pub fn branches(self) -> u32 {
        self.branches
    }

    pub fn terminals(self) -> u32 {
        self.terminals
    }

    pub fn junctions(self) -> u32 {
        self.junctions
    }

    pub fn nodes(self) -> u32 {
        self.nodes
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FilamentStatistics {
    length: f64,
    diameter: f64,
    volume: f64,
    topology: FilamentTopology,
    centroid: [f32; 3],
    bounds: Bounds,
}

impl FilamentStatistics {
    pub fn new(
        length: f64,
        diameter: f64,
        volume: f64,
        topology: FilamentTopology,
        centroid: [f32; 3],
        bounds: Bounds,
    ) -> Self {
        assert!(
            length.is_finite() && length > 0.0,
            "a filament length is a positive length"
        );
        assert!(
            diameter.is_finite() && diameter > 0.0,
            "a filament diameter is a positive length"
        );
        assert!(
            volume.is_finite() && volume > 0.0,
            "a filament volume is a positive volume"
        );
        assert!(
            centroid.iter().all(|value| value.is_finite()),
            "a filament centroid must be finite"
        );
        Self {
            length,
            diameter,
            volume,
            topology,
            centroid,
            bounds,
        }
    }

    pub fn length(self) -> f64 {
        self.length
    }

    pub fn diameter(self) -> f64 {
        self.diameter
    }

    pub fn volume(self) -> f64 {
        self.volume
    }

    pub fn topology(self) -> FilamentTopology {
        self.topology
    }

    pub fn branches(self) -> u32 {
        self.topology.branches()
    }

    pub fn terminals(self) -> u32 {
        self.topology.terminals()
    }

    pub fn junctions(self) -> u32 {
        self.topology.junctions()
    }

    pub fn nodes(self) -> u32 {
        self.topology.nodes()
    }

    pub fn centroid(self) -> [f32; 3] {
        self.centroid
    }

    pub fn bounds(self) -> Bounds {
        self.bounds
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct FilamentSet {
    level: usize,
    geometry: PackedMesh,
    objects: Vec<FilamentStatistics>,
    intensities: IntensityTable,
}

impl FilamentSet {
    pub fn new(
        level: usize,
        mesh: Mesh,
        objects: Vec<FilamentStatistics>,
        intensities: IntensityTable,
    ) -> Self {
        let geometry = PackedMesh::new(mesh);
        assert_eq!(
            geometry.object_count(),
            objects.len(),
            "every packed filament names an object"
        );
        assert_eq!(
            geometry.is_empty(),
            objects.is_empty(),
            "the mesh and the object list agree on emptiness"
        );
        assert_eq!(
            intensities.objects(),
            objects.len(),
            "every filament is measured in every channel"
        );
        Self {
            level,
            geometry,
            objects,
            intensities,
        }
    }

    pub fn from_parts(
        level: usize,
        geometry: PackedMesh,
        objects: Vec<FilamentStatistics>,
        intensities: IntensityTable,
    ) -> Option<Self> {
        if geometry.object_count() != objects.len()
            || geometry.is_empty() != objects.is_empty()
            || intensities.objects() != objects.len()
        {
            return None;
        }
        Some(Self {
            level,
            geometry,
            objects,
            intensities,
        })
    }

    pub fn level(&self) -> usize {
        self.level
    }

    pub fn geometry(&self) -> &PackedMesh {
        &self.geometry
    }

    pub fn objects(&self) -> &[FilamentStatistics] {
        &self.objects
    }

    pub fn intensities(&self) -> &IntensityTable {
        &self.intensities
    }

    pub fn object(&self, index: usize) -> Option<FilamentStatistics> {
        self.objects.get(index).copied()
    }

    pub fn object_count(&self) -> usize {
        self.objects.len()
    }

    pub fn is_empty(&self) -> bool {
        self.objects.is_empty()
    }
}