spatialrust_core/
tensor.rs1use std::ops::Range;
8
9use crate::{PointBuffer, PointCloud, PointSchema, SpatialResult};
10
11pub const DEFAULT_SPATIAL_TENSOR_CHUNK_SIZE: usize = 16_384;
13
14#[derive(Clone, Copy, Debug)]
16pub struct SpatialTensor<'a> {
17 cloud: &'a PointCloud,
18 chunk_size: usize,
19}
20
21#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct SpatialTensorChunk {
24 range: Range<usize>,
25}
26
27impl<'a> SpatialTensor<'a> {
28 pub fn new(cloud: &'a PointCloud, chunk_size: usize) -> SpatialResult<Self> {
30 if chunk_size == 0 {
31 return Err(crate::SpatialError::InvalidArgument(
32 "SpatialTensor chunk_size must be positive".into(),
33 ));
34 }
35 Ok(Self { cloud, chunk_size })
36 }
37
38 #[must_use]
40 pub const fn cloud(&self) -> &'a PointCloud {
41 self.cloud
42 }
43
44 #[must_use]
46 pub const fn chunk_size(&self) -> usize {
47 self.chunk_size
48 }
49
50 #[must_use]
52 pub fn schema(&self) -> &PointSchema {
53 self.cloud.schema()
54 }
55
56 #[must_use]
58 pub fn len(&self) -> usize {
59 self.cloud.len()
60 }
61
62 #[must_use]
64 pub fn is_empty(&self) -> bool {
65 self.cloud.is_empty()
66 }
67
68 pub fn chunks(&self) -> impl Iterator<Item = SpatialTensorChunk> + 'a {
70 let len = self.cloud.len();
71 let chunk_size = self.chunk_size;
72 (0..len).step_by(chunk_size).map(move |start| {
73 let end = (start + chunk_size).min(len);
74 SpatialTensorChunk { range: start..end }
75 })
76 }
77}
78
79impl SpatialTensorChunk {
80 #[must_use]
82 pub fn range(&self) -> Range<usize> {
83 self.range.clone()
84 }
85
86 #[must_use]
88 pub fn len(&self) -> usize {
89 self.range.len()
90 }
91
92 #[must_use]
94 pub fn is_empty(&self) -> bool {
95 self.range.is_empty()
96 }
97
98 pub fn field_f32<'a>(&self, cloud: &'a PointCloud, name: &str) -> SpatialResult<&'a [f32]> {
100 let values = cloud.field(name)?.as_f32()?;
101 Ok(&values[self.range.clone()])
102 }
103
104 pub fn field_buffer<'a>(
106 &self,
107 cloud: &'a PointCloud,
108 name: &str,
109 ) -> SpatialResult<SpatialTensorFieldChunk<'a>> {
110 let buffer = cloud.field(name)?;
111 Ok(SpatialTensorFieldChunk { buffer, range: self.range.clone() })
112 }
113}
114
115#[derive(Clone, Debug)]
117pub struct SpatialTensorFieldChunk<'a> {
118 buffer: &'a PointBuffer,
119 range: Range<usize>,
120}
121
122impl<'a> SpatialTensorFieldChunk<'a> {
123 pub fn as_f32(&self) -> SpatialResult<&[f32]> {
125 Ok(&self.buffer.as_f32()?[self.range.clone()])
126 }
127
128 #[must_use]
130 pub fn range(&self) -> Range<usize> {
131 self.range.clone()
132 }
133}
134
135impl PointCloud {
136 pub fn spatial_tensor_chunks(&self, chunk_size: usize) -> SpatialResult<SpatialTensor<'_>> {
138 SpatialTensor::new(self, chunk_size)
139 }
140
141 pub fn spatial_tensor(&self) -> SpatialResult<SpatialTensor<'_>> {
143 self.spatial_tensor_chunks(DEFAULT_SPATIAL_TENSOR_CHUNK_SIZE)
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::{SpatialTensor, DEFAULT_SPATIAL_TENSOR_CHUNK_SIZE};
150 use crate::PointCloudBuilder;
151
152 #[test]
153 fn chunks_cover_all_points() {
154 let mut builder = PointCloudBuilder::xyz();
155 for index in 0..5 {
156 builder.push_point([index as f32, 0.0, 0.0]).unwrap();
157 }
158 let cloud = builder.build().unwrap();
159 let tensor = SpatialTensor::new(&cloud, 2).unwrap();
160 let chunks: Vec<_> = tensor.chunks().collect();
161 assert_eq!(chunks.len(), 3);
162 assert_eq!(chunks[0].len(), 2);
163 assert_eq!(chunks[2].len(), 1);
164 let covered: usize = chunks.iter().map(super::SpatialTensorChunk::len).sum();
165 assert_eq!(covered, cloud.len());
166 }
167
168 #[test]
169 fn field_f32_slice_matches_column() {
170 let mut builder = PointCloudBuilder::xyz();
171 builder.push_point([1.0, 2.0, 3.0]).unwrap();
172 builder.push_point([4.0, 5.0, 6.0]).unwrap();
173 let cloud = builder.build().unwrap();
174 let chunk = SpatialTensor::new(&cloud, DEFAULT_SPATIAL_TENSOR_CHUNK_SIZE)
175 .unwrap()
176 .chunks()
177 .next()
178 .unwrap();
179 assert_eq!(chunk.field_f32(&cloud, "y").unwrap(), &[2.0, 5.0]);
180 }
181
182 #[test]
183 fn rejects_zero_chunk_size() {
184 let cloud = crate::PointCloud::xyz();
185 assert!(SpatialTensor::new(&cloud, 0).is_err());
186 }
187}