Skip to main content

grana_object/
filament.rs

1use crate::{Bounds, IntensityTable, Mesh, PackedMesh};
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4pub struct FilamentTopology {
5    branches: u32,
6    terminals: u32,
7    junctions: u32,
8    nodes: u32,
9}
10
11impl FilamentTopology {
12    pub fn new(branches: u32, terminals: u32, junctions: u32, nodes: u32) -> Self {
13        assert!(branches > 0, "a filament holds at least one branch");
14        assert!(
15            terminals <= 2 * branches && junctions < branches,
16            "a filament counts its terminals and junctions"
17        );
18        assert!(nodes > 0, "a filament holds at least one node");
19        Self {
20            branches,
21            terminals,
22            junctions,
23            nodes,
24        }
25    }
26
27    pub fn branches(self) -> u32 {
28        self.branches
29    }
30
31    pub fn terminals(self) -> u32 {
32        self.terminals
33    }
34
35    pub fn junctions(self) -> u32 {
36        self.junctions
37    }
38
39    pub fn nodes(self) -> u32 {
40        self.nodes
41    }
42}
43
44#[derive(Clone, Copy, Debug, PartialEq)]
45pub struct FilamentStatistics {
46    length: f64,
47    diameter: f64,
48    volume: f64,
49    topology: FilamentTopology,
50    centroid: [f32; 3],
51    bounds: Bounds,
52}
53
54impl FilamentStatistics {
55    pub fn new(
56        length: f64,
57        diameter: f64,
58        volume: f64,
59        topology: FilamentTopology,
60        centroid: [f32; 3],
61        bounds: Bounds,
62    ) -> Self {
63        assert!(
64            length.is_finite() && length > 0.0,
65            "a filament length is a positive length"
66        );
67        assert!(
68            diameter.is_finite() && diameter > 0.0,
69            "a filament diameter is a positive length"
70        );
71        assert!(
72            volume.is_finite() && volume > 0.0,
73            "a filament volume is a positive volume"
74        );
75        assert!(
76            centroid.iter().all(|value| value.is_finite()),
77            "a filament centroid must be finite"
78        );
79        Self {
80            length,
81            diameter,
82            volume,
83            topology,
84            centroid,
85            bounds,
86        }
87    }
88
89    pub fn length(self) -> f64 {
90        self.length
91    }
92
93    pub fn diameter(self) -> f64 {
94        self.diameter
95    }
96
97    pub fn volume(self) -> f64 {
98        self.volume
99    }
100
101    pub fn topology(self) -> FilamentTopology {
102        self.topology
103    }
104
105    pub fn branches(self) -> u32 {
106        self.topology.branches()
107    }
108
109    pub fn terminals(self) -> u32 {
110        self.topology.terminals()
111    }
112
113    pub fn junctions(self) -> u32 {
114        self.topology.junctions()
115    }
116
117    pub fn nodes(self) -> u32 {
118        self.topology.nodes()
119    }
120
121    pub fn centroid(self) -> [f32; 3] {
122        self.centroid
123    }
124
125    pub fn bounds(self) -> Bounds {
126        self.bounds
127    }
128}
129
130#[derive(Clone, Debug, PartialEq)]
131pub struct FilamentSet {
132    level: usize,
133    geometry: PackedMesh,
134    objects: Vec<FilamentStatistics>,
135    intensities: IntensityTable,
136}
137
138impl FilamentSet {
139    pub fn new(
140        level: usize,
141        mesh: Mesh,
142        objects: Vec<FilamentStatistics>,
143        intensities: IntensityTable,
144    ) -> Self {
145        let geometry = PackedMesh::new(mesh);
146        assert_eq!(
147            geometry.object_count(),
148            objects.len(),
149            "every packed filament names an object"
150        );
151        assert_eq!(
152            geometry.is_empty(),
153            objects.is_empty(),
154            "the mesh and the object list agree on emptiness"
155        );
156        assert_eq!(
157            intensities.objects(),
158            objects.len(),
159            "every filament is measured in every channel"
160        );
161        Self {
162            level,
163            geometry,
164            objects,
165            intensities,
166        }
167    }
168
169    pub fn from_parts(
170        level: usize,
171        geometry: PackedMesh,
172        objects: Vec<FilamentStatistics>,
173        intensities: IntensityTable,
174    ) -> Option<Self> {
175        if geometry.object_count() != objects.len()
176            || geometry.is_empty() != objects.is_empty()
177            || intensities.objects() != objects.len()
178        {
179            return None;
180        }
181        Some(Self {
182            level,
183            geometry,
184            objects,
185            intensities,
186        })
187    }
188
189    pub fn level(&self) -> usize {
190        self.level
191    }
192
193    pub fn geometry(&self) -> &PackedMesh {
194        &self.geometry
195    }
196
197    pub fn objects(&self) -> &[FilamentStatistics] {
198        &self.objects
199    }
200
201    pub fn intensities(&self) -> &IntensityTable {
202        &self.intensities
203    }
204
205    pub fn object(&self, index: usize) -> Option<FilamentStatistics> {
206        self.objects.get(index).copied()
207    }
208
209    pub fn object_count(&self) -> usize {
210        self.objects.len()
211    }
212
213    pub fn is_empty(&self) -> bool {
214        self.objects.is_empty()
215    }
216}