1use crate::{Bounds, IntensityTable, Mesh, PackedMesh};
2
3#[derive(Clone, Copy, Debug, PartialEq)]
4pub struct SurfaceStatistics {
5 voxel_count: u64,
6 volume: f64,
7 area: f64,
8 sphericity: f64,
9 centroid: [f32; 3],
10 bounds: Bounds,
11}
12
13impl SurfaceStatistics {
14 pub fn new(
15 voxel_count: u64,
16 volume: f64,
17 area: f64,
18 centroid: [f32; 3],
19 bounds: Bounds,
20 ) -> Self {
21 assert!(voxel_count > 0, "a surface encloses at least one voxel");
22 assert!(
23 volume.is_finite() && volume > 0.0,
24 "a surface volume is a positive volume"
25 );
26 assert!(
27 area.is_finite() && area > 0.0,
28 "a surface area is a positive area"
29 );
30 assert!(
31 centroid.iter().all(|value| value.is_finite()),
32 "a surface centroid must be finite"
33 );
34 let sphericity =
35 (std::f64::consts::PI.cbrt() * (6.0 * volume).powf(2.0 / 3.0) / area).clamp(0.0, 1.0);
36 Self {
37 voxel_count,
38 volume,
39 area,
40 sphericity,
41 centroid,
42 bounds,
43 }
44 }
45
46 pub fn voxel_count(self) -> u64 {
47 self.voxel_count
48 }
49
50 pub fn volume(self) -> f64 {
51 self.volume
52 }
53
54 pub fn area(self) -> f64 {
55 self.area
56 }
57
58 pub fn sphericity(self) -> f64 {
59 self.sphericity
60 }
61
62 pub fn centroid(self) -> [f32; 3] {
63 self.centroid
64 }
65
66 pub fn bounds(self) -> Bounds {
67 self.bounds
68 }
69}
70
71#[derive(Clone, Debug, PartialEq)]
72pub struct SurfaceSet {
73 level: usize,
74 geometry: PackedMesh,
75 objects: Vec<SurfaceStatistics>,
76 intensities: IntensityTable,
77}
78
79impl SurfaceSet {
80 pub fn new(
81 level: usize,
82 mesh: Mesh,
83 objects: Vec<SurfaceStatistics>,
84 intensities: IntensityTable,
85 ) -> Self {
86 let geometry = PackedMesh::new(mesh);
87 assert_eq!(
88 geometry.object_count(),
89 objects.len(),
90 "every packed surface names an object"
91 );
92 assert_eq!(
93 geometry.is_empty(),
94 objects.is_empty(),
95 "the mesh and the object list agree on emptiness"
96 );
97 assert_eq!(
98 intensities.objects(),
99 objects.len(),
100 "every surface is measured in every channel"
101 );
102 Self {
103 level,
104 geometry,
105 objects,
106 intensities,
107 }
108 }
109
110 pub fn from_parts(
111 level: usize,
112 geometry: PackedMesh,
113 objects: Vec<SurfaceStatistics>,
114 intensities: IntensityTable,
115 ) -> Option<Self> {
116 if geometry.object_count() != objects.len()
117 || geometry.is_empty() != objects.is_empty()
118 || intensities.objects() != objects.len()
119 {
120 return None;
121 }
122 Some(Self {
123 level,
124 geometry,
125 objects,
126 intensities,
127 })
128 }
129
130 pub fn level(&self) -> usize {
131 self.level
132 }
133
134 pub fn geometry(&self) -> &PackedMesh {
135 &self.geometry
136 }
137
138 pub fn objects(&self) -> &[SurfaceStatistics] {
139 &self.objects
140 }
141
142 pub fn intensities(&self) -> &IntensityTable {
143 &self.intensities
144 }
145
146 pub fn object(&self, index: usize) -> Option<SurfaceStatistics> {
147 self.objects.get(index).copied()
148 }
149
150 pub fn object_count(&self) -> usize {
151 self.objects.len()
152 }
153
154 pub fn is_empty(&self) -> bool {
155 self.objects.is_empty()
156 }
157}