Skip to main content

ufmf/
lib.rs

1// Copyright (C) The Strand-Braid Authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4#[macro_use]
5extern crate structure;
6
7use std::collections::BTreeMap;
8use std::f64;
9use std::io::{Seek, SeekFrom, Write};
10
11use chrono::{DateTime, Utc};
12use formats::{ImageStride, PixelFormat, pixel_format::PixFmt};
13use machine_vision_formats as formats;
14use strand_dynamic_frame::{DynamicFrame, match_all_dynamic_fmts};
15
16pub type UFMFResult<M> = std::result::Result<M, UFMFError>;
17
18mod save_indices;
19
20#[derive(Debug, thiserror::Error)]
21pub enum UFMFError {
22    #[error("unimplemented pixel_format {0}")]
23    UnimplementedPixelFormat(PixFmt),
24
25    #[error("already closed")]
26    AlreadyClosed,
27
28    #[error("the pixel format changed")]
29    FormatChanged,
30
31    #[error("{source}")]
32    Io {
33        #[from]
34        source: std::io::Error,
35    },
36    #[error("{0}")]
37    Cast(#[from] cast::Error),
38}
39
40const KEYFRAME_CHUNK: u8 = 0;
41const FRAME_CHUNK: u8 = 1;
42const INDEX_DICT_CHUNK: u8 = 2;
43
44fn pack_header(v: u32, index_loc: u64, w: u16, h: u16, cl: u8) -> std::io::Result<Vec<u8>> {
45    #[expect(clippy::too_many_arguments)]
46    structure!("<4sIQHHB").pack(b"ufmf", v, index_loc, w, h, cl)
47}
48
49fn write_header<F: Write + Seek>(
50    f: &mut F,
51    index_loc: usize,
52    max_width: u16,
53    max_height: u16,
54    pixel_format: PixFmt,
55) -> UFMFResult<usize> {
56    let coding = get_format(pixel_format)?;
57
58    let buf: Vec<u8> = pack_header(
59        3,
60        cast::u64(index_loc),
61        max_width,
62        max_height,
63        cast::u8(coding.len())?,
64    )?;
65
66    let mut pos = 0;
67    pos += f.write(&buf)?;
68    pos += f.write(&coding)?;
69    Ok(pos)
70}
71
72fn write_image<F: Write + Seek, FMT>(
73    f: &mut F,
74    frame: &dyn ImageStride<FMT>,
75    bytes_per_pixel: u8,
76    rect: &RectFromCorner,
77) -> UFMFResult<usize> {
78    let image_data = frame.image_data();
79    let xoffset = rect.x0 as usize * bytes_per_pixel as usize;
80    let row_bytes = rect.w as usize * bytes_per_pixel as usize;
81    let mut pos = 0;
82
83    for i in rect.y0 as usize..(rect.y0 + rect.h) as usize {
84        let start = i * frame.stride() + xoffset;
85        let stop = start + row_bytes;
86        let row_data = &image_data[start..stop];
87        pos += f.write(row_data)?;
88    }
89    Ok(pos)
90}
91
92fn get_format(pixel_format: PixFmt) -> UFMFResult<Vec<u8>> {
93    use PixFmt::*;
94    let r = match pixel_format {
95        Mono8 => b"MONO8".to_vec(),
96        // Mono32f => b"MONO32f".to_vec(),
97        BayerRG8 => b"RAW8:RGGB".to_vec(),
98        BayerGB8 => b"RAW8:GBRG".to_vec(),
99        BayerGR8 => b"RAW8:GRBG".to_vec(),
100        BayerBG8 => b"RAW8:BGGR".to_vec(),
101        YUV422 => b"YUV422".to_vec(),
102        RGB8 => b"RGB8".to_vec(),
103        f => {
104            return Err(UFMFError::UnimplementedPixelFormat(f));
105        }
106    };
107    Ok(r)
108}
109
110fn get_dtype(pixel_format: formats::pixel_format::PixFmt) -> UFMFResult<u8> {
111    use formats::pixel_format::PixFmt::*;
112    let r = match pixel_format {
113        Mono8 | BayerRG8 | BayerGB8 | BayerGR8 | BayerBG8 | YUV422 | RGB8 => b'B',
114        Mono32f | BayerRG32f | BayerGB32f | BayerGR32f | BayerBG32f => b'f',
115        x => {
116            return Err(UFMFError::UnimplementedPixelFormat(x));
117        }
118    };
119    Ok(r)
120}
121
122pub struct UFMFWriter<F: Write + Seek> {
123    f: Option<F>,
124    pos: usize,
125    max_width: u16,
126    max_height: u16,
127    xinc: u8,
128    yinc: u8,
129    index_frame: Vec<TimestampLoc>,
130    index_keyframes: BTreeMap<Vec<u8>, Vec<TimestampLoc>>,
131    bytes_per_pixel: u8,
132    pixel_format: formats::pixel_format::PixFmt,
133}
134
135impl<F: Write + Seek> std::fmt::Debug for UFMFWriter<F> {
136    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
137        write!(f, "UFMFWriter {{ }}")
138    }
139}
140
141struct TimestampLoc {
142    timestamp: f64,
143    loc: u64,
144}
145
146/// Specifies a rectangular region, drawn from center.
147pub struct RectFromCenter {
148    /// x center of region
149    x: u16,
150    /// y center of region
151    y: u16,
152    /// width of region
153    w: u16,
154    /// height of region
155    h: u16,
156}
157
158impl RectFromCenter {
159    pub fn from_xy_wh(x: u16, y: u16, w: u16, h: u16) -> Self {
160        Self { x, y, w, h }
161    }
162}
163
164/// Specifies a rectangular region, drawn from lower-left.
165pub struct RectFromCorner {
166    /// x lower left of region
167    x0: u16,
168    /// y lower left of region
169    y0: u16,
170    /// width of region
171    w: u16,
172    /// height of region
173    h: u16,
174}
175
176struct Region<'a, 'b> {
177    origframe: &'a DynamicFrame<'b>,
178    rect: &'a RectFromCorner,
179}
180
181fn do_size(x: u16, mut w: u16, xinc: u16, max_width: u16) -> (u16, u16) {
182    let w_radius = w / 2;
183
184    let xmin = x.saturating_sub(w_radius) / xinc * xinc; // # keep 2x2 Bayer
185
186    let xmax = xmin + w;
187    let newxmax = if xmax < max_width { xmax } else { max_width };
188    if newxmax != xmax {
189        w = newxmax - xmin;
190    }
191    (xmin, w)
192}
193
194impl<F> UFMFWriter<F>
195where
196    F: Write + Seek,
197{
198    pub fn new(
199        mut f: F,
200        max_width: u16,
201        max_height: u16,
202        pixel_format: PixFmt,
203        frame_timestamp0: Option<(&DynamicFrame, DateTime<Utc>)>,
204    ) -> UFMFResult<Self> {
205        if let Some((frame0, _timestamp0)) = frame_timestamp0.as_ref()
206            && frame0.pixel_format() != pixel_format
207        {
208            return Err(UFMFError::FormatChanged);
209        }
210        let pos = write_header(&mut f, 0, max_width, max_height, pixel_format)?;
211
212        use PixFmt::*;
213        let (xinc, yinc) = match pixel_format {
214            Mono8 | BayerRG8 | BayerGB8 | BayerGR8 | BayerBG8 => (2, 2),
215            YUV422 => (4, 1),
216            e => {
217                return Err(UFMFError::UnimplementedPixelFormat(e));
218            }
219        };
220
221        let bytes_per_pixel = pixel_format.bits_per_pixel() / 8;
222
223        let f = Some(f);
224
225        let mut result = Self {
226            f,
227            pos,
228            max_width,
229            max_height,
230            xinc,
231            yinc,
232            index_frame: Vec::new(),
233            index_keyframes: BTreeMap::new(),
234            bytes_per_pixel,
235            pixel_format,
236        };
237
238        if let Some((frame0, timestamp0)) = frame_timestamp0 {
239            match_all_dynamic_fmts!(
240                frame0,
241                x,
242                result.add_keyframe(b"frame0", &x, timestamp0)?,
243                UFMFError::UnimplementedPixelFormat(pixel_format)
244            );
245        }
246
247        Ok(result)
248    }
249
250    pub fn add_frame(
251        &mut self,
252        origframe: &DynamicFrame,
253        timestamp: DateTime<Utc>,
254        point_data: &[RectFromCenter],
255    ) -> UFMFResult<Vec<RectFromCorner>> {
256        if origframe.pixel_format() != self.pixel_format {
257            return Err(UFMFError::FormatChanged);
258        }
259        let timestamp = strand_datetime_conversion::datetime_to_f64(&timestamp);
260
261        let rects: Vec<RectFromCorner> = point_data
262            .iter()
263            .map(|i| {
264                let (x0, w) = do_size(i.x, i.w, self.xinc as u16, self.max_width);
265                let (y0, h) = do_size(i.y, i.h, self.yinc as u16, self.max_height);
266                RectFromCorner { x0, y0, w, h }
267            })
268            .collect();
269
270        {
271            // This is a scope in which we borrow from `rects`.
272            let regions = rects
273                .iter()
274                .map(|rect| Region { origframe, rect })
275                .collect();
276            self.add_frame_regions(timestamp, regions)?;
277        }
278        Ok(rects)
279    }
280
281    fn add_frame_regions(&mut self, timestamp: f64, regions: Vec<Region>) -> UFMFResult<()> {
282        let mut self_f = match self.f {
283            Some(ref mut f) => f,
284            None => {
285                return Err(UFMFError::AlreadyClosed);
286            }
287        };
288
289        self.index_frame.push(TimestampLoc {
290            timestamp,
291            loc: self.pos as u64,
292        });
293
294        let n_pts = cast::u16(regions.len())?;
295        let bytes_per_pixel = self.bytes_per_pixel;
296
297        let buf0 = vec![FRAME_CHUNK];
298        let buf1 = structure!("<dH").pack(timestamp, n_pts)?;
299
300        self.pos += self_f.write(&buf0)?;
301        self.pos += self_f.write(&buf1)?;
302
303        for region in regions.iter() {
304            let this_str_head = structure!("<HHHH").pack(
305                region.rect.x0,
306                region.rect.y0,
307                region.rect.w,
308                region.rect.h,
309            )?;
310            self.pos += self_f.write(&this_str_head)?;
311            self.pos += match_all_dynamic_fmts!(
312                region.origframe,
313                frame,
314                write_image(&mut self_f, &frame, bytes_per_pixel, region.rect)?,
315                UFMFError::UnimplementedPixelFormat(self.pixel_format)
316            );
317        }
318        Ok(())
319    }
320
321    pub fn add_keyframe<FRAME, FMT>(
322        &mut self,
323        keyframe_type: &[u8],
324        frame: &FRAME,
325        timestamp_dt: DateTime<Utc>,
326    ) -> UFMFResult<()>
327    where
328        FRAME: ImageStride<FMT>,
329        FMT: PixelFormat,
330    {
331        let mut self_f = match self.f {
332            Some(ref mut f) => f,
333            None => {
334                return Err(UFMFError::AlreadyClosed);
335            }
336        };
337
338        let bytes_per_pixel = formats::pixel_format::pixfmt::<FMT>()
339            .unwrap()
340            .bits_per_pixel()
341            / 8;
342
343        let timestamp = strand_datetime_conversion::datetime_to_f64(&timestamp_dt);
344        let dtype = get_dtype(formats::pixel_format::pixfmt::<FMT>().unwrap())?;
345        let width = cast::u16(frame.width())?;
346        let height = cast::u16(frame.height())?;
347
348        {
349            let entry = self
350                .index_keyframes
351                .entry(keyframe_type.to_vec())
352                .or_default();
353            let timestamp = strand_datetime_conversion::datetime_to_f64(&timestamp_dt);
354            entry.push(TimestampLoc {
355                timestamp,
356                loc: self.pos as u64,
357            });
358        }
359
360        let buf = vec![KEYFRAME_CHUNK, cast::u8(keyframe_type.len())?];
361        self.pos += self_f.write(&buf)?;
362        self.pos += self_f.write(keyframe_type)?;
363
364        let buf = structure!("<BHHd").pack(dtype, width, height, timestamp)?;
365        let rect = RectFromCorner {
366            x0: 0,
367            y0: 0,
368            w: width,
369            h: height,
370        };
371        self.pos += self_f.write(&buf)?;
372        self.pos += write_image(&mut self_f, frame, bytes_per_pixel, &rect)?;
373        Ok(())
374    }
375}
376
377impl<F> UFMFWriter<F>
378where
379    F: Write + Seek,
380{
381    /// Close the writer.
382    ///
383    /// Ideally, this is called prior to dropping to prevent the possibility of
384    /// silently ignoring errors.
385    pub fn close(&mut self) -> UFMFResult<F> {
386        let opt_f = self.f.take();
387
388        let mut self_f = match opt_f {
389            Some(f) => f,
390            None => {
391                return Err(UFMFError::AlreadyClosed);
392            }
393        };
394
395        self.pos += self_f.write(&[INDEX_DICT_CHUNK])?;
396        save_indices::save_indices(&mut self_f, &self.index_frame, &self.index_keyframes)?;
397        self_f.seek(SeekFrom::Start(0))?;
398        write_header(
399            &mut self_f,
400            self.pos,
401            self.max_width,
402            self.max_height,
403            self.pixel_format,
404        )?;
405        Ok(self_f)
406    }
407}
408
409/// This will silently ignore any error.
410impl<F: Write + Seek> Drop for UFMFWriter<F> {
411    fn drop(&mut self) {
412        // We silently drop error.
413        match self.close() {
414            Ok(_f) => {}
415            Err(_e) => {} // See https://github.com/rust-lang/rfcs/issues/814
416        }
417    }
418}
419
420#[cfg(test)]
421mod tests {
422    use crate::*;
423    use byteorder::WriteBytesExt;
424    use formats::{
425        owned::OImage,
426        pixel_format::{Mono8, Mono32f},
427    };
428    use strand_dynamic_frame::DynamicFrameOwned;
429
430    #[expect(clippy::float_cmp)]
431    fn arange(start: u8, timestamp: f64) -> (DynamicFrameOwned, DateTime<Utc>) {
432        let w = 10;
433        let h = 10;
434        let mut image_data = Vec::new();
435        for i in 0..100 {
436            image_data.push(start + i as u8);
437        }
438
439        let dt = strand_datetime_conversion::f64_to_datetime(timestamp);
440        let host_timestamp = dt.with_timezone(&chrono::Utc);
441
442        let roundtrip = strand_datetime_conversion::datetime_to_f64(&host_timestamp);
443        assert_eq!(timestamp, roundtrip); // Although this is a float and thus
444        // not guaranteed in general to roundtrip without change, it must pass
445        // through the roundtrip without change in order to hope that the byte-
446        // by-byte comparison in the test will succeed.
447
448        (
449            DynamicFrameOwned::from_static(
450                OImage::<Mono8>::new(w, h, w.try_into().unwrap(), image_data).unwrap(),
451            ),
452            dt,
453        )
454    }
455
456    #[expect(clippy::float_cmp)]
457    fn arange_float(start: f32, timestamp: f64) -> (DynamicFrameOwned, DateTime<Utc>) {
458        let w = 10;
459        let h = 10;
460
461        let mut f = std::io::Cursor::new(Vec::with_capacity(4 * 100));
462        for i in 0..100 {
463            let value: f32 = start + i as f32;
464            f.write_f32::<byteorder::LittleEndian>(value).unwrap();
465        }
466        let image_data = f.into_inner();
467
468        let ts_utc = strand_datetime_conversion::f64_to_datetime(timestamp);
469
470        let roundtrip = strand_datetime_conversion::datetime_to_f64(&ts_utc);
471        assert_eq!(timestamp, roundtrip); // Although this is a float and thus
472        // not guaranteed in general to roundtrip without change, it must pass
473        // through the roundtrip without change in order to hope that the byte-
474        // by-byte comparison in the test will succeed.
475
476        (
477            DynamicFrameOwned::from_static(
478                OImage::<Mono32f>::new(w, h, usize::try_from(w).unwrap() * 4, image_data).unwrap(),
479            ),
480            ts_utc,
481        )
482    }
483
484    #[test]
485    fn test_pack_header() {
486        let buf = pack_header(1, 2, 3, 4, 5).unwrap();
487        assert_eq!(
488            buf,
489            &[
490                b'u', b'f', b'm', b'f', 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 4, 0, 5,
491            ]
492        );
493    }
494
495    #[test]
496    fn test_empty_file() {
497        let w = 320;
498        let h = 240;
499        let pixel_format = formats::pixel_format::PixFmt::Mono8;
500        let f = std::io::Cursor::new(Vec::new());
501        let mut writer = UFMFWriter::new(f, w, h, pixel_format, None).unwrap();
502        let f = writer.close().unwrap();
503
504        // check that we cannot close again
505        match writer.close() {
506            Ok(_) => panic!("expected error"),
507            Err(_e) => {} // TODO: check this is AlreadyClosed error.
508        };
509
510        // check what was writen
511        let buf = f.into_inner();
512
513        // The expected values are from this Python program:
514
515        /*
516        import ufmf
517
518        fname = 'empty.ufmf'
519        saver = ufmf.UfmfSaverV3(fname,max_width=320,max_height=240,coding='MONO8')
520        saver.close()
521
522        buf = open(fname,mode='r').read()
523        print(', '.join(['0x%x'%ord(c) for c in buf]))
524        */
525        let expected: &[u8] = &[
526            0x75, 0x66, 0x6d, 0x66, 0x3, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
527            0x40, 0x1, 0xf0, 0x0, 0x5, 0x4d, 0x4f, 0x4e, 0x4f, 0x38, 0x2, 0x64, 0x2, 0x5, 0x0,
528            0x66, 0x72, 0x61, 0x6d, 0x65, 0x64, 0x0, 0x8, 0x0, 0x6b, 0x65, 0x79, 0x66, 0x72, 0x61,
529            0x6d, 0x65, 0x64, 0x0,
530        ];
531        assert_eq!(&buf[0..], expected);
532    }
533
534    #[test]
535    fn test_saving_regions() {
536        let (arr, dt) = arange(0, 123.456);
537        let frame0_owned = arr.borrow();
538        let frame0 = Some((&frame0_owned, dt));
539        let w = 10;
540        let h = 10;
541        let pixel_format = formats::pixel_format::PixFmt::Mono8;
542        let f = std::io::Cursor::new(Vec::new());
543        let mut writer = UFMFWriter::new(f, w, h, pixel_format, frame0).unwrap();
544
545        let (arr2, dt2) = arange(100, 42.42);
546        let point_data = vec![
547            RectFromCenter::from_xy_wh(0, 0, 4, 4),
548            RectFromCenter::from_xy_wh(4, 4, 4, 4),
549            RectFromCenter::from_xy_wh(9, 9, 4, 4),
550        ];
551
552        writer.add_frame(&arr2.borrow(), dt2, &point_data).unwrap();
553
554        let f = writer.close().unwrap();
555
556        // check that we cannot close again
557        match writer.close() {
558            Ok(_) => panic!("expected error"),
559            Err(_e) => {} // TODO: check this is AlreadyClosed error.
560        };
561
562        // check what was writen
563        let buf = f.into_inner();
564
565        // The expected values are from this Python program:
566
567        /*
568
569        from __future__ import print_function
570        import ufmf
571        import numpy as np
572
573        frame0 = np.arange(100, dtype=np.uint8)
574        frame0.shape = (10,10)
575
576        print(frame0)
577
578        timestamp0 = 123.456
579
580        fname = 'small.ufmf'
581        saver = ufmf.UfmfSaverV3(fname,max_width=10,max_height=10,coding='MONO8',
582            frame0=frame0,timestamp0=timestamp0)
583
584        print('---')
585        frame1 = np.arange(100, dtype=np.uint8) + 100
586        frame1.shape = frame0.shape
587        print(frame1)
588
589        point_data = [
590            # xidx, yidx, w, h
591            (0, 0, 4, 4),
592            (4, 4, 4, 4),
593            (9, 9, 4, 4),
594        ]
595
596        saver.add_frame(frame1,timestamp=42.42,point_data=point_data)
597
598        saver.close()
599
600        print('---')
601
602        buf = open(fname,mode='r').read()
603        print(', '.join(['0x%x'%ord(c) for c in buf]))
604
605        */
606
607        let expected: &[u8] = &[
608            0x75, 0x66, 0x6d, 0x66, 0x3, 0x0, 0x0, 0x0, 0xe7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
609            0xa, 0x0, 0xa, 0x0, 0x5, 0x4d, 0x4f, 0x4e, 0x4f, 0x38, 0x0, 0x6, 0x66, 0x72, 0x61,
610            0x6d, 0x65, 0x30, 0x42, 0xa, 0x0, 0xa, 0x0, 0x77, 0xbe, 0x9f, 0x1a, 0x2f, 0xdd, 0x5e,
611            0x40, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
612            0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
613            0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
614            0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
615            0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
616            0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
617            0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63,
618            0x1, 0xf6, 0x28, 0x5c, 0x8f, 0xc2, 0x35, 0x45, 0x40, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4,
619            0x0, 0x4, 0x0, 0x64, 0x65, 0x66, 0x67, 0x6e, 0x6f, 0x70, 0x71, 0x78, 0x79, 0x7a, 0x7b,
620            0x82, 0x83, 0x84, 0x85, 0x2, 0x0, 0x2, 0x0, 0x4, 0x0, 0x4, 0x0, 0x7a, 0x7b, 0x7c, 0x7d,
621            0x84, 0x85, 0x86, 0x87, 0x8e, 0x8f, 0x90, 0x91, 0x98, 0x99, 0x9a, 0x9b, 0x6, 0x0, 0x6,
622            0x0, 0x4, 0x0, 0x4, 0x0, 0xa6, 0xa7, 0xa8, 0xa9, 0xb0, 0xb1, 0xb2, 0xb3, 0xba, 0xbb,
623            0xbc, 0xbd, 0xc4, 0xc5, 0xc6, 0xc7, 0x2, 0x64, 0x2, 0x5, 0x0, 0x66, 0x72, 0x61, 0x6d,
624            0x65, 0x64, 0x2, 0x3, 0x0, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x8, 0x0, 0x0, 0x0, 0x93, 0x0,
625            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0x0, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
626            0x70, 0x61, 0x64, 0x8, 0x0, 0x0, 0x0, 0xf6, 0x28, 0x5c, 0x8f, 0xc2, 0x35, 0x45, 0x40,
627            0x8, 0x0, 0x6b, 0x65, 0x79, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x64, 0x1, 0x6, 0x0, 0x66,
628            0x72, 0x61, 0x6d, 0x65, 0x30, 0x64, 0x2, 0x3, 0x0, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x8,
629            0x0, 0x0, 0x0, 0x1a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0x0, 0x74, 0x69, 0x6d,
630            0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x61, 0x64, 0x8, 0x0, 0x0, 0x0, 0x77, 0xbe, 0x9f,
631            0x1a, 0x2f, 0xdd, 0x5e, 0x40,
632        ];
633        assert_eq!(&buf[0..], expected);
634    }
635
636    #[test]
637    fn test_float_keyframe() {
638        use formats::pixel_format::Mono32f;
639        let w = 10;
640        let h = 10;
641        let pixel_format = formats::pixel_format::PixFmt::Mono8;
642        let f = std::io::Cursor::new(Vec::new());
643        let mut writer = UFMFWriter::new(f, w, h, pixel_format, None).unwrap();
644        let (running_mean, ts) = arange_float(0.1, 123.456);
645
646        let running_mean_owned = running_mean.borrow();
647        let running_mean = running_mean_owned.as_static::<Mono32f>().unwrap();
648
649        writer.add_keyframe(b"mean", &running_mean, ts).unwrap();
650        let f = writer.close().unwrap();
651
652        // check that we cannot close again
653        match writer.close() {
654            Ok(_) => panic!("expected error"),
655            Err(_e) => {} // TODO: check this is AlreadyClosed error.
656        };
657
658        // check what was writen
659        let buf = f.into_inner();
660
661        // The expected values are from this Python program:
662
663        /*
664        from __future__ import print_function
665        import ufmf
666        import numpy as np
667
668        running_mean_im = np.arange(100, dtype=np.float32) + 0.1
669        running_mean_im.shape = (10,10)
670
671        print(running_mean_im)
672
673        fname = 'with_mean.ufmf'
674        saver = ufmf.UfmfSaverV3(fname,max_width=10,max_height=10,coding='MONO8')
675        saver.add_keyframe('mean',running_mean_im,timestamp=123.456)
676        saver.close()
677
678        print('---')
679
680        buf = open(fname,mode='r').read()
681        print(', '.join(['0x%x'%ord(c) for c in buf]))
682
683        */
684
685        let expected: &[u8] = &[
686            0x75, 0x66, 0x6d, 0x66, 0x3, 0x0, 0x0, 0x0, 0xbe, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
687            0xa, 0x0, 0xa, 0x0, 0x5, 0x4d, 0x4f, 0x4e, 0x4f, 0x38, 0x0, 0x4, 0x6d, 0x65, 0x61,
688            0x6e, 0x66, 0xa, 0x0, 0xa, 0x0, 0x77, 0xbe, 0x9f, 0x1a, 0x2f, 0xdd, 0x5e, 0x40, 0xcd,
689            0xcc, 0xcc, 0x3d, 0xcd, 0xcc, 0x8c, 0x3f, 0x66, 0x66, 0x6, 0x40, 0x66, 0x66, 0x46,
690            0x40, 0x33, 0x33, 0x83, 0x40, 0x33, 0x33, 0xa3, 0x40, 0x33, 0x33, 0xc3, 0x40, 0x33,
691            0x33, 0xe3, 0x40, 0x9a, 0x99, 0x1, 0x41, 0x9a, 0x99, 0x11, 0x41, 0x9a, 0x99, 0x21,
692            0x41, 0x9a, 0x99, 0x31, 0x41, 0x9a, 0x99, 0x41, 0x41, 0x9a, 0x99, 0x51, 0x41, 0x9a,
693            0x99, 0x61, 0x41, 0x9a, 0x99, 0x71, 0x41, 0xcd, 0xcc, 0x80, 0x41, 0xcd, 0xcc, 0x88,
694            0x41, 0xcd, 0xcc, 0x90, 0x41, 0xcd, 0xcc, 0x98, 0x41, 0xcd, 0xcc, 0xa0, 0x41, 0xcd,
695            0xcc, 0xa8, 0x41, 0xcd, 0xcc, 0xb0, 0x41, 0xcd, 0xcc, 0xb8, 0x41, 0xcd, 0xcc, 0xc0,
696            0x41, 0xcd, 0xcc, 0xc8, 0x41, 0xcd, 0xcc, 0xd0, 0x41, 0xcd, 0xcc, 0xd8, 0x41, 0xcd,
697            0xcc, 0xe0, 0x41, 0xcd, 0xcc, 0xe8, 0x41, 0xcd, 0xcc, 0xf0, 0x41, 0xcd, 0xcc, 0xf8,
698            0x41, 0x66, 0x66, 0x0, 0x42, 0x66, 0x66, 0x4, 0x42, 0x66, 0x66, 0x8, 0x42, 0x66, 0x66,
699            0xc, 0x42, 0x66, 0x66, 0x10, 0x42, 0x66, 0x66, 0x14, 0x42, 0x66, 0x66, 0x18, 0x42,
700            0x66, 0x66, 0x1c, 0x42, 0x66, 0x66, 0x20, 0x42, 0x66, 0x66, 0x24, 0x42, 0x66, 0x66,
701            0x28, 0x42, 0x66, 0x66, 0x2c, 0x42, 0x66, 0x66, 0x30, 0x42, 0x66, 0x66, 0x34, 0x42,
702            0x66, 0x66, 0x38, 0x42, 0x66, 0x66, 0x3c, 0x42, 0x66, 0x66, 0x40, 0x42, 0x66, 0x66,
703            0x44, 0x42, 0x66, 0x66, 0x48, 0x42, 0x66, 0x66, 0x4c, 0x42, 0x66, 0x66, 0x50, 0x42,
704            0x66, 0x66, 0x54, 0x42, 0x66, 0x66, 0x58, 0x42, 0x66, 0x66, 0x5c, 0x42, 0x66, 0x66,
705            0x60, 0x42, 0x66, 0x66, 0x64, 0x42, 0x66, 0x66, 0x68, 0x42, 0x66, 0x66, 0x6c, 0x42,
706            0x66, 0x66, 0x70, 0x42, 0x66, 0x66, 0x74, 0x42, 0x66, 0x66, 0x78, 0x42, 0x66, 0x66,
707            0x7c, 0x42, 0x33, 0x33, 0x80, 0x42, 0x33, 0x33, 0x82, 0x42, 0x33, 0x33, 0x84, 0x42,
708            0x33, 0x33, 0x86, 0x42, 0x33, 0x33, 0x88, 0x42, 0x33, 0x33, 0x8a, 0x42, 0x33, 0x33,
709            0x8c, 0x42, 0x33, 0x33, 0x8e, 0x42, 0x33, 0x33, 0x90, 0x42, 0x33, 0x33, 0x92, 0x42,
710            0x33, 0x33, 0x94, 0x42, 0x33, 0x33, 0x96, 0x42, 0x33, 0x33, 0x98, 0x42, 0x33, 0x33,
711            0x9a, 0x42, 0x33, 0x33, 0x9c, 0x42, 0x33, 0x33, 0x9e, 0x42, 0x33, 0x33, 0xa0, 0x42,
712            0x33, 0x33, 0xa2, 0x42, 0x33, 0x33, 0xa4, 0x42, 0x33, 0x33, 0xa6, 0x42, 0x33, 0x33,
713            0xa8, 0x42, 0x33, 0x33, 0xaa, 0x42, 0x33, 0x33, 0xac, 0x42, 0x33, 0x33, 0xae, 0x42,
714            0x33, 0x33, 0xb0, 0x42, 0x33, 0x33, 0xb2, 0x42, 0x33, 0x33, 0xb4, 0x42, 0x33, 0x33,
715            0xb6, 0x42, 0x33, 0x33, 0xb8, 0x42, 0x33, 0x33, 0xba, 0x42, 0x33, 0x33, 0xbc, 0x42,
716            0x33, 0x33, 0xbe, 0x42, 0x33, 0x33, 0xc0, 0x42, 0x33, 0x33, 0xc2, 0x42, 0x33, 0x33,
717            0xc4, 0x42, 0x33, 0x33, 0xc6, 0x42, 0x2, 0x64, 0x2, 0x5, 0x0, 0x66, 0x72, 0x61, 0x6d,
718            0x65, 0x64, 0x0, 0x8, 0x0, 0x6b, 0x65, 0x79, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x64, 0x1,
719            0x4, 0x0, 0x6d, 0x65, 0x61, 0x6e, 0x64, 0x2, 0x3, 0x0, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
720            0x8, 0x0, 0x0, 0x0, 0x1a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0x0, 0x74, 0x69,
721            0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x61, 0x64, 0x8, 0x0, 0x0, 0x0, 0x77, 0xbe,
722            0x9f, 0x1a, 0x2f, 0xdd, 0x5e, 0x40,
723        ];
724        assert_eq!(&buf[0..], expected);
725    }
726}