Skip to main content

flydra2/
frame_bundler.rs

1// Copyright (C) The Strand-Braid Authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use std::{cmp::Ordering, pin::Pin};
5
6use futures::{
7    stream::Stream,
8    task::{Context, Poll},
9};
10use pin_project::pin_project;
11
12use crate::FrameDataAndPoints;
13
14use crate::bundled_data::BundledAllCamsOneFrameDistorted;
15use crate::connected_camera_manager::HasCameraList;
16
17/// Orders data from all available cameras from a given frame.
18///
19/// The returned stream will be monotonically increasing. Note that out-of-order
20/// data will be dropped and, although the returned values will be monotonically
21/// increasing, it will not, in general, be contiguous. In otherwords, it is
22/// possible that there will be gaps in the resulting monotonically increasing
23/// sequence.
24#[pin_project]
25pub(crate) struct OrderedLossyFrameBundler<St, HCL>
26where
27    St: Stream<Item = StreamItem>,
28    HCL: HasCameraList,
29{
30    #[pin]
31    stream: St,
32    ccm: HCL,
33    current: Option<BundledAllCamsOneFrameDistorted>,
34    #[pin]
35    pending: Option<StreamItem>,
36}
37
38#[derive(Debug)]
39pub enum StreamItem {
40    EOF,
41    Packet(FrameDataAndPoints),
42}
43
44impl<St, HCL> OrderedLossyFrameBundler<St, HCL>
45where
46    St: Stream<Item = StreamItem>,
47    HCL: HasCameraList,
48{
49    fn new(stream: St, ccm: HCL) -> Self {
50        Self {
51            stream,
52            ccm,
53            current: None,
54            pending: None,
55        }
56    }
57}
58
59impl<St, HCL> Stream for OrderedLossyFrameBundler<St, HCL>
60where
61    St: Stream<Item = StreamItem>,
62    HCL: HasCameraList,
63{
64    // In theory, it would be possible to return contiguous frame values,
65    // but this would require more complexity here.
66    type Item = BundledAllCamsOneFrameDistorted;
67    fn poll_next(
68        mut self: Pin<&mut Self>,
69        cx: &mut Context<'_>,
70    ) -> Poll<Option<BundledAllCamsOneFrameDistorted>> {
71        use futures::ready;
72
73        loop {
74            let all_cameras = self.ccm.camera_list();
75
76            let mut this = self.as_mut().project();
77
78            // ensure that we have a pending item to work with, return if not.
79            if this.pending.is_none() {
80                let item = match ready!(this.stream.poll_next(cx)) {
81                    Some(e) => e,
82                    None => {
83                        return Poll::Ready(None);
84                    }
85                };
86                this.pending.set(Some(item));
87            }
88
89            // The following unwrap cannot fail because of above.
90            let new_item: FrameDataAndPoints = match this.pending.take().unwrap() {
91                StreamItem::EOF => {
92                    return Poll::Ready(this.current.take());
93                }
94                StreamItem::Packet(new_item) => new_item,
95            };
96
97            if this.current.is_none() {
98                // In the case of no existing data, save this new frame data.
99                *this.current = Some(BundledAllCamsOneFrameDistorted::new(new_item));
100            } else {
101                // In the case of existing data, check if we are done or not.
102                let dt = {
103                    let current = this.current.as_ref().unwrap();
104                    new_item.frame_data.synced_frame.0 as i64 - current.frame().0 as i64
105                };
106
107                match dt.cmp(&0) {
108                    Ordering::Equal => {
109                        // new packet from ongoing frame.
110                        let current: &mut Option<BundledAllCamsOneFrameDistorted> = this.current;
111                        let current_cameras = {
112                            let x = current.as_mut().unwrap();
113                            x.push(new_item);
114                            x.cameras()
115                        };
116
117                        if current_cameras == &all_cameras {
118                            let previous = current.take().unwrap();
119                            *current = None;
120                            return Poll::Ready(Some(previous));
121                        }
122                    }
123                    Ordering::Greater => {
124                        // New packet from future frame. Return the accumulated-
125                        // until now data and start accumulating from this new
126                        // data.
127                        let current: &mut Option<BundledAllCamsOneFrameDistorted> = this.current;
128                        let previous = current.take().unwrap();
129                        *current = Some(BundledAllCamsOneFrameDistorted::new(new_item));
130                        return Poll::Ready(Some(previous));
131                    }
132                    Ordering::Less => {
133                        // Drop `new_item` because it arrived too late: the
134                        // current frame has already advanced past it, so this
135                        // data cannot be included in the live 3D reconstruction.
136                        // It remains on disk, so offline retracking can still use
137                        // it. This drop is otherwise invisible; logging it (at
138                        // debug level, opt-in via RUST_LOG) makes the
139                        // live-vs-retrack data loss observable when diagnosing
140                        // unexpectedly short live trajectories.
141                        tracing::debug!(
142                            "dropping late camera data: cam {} frame {} arrived {} frame(s) \
143                             after the current frame and was excluded from live tracking",
144                            new_item.frame_data.cam_name.as_str(),
145                            new_item.frame_data.synced_frame.0,
146                            -dt,
147                        );
148                    }
149                }
150            }
151        }
152    }
153}
154
155pub(crate) fn bundle_frames<St, HCL>(stream: St, ccm: HCL) -> OrderedLossyFrameBundler<St, HCL>
156where
157    St: Stream<Item = StreamItem>,
158    HCL: HasCameraList,
159{
160    OrderedLossyFrameBundler::new(stream, ccm)
161}
162
163#[test]
164fn test_frame_bundler() {
165    use futures::stream::{self, StreamExt};
166
167    use crate::{FlydraFloatTimestampLocal, FrameData, SyncFno};
168
169    let cam_name_1 = crate::RawCamName::new("cam1".into());
170    let cam_num_1 = crate::CamNum(1);
171    let cam_name_2 = crate::RawCamName::new("cam2".into());
172    let cam_num_2 = crate::CamNum(2);
173    let trigger_timestamp = None;
174
175    let packet1_frame1_cam1 = FrameDataAndPoints {
176        frame_data: FrameData::new(
177            cam_name_1,
178            cam_num_1,
179            SyncFno(1),
180            trigger_timestamp.clone(),
181            FlydraFloatTimestampLocal::from_f64(0.0),
182            None,
183            None,
184        ),
185        points: Vec::new(),
186    };
187
188    let packet2_frame1_cam2 = FrameDataAndPoints {
189        frame_data: FrameData::new(
190            cam_name_2.clone(),
191            cam_num_2,
192            SyncFno(1),
193            trigger_timestamp.clone(),
194            FlydraFloatTimestampLocal::from_f64(0.0),
195            None,
196            None,
197        ),
198        points: Vec::new(),
199    };
200
201    let packet2_frame0_cam2 = FrameDataAndPoints {
202        frame_data: FrameData::new(
203            cam_name_2.clone(),
204            cam_num_2,
205            SyncFno(0),
206            trigger_timestamp.clone(),
207            FlydraFloatTimestampLocal::from_f64(0.0),
208            None,
209            None,
210        ),
211        points: Vec::new(),
212    };
213
214    let packet2_frame2_cam2 = FrameDataAndPoints {
215        frame_data: FrameData::new(
216            cam_name_2.clone(),
217            cam_num_2,
218            SyncFno(2),
219            trigger_timestamp.clone(),
220            FlydraFloatTimestampLocal::from_f64(0.0),
221            None,
222            None,
223        ),
224        points: Vec::new(),
225    };
226
227    let packet2_frame3_cam2 = FrameDataAndPoints {
228        frame_data: FrameData::new(
229            cam_name_2,
230            cam_num_2,
231            SyncFno(3),
232            trigger_timestamp,
233            FlydraFloatTimestampLocal::from_f64(0.0),
234            None,
235            None,
236        ),
237        points: Vec::new(),
238    };
239
240    // with zero packets
241
242    let inputs: Vec<_> = vec![StreamItem::EOF];
243
244    let cameras = crate::connected_camera_manager::CameraList::new(&[1, 2]);
245    let bundled = bundle_frames(stream::iter(inputs), cameras.clone());
246    let actual: Vec<_> = futures::executor::block_on(bundled.collect());
247    assert_eq!(actual.len(), 0);
248
249    // with one packet
250
251    let inputs: Vec<_> = vec![
252        StreamItem::Packet(packet1_frame1_cam1.clone()),
253        StreamItem::EOF,
254    ];
255
256    let expected = packet1_frame1_cam1.clone();
257    let bundled = bundle_frames(stream::iter(inputs), cameras.clone());
258    let actual: Vec<_> = futures::executor::block_on(bundled.collect());
259    assert_eq!(actual.len(), 1);
260    assert_eq!(
261        actual[0],
262        BundledAllCamsOneFrameDistorted::new(expected.clone())
263    );
264
265    // with two packets from same frame
266
267    let inputs: Vec<_> = vec![
268        StreamItem::Packet(packet1_frame1_cam1.clone()),
269        StreamItem::Packet(packet2_frame1_cam2.clone()),
270        StreamItem::EOF,
271    ];
272
273    let bundled = bundle_frames(stream::iter(inputs), cameras.clone());
274    let actual: Vec<_> = futures::executor::block_on(bundled.collect());
275    assert_eq!(actual.len(), 1);
276    assert_eq!(actual[0].num_cameras(), 2);
277
278    // with two packets from with a later outdated frame
279
280    let inputs: Vec<_> = vec![
281        StreamItem::Packet(packet1_frame1_cam1.clone()),
282        StreamItem::Packet(packet2_frame0_cam2),
283        StreamItem::EOF,
284    ];
285
286    let bundled = bundle_frames(stream::iter(inputs), cameras.clone());
287    let actual: Vec<_> = futures::executor::block_on(bundled.collect());
288    assert_eq!(actual.len(), 1);
289    assert_eq!(actual[0], BundledAllCamsOneFrameDistorted::new(expected));
290
291    // with two subsequent packets
292
293    let inputs: Vec<_> = vec![
294        StreamItem::Packet(packet1_frame1_cam1.clone()),
295        StreamItem::Packet(packet2_frame2_cam2),
296        StreamItem::EOF,
297    ];
298
299    let bundled = bundle_frames(stream::iter(inputs), cameras.clone());
300    let actual: Vec<_> = futures::executor::block_on(bundled.collect());
301    assert_eq!(actual.len(), 2);
302    assert_eq!(actual[0].num_cameras(), 1);
303    assert_eq!(actual[1].num_cameras(), 1);
304
305    // with non-adjacent subsequent packets
306
307    let inputs: Vec<_> = vec![
308        StreamItem::Packet(packet1_frame1_cam1.clone()),
309        StreamItem::Packet(packet2_frame3_cam2),
310        StreamItem::EOF,
311    ];
312
313    let bundled = bundle_frames(stream::iter(inputs), cameras.clone());
314    let actual: Vec<_> = futures::executor::block_on(bundled.collect());
315    assert_eq!(actual.len(), 2);
316    assert_eq!(actual[0].num_cameras(), 1);
317    assert_eq!(actual[1].num_cameras(), 1);
318
319    // At the moment all frames arrived and not one frame later. Thus, no EOF
320    // marker.
321
322    let inputs: Vec<_> = vec![
323        StreamItem::Packet(packet1_frame1_cam1.clone()),
324        StreamItem::Packet(packet2_frame1_cam2),
325    ];
326
327    let bundled = bundle_frames(stream::iter(inputs), cameras.clone());
328    let actual: Vec<_> = futures::executor::block_on(bundled.collect());
329    assert_eq!(actual.len(), 1);
330    assert_eq!(actual[0].num_cameras(), 2);
331
332    // But not if only one frame arrives.
333
334    let inputs: Vec<_> = vec![StreamItem::Packet(packet1_frame1_cam1)];
335
336    let bundled = bundle_frames(stream::iter(inputs), cameras);
337    let actual: Vec<_> = futures::executor::block_on(bundled.collect());
338    assert_eq!(actual.len(), 0);
339}
340
341#[test]
342fn test_async_stream_ops() {
343    use futures::future;
344    use futures::stream::{self, StreamExt};
345
346    let stream = stream::iter(1..=10);
347    let evens = stream.filter_map(|x| {
348        let ret = if x % 2 == 0 { Some(x + 1) } else { None };
349        future::ready(ret)
350    });
351
352    let result: Vec<_> = futures::executor::block_on(evens.collect());
353    assert_eq!(vec![3, 5, 7, 9, 11], result);
354}