Skip to main content

grana_object/
track.rs

1#[derive(Clone, Copy, Debug, PartialEq)]
2pub struct TrackNode {
3    time: u32,
4    object: usize,
5    centroid: [f32; 3],
6}
7
8impl TrackNode {
9    pub fn new(time: u32, object: usize, centroid: [f32; 3]) -> Self {
10        assert!(
11            centroid.iter().all(|value| value.is_finite()),
12            "a track node lies at a finite centroid"
13        );
14        Self {
15            time,
16            object,
17            centroid,
18        }
19    }
20
21    pub fn time(self) -> u32 {
22        self.time
23    }
24
25    pub fn object(self) -> usize {
26        self.object
27    }
28
29    pub fn centroid(self) -> [f32; 3] {
30        self.centroid
31    }
32}
33
34#[derive(Clone, Copy, Debug, PartialEq)]
35pub struct TrackStatistics {
36    frames: u32,
37    duration: f64,
38    length: f64,
39    displacement: f64,
40    speed: f64,
41    straightness: f64,
42}
43
44impl TrackStatistics {
45    pub fn frames(self) -> u32 {
46        self.frames
47    }
48
49    pub fn duration(self) -> f64 {
50        self.duration
51    }
52
53    pub fn length(self) -> f64 {
54        self.length
55    }
56
57    pub fn displacement(self) -> f64 {
58        self.displacement
59    }
60
61    pub fn speed(self) -> f64 {
62        self.speed
63    }
64
65    pub fn straightness(self) -> f64 {
66        self.straightness
67    }
68}
69
70#[derive(Clone, Debug, PartialEq)]
71pub struct Track {
72    parent: Option<u32>,
73    nodes: Vec<TrackNode>,
74}
75
76impl Track {
77    pub fn new(parent: Option<u32>, nodes: Vec<TrackNode>) -> Self {
78        assert!(!nodes.is_empty(), "a track holds at least one node");
79        for pair in nodes.windows(2) {
80            assert!(
81                pair[1].time > pair[0].time,
82                "track nodes follow the time axis"
83            );
84        }
85        Self { parent, nodes }
86    }
87
88    pub fn parent(&self) -> Option<u32> {
89        self.parent
90    }
91
92    pub fn nodes(&self) -> &[TrackNode] {
93        &self.nodes
94    }
95
96    pub fn start(&self) -> u32 {
97        self.nodes.first().expect("a track holds nodes").time
98    }
99
100    pub fn end(&self) -> u32 {
101        self.nodes.last().expect("a track holds nodes").time
102    }
103
104    pub fn statistics(&self, frame_interval: f32) -> TrackStatistics {
105        assert!(
106            frame_interval.is_finite() && frame_interval > 0.0,
107            "the frame interval is a positive duration"
108        );
109        let first = self.nodes.first().expect("a track holds nodes");
110        let last = self.nodes.last().expect("a track holds nodes");
111        let frames = last.time - first.time + 1;
112        let duration = f64::from(frames - 1) * f64::from(frame_interval);
113        let length = self
114            .nodes
115            .windows(2)
116            .map(|pair| distance(pair[0].centroid, pair[1].centroid))
117            .sum::<f64>();
118        let displacement = distance(first.centroid, last.centroid);
119        let speed = if duration > 0.0 {
120            length / duration
121        } else {
122            0.0
123        };
124        let straightness = if length > 0.0 {
125            (displacement / length).clamp(0.0, 1.0)
126        } else {
127            0.0
128        };
129        TrackStatistics {
130            frames,
131            duration,
132            length,
133            displacement,
134            speed,
135            straightness,
136        }
137    }
138}
139
140#[derive(Clone, Debug, PartialEq)]
141pub struct TrackSet {
142    frame_interval: f32,
143    tracks: Vec<Track>,
144    links: Vec<(u32, usize, u32)>,
145    divisions: usize,
146}
147
148impl TrackSet {
149    pub fn new(frame_interval: f32, tracks: Vec<Track>) -> Self {
150        assert!(
151            frame_interval.is_finite() && frame_interval > 0.0,
152            "the frame interval is a positive duration"
153        );
154        let mut children = vec![0usize; tracks.len()];
155        for (index, track) in tracks.iter().enumerate() {
156            if let Some(parent) = track.parent() {
157                let parent = parent as usize;
158                assert!(parent < index, "a track follows its parent");
159                assert!(
160                    tracks[parent].end() < track.start(),
161                    "a child track starts after its parent ends"
162                );
163                children[parent] += 1;
164            }
165        }
166        for count in &children {
167            assert!(
168                *count == 0 || *count == 2,
169                "a division produces exactly two daughter tracks"
170            );
171        }
172        let divisions = children.iter().filter(|count| **count == 2).count();
173        let mut links = Vec::new();
174        for (index, track) in tracks.iter().enumerate() {
175            for node in track.nodes() {
176                links.push((node.time(), node.object(), index as u32));
177            }
178        }
179        links.sort_unstable();
180        assert!(
181            links
182                .windows(2)
183                .all(|pair| (pair[0].0, pair[0].1) != (pair[1].0, pair[1].1)),
184            "every detection belongs to one track"
185        );
186        Self {
187            frame_interval,
188            tracks,
189            links,
190            divisions,
191        }
192    }
193
194    pub fn frame_interval(&self) -> f32 {
195        self.frame_interval
196    }
197
198    pub fn tracks(&self) -> &[Track] {
199        &self.tracks
200    }
201
202    pub fn len(&self) -> usize {
203        self.tracks.len()
204    }
205
206    pub fn is_empty(&self) -> bool {
207        self.tracks.is_empty()
208    }
209
210    pub fn node_count(&self) -> usize {
211        self.links.len()
212    }
213
214    pub fn divisions(&self) -> usize {
215        self.divisions
216    }
217
218    pub fn track(&self, index: usize) -> Option<&Track> {
219        self.tracks.get(index)
220    }
221
222    pub fn track_at(&self, time: u32, object: usize) -> Option<usize> {
223        self.links
224            .binary_search_by_key(&(time, object), |link| (link.0, link.1))
225            .ok()
226            .map(|index| self.links[index].2 as usize)
227    }
228
229    pub fn statistics(&self, index: usize) -> Option<TrackStatistics> {
230        self.track(index)
231            .map(|track| track.statistics(self.frame_interval))
232    }
233
234    pub fn children(&self, index: usize) -> impl Iterator<Item = usize> + '_ {
235        self.tracks
236            .iter()
237            .enumerate()
238            .filter(move |(_, track)| track.parent() == Some(index as u32))
239            .map(|(child, _)| child)
240    }
241
242    pub fn mesh(&self, radius: f32) -> crate::Mesh {
243        assert!(
244            radius.is_finite() && radius > 0.0,
245            "a track tube radius must be a positive length"
246        );
247        let mut vertices = Vec::new();
248        let mut indices = Vec::new();
249        for (index, track) in self.tracks.iter().enumerate() {
250            let object = index as u32;
251            let mut points = Vec::with_capacity(track.nodes().len() + 1);
252            if let Some(parent) = track.parent() {
253                let parent = self
254                    .track(parent as usize)
255                    .expect("the parent track exists");
256                let end = parent.nodes().last().expect("the parent holds nodes");
257                points.push(glam::Vec3::from(end.centroid()));
258            }
259            points.extend(
260                track
261                    .nodes()
262                    .iter()
263                    .map(|node| glam::Vec3::from(node.centroid())),
264            );
265            deduplicate(&mut points);
266            match points.len() {
267                0 => {}
268                1 => crate::push_sphere(
269                    &mut vertices,
270                    &mut indices,
271                    points[0].to_array(),
272                    radius,
273                    object,
274                ),
275                _ => {
276                    let positions = points
277                        .iter()
278                        .map(|point| point.to_array())
279                        .collect::<Vec<_>>();
280                    let radii = vec![radius; positions.len()];
281                    crate::push_tube(&mut vertices, &mut indices, &positions, &radii, object);
282                }
283            }
284        }
285        crate::Mesh::new(vertices, indices)
286    }
287}
288
289fn deduplicate(points: &mut Vec<glam::Vec3>) {
290    let mut unique = Vec::with_capacity(points.len());
291    for point in points.drain(..) {
292        if unique
293            .last()
294            .is_none_or(|last: &glam::Vec3| last.distance_squared(point) > 1.0e-12)
295        {
296            unique.push(point);
297        }
298    }
299    *points = unique;
300}
301
302fn distance(left: [f32; 3], right: [f32; 3]) -> f64 {
303    (0..3)
304        .map(|axis| f64::from(right[axis] - left[axis]).powi(2))
305        .sum::<f64>()
306        .sqrt()
307}