1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
//! Caller-owned categorical label volumes and reversible segment styles.
use crate::{CoreError, VolumeRegion, VolumeSlice};
use molgfx_math::{Aabb, Mat4, Quat, Rgba8, Vec3};
use std::sync::Arc;
#[cfg(test)]
#[path = "segmentation_tests.rs"]
mod tests;
/// A row-major, x-fastest integer label grid with affine world placement.
#[derive(Clone, Debug)]
pub struct SegmentedVolume {
dimensions: [u32; 3],
voxel_to_world: Mat4,
labels: Arc<[u32]>,
}
impl SegmentedVolume {
/// Validates a categorical grid without copying its shared label storage.
///
/// Dimensions must be at least two and fit the portable 3-D texture
/// ceiling. Labels are opaque caller data, so every `u32` value is valid.
///
/// # Errors
///
/// Returns [`CoreError::InvalidSegmentation`] when dimensions, label
/// count, or the voxel transform is invalid.
pub fn new(
dimensions: [u32; 3],
voxel_to_world: Mat4,
labels: Arc<[u32]>,
) -> Result<Self, CoreError> {
if dimensions
.iter()
.any(|&dimension| !(2..=u32::from(u16::MAX)).contains(&dimension))
{
return Err(invalid("dimensions must be between 2 and 65535"));
}
let Some(voxels) = dimensions.iter().try_fold(1u64, |product, &dimension| {
product.checked_mul(u64::from(dimension))
}) else {
return Err(invalid("dimension product overflows"));
};
if usize::try_from(voxels).ok() != Some(labels.len()) {
return Err(invalid("label count does not match dimensions"));
}
if !voxel_to_world
.to_cols_array()
.iter()
.all(|component| component.is_finite())
|| !voxel_to_world.determinant().is_finite()
|| voxel_to_world.determinant().abs() <= 1e-8
{
return Err(invalid("voxel transform must be finite and invertible"));
}
Ok(Self {
dimensions,
voxel_to_world,
labels,
})
}
/// Builds an axis-aligned categorical grid from an origin and positive
/// voxel spacing.
///
/// # Errors
///
/// Returns [`CoreError::InvalidSegmentation`] when the origin or
/// spacing is non-finite, spacing is not positive, or the labels do
/// not match the requested dimensions.
pub fn from_spacing(
dimensions: [u32; 3],
origin: Vec3,
spacing: Vec3,
labels: Arc<[u32]>,
) -> Result<Self, CoreError> {
if !origin.is_finite() || !spacing.is_finite() || spacing.min_element() <= 0.0 {
return Err(invalid("origin and positive spacing must be finite"));
}
Self::new(
dimensions,
Mat4::from_scale_rotation_translation(spacing, Quat::default(), origin),
labels,
)
}
/// Grid dimensions in x, y and z order.
#[must_use]
pub const fn dimensions(&self) -> [u32; 3] {
self.dimensions
}
/// Affine transform from voxel-index coordinates to world Ångström.
#[must_use]
pub const fn voxel_to_world(&self) -> Mat4 {
self.voxel_to_world
}
/// Shared row-major labels; x varies fastest.
#[must_use]
pub fn labels(&self) -> &[u32] {
&self.labels
}
/// Tight world-space axis-aligned bound around the transformed grid.
#[must_use]
pub fn world_aabb(&self) -> Aabb {
let maximum = Vec3::new(
dimension_f32(self.dimensions[0] - 1),
dimension_f32(self.dimensions[1] - 1),
dimension_f32(self.dimensions[2] - 1),
);
Aabb::new(Vec3::ZERO, maximum).transform(&self.voxel_to_world)
}
}
/// One independent visual style for one integer label.
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct SegmentStyle {
/// Exact caller-supplied voxel label.
pub label: u32,
/// Reversible display color.
pub color: Rgba8,
/// Optical opacity in [0, 1]; zero hides the label.
pub opacity: f32,
}
impl SegmentStyle {
/// Creates one label style.
#[must_use]
pub const fn new(label: u32, color: Rgba8, opacity: f32) -> Self {
Self {
label,
color,
opacity,
}
}
}
/// Immutable, sorted, shareable styles for a categorical volume.
#[derive(Clone, PartialEq, Debug)]
pub struct SegmentStyleTable {
styles: Arc<[SegmentStyle]>,
}
impl SegmentStyleTable {
/// Validates and sorts styles by label.
///
/// # Errors
///
/// Labels must be unique and opacities must be finite in [0, 1].
pub fn new(styles: &[SegmentStyle]) -> Result<Self, CoreError> {
if styles
.iter()
.any(|style| !style.opacity.is_finite() || !(0.0..=1.0).contains(&style.opacity))
{
return Err(invalid("segment opacity must be finite in [0, 1]"));
}
let mut sorted = styles.to_vec();
sorted.sort_unstable_by_key(|style| style.label);
if sorted.windows(2).any(|pair| pair[0].label == pair[1].label) {
return Err(invalid("segment labels must be unique"));
}
Ok(Self {
styles: Arc::from(sorted.into_boxed_slice()),
})
}
/// Styles in deterministic ascending-label order.
#[must_use]
pub fn styles(&self) -> &[SegmentStyle] {
&self.styles
}
/// Binary-searches one style by exact label.
#[must_use]
pub fn style_for(&self, label: u32) -> Option<SegmentStyle> {
self.styles
.binary_search_by_key(&label, |style| style.label)
.ok()
.and_then(|index| self.styles.get(index).copied())
}
}
impl Default for SegmentStyleTable {
fn default() -> Self {
Self {
styles: Arc::from([]),
}
}
}
/// Sampling and clipping controls for one categorical representation.
#[derive(Clone, PartialEq, Debug)]
pub struct SegmentationStyle {
/// Independent label-to-color-and-opacity mapping.
pub styles: SegmentStyleTable,
/// Global multiplier applied after the per-label opacity.
pub opacity_scale: f32,
/// Ray step relative to the smallest voxel axis.
pub step_scale: f32,
/// Optional world-space plane sampled as one categorical slice.
pub slice: Option<VolumeSlice>,
/// Optional half-open voxel region rendered from the resident grid.
pub region: Option<VolumeRegion>,
}
impl Default for SegmentationStyle {
fn default() -> Self {
Self {
styles: SegmentStyleTable::default(),
opacity_scale: 1.0,
step_scale: 0.65,
slice: None,
region: None,
}
}
}
const fn invalid(reason: &'static str) -> CoreError {
CoreError::InvalidSegmentation { reason }
}
fn dimension_f32(value: u32) -> f32 {
match u16::try_from(value) {
Ok(value) => f32::from(value),
Err(_) => f32::from(u16::MAX),
}
}