Skip to main content

braid_mvg/
pymvg_support.rs

1// Copyright (C) The Strand-Braid Authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! PyMVG format support for camera systems.
5//!
6//! This module provides data structures and serialization support for the PyMVG
7//! (Python Multi-View Geometry) JSON format. PyMVG is a Python library for
8//! multiple view geometry that uses a specific JSON schema for storing camera
9//! calibration data.
10//!
11//! The module includes:
12//! - [`PymvgCamera`]: Individual camera representation in PyMVG format
13//! - [`PymvgMultiCameraSystemV1`]: Multi-camera system in PyMVG format
14
15#![allow(non_snake_case)]
16
17use serde::{Deserialize, Deserializer, Serialize, Serializer};
18
19use nalgebra::DefaultAllocator;
20use nalgebra::RealField;
21use nalgebra::allocator::Allocator;
22use nalgebra::core::dimension::{U3, U4};
23use nalgebra::core::{Matrix3, OMatrix, Vector5};
24use nalgebra::dimension::DimName;
25use nalgebra::geometry::Point3;
26
27/// Multi-camera system in PyMVG JSON format.
28///
29/// This struct represents a complete camera system as stored in PyMVG files,
30/// including version information and a collection of individual cameras.
31#[derive(Debug, Serialize, Deserialize)]
32#[serde(deny_unknown_fields)]
33pub struct PymvgMultiCameraSystemV1<R: RealField> {
34    pub(crate) __pymvg_file_version__: String,
35    pub(crate) camera_system: Vec<PymvgCamera<R>>,
36}
37
38/// Individual camera representation in PyMVG JSON format.
39///
40/// This struct contains all camera parameters including intrinsics (K, D),
41/// extrinsics (Q, translation), projection matrix (P), rectification matrix (R),
42/// and image dimensions as stored in PyMVG camera calibration files.
43#[derive(Debug, Serialize, Deserialize)]
44#[serde(deny_unknown_fields)]
45pub struct PymvgCamera<R: RealField> {
46    pub(crate) name: String,
47    pub(crate) width: usize,
48    pub(crate) height: usize,
49    #[serde(with = "array_of_arrays")]
50    pub(crate) P: OMatrix<R, U3, U4>,
51    #[serde(with = "array_of_arrays")]
52    pub(crate) K: Matrix3<R>,
53    pub(crate) D: Vector5<R>,
54    #[serde(with = "array_of_arrays")]
55    pub(crate) R: Matrix3<R>,
56    #[serde(with = "array_of_arrays")]
57    pub(crate) Q: Matrix3<R>,
58    pub(crate) translation: Point3<R>,
59}
60
61mod array_of_arrays {
62    use super::*;
63
64    /// Serialize an nalgebra::OMatrix to an array of arrays of floats
65    ///
66    /// The nalgebra serialization does not work exactly like this, so here we
67    /// roll our own.
68    pub fn serialize<S, R, ROWS, COLS>(
69        arr: &OMatrix<R, ROWS, COLS>,
70        serializer: S,
71    ) -> Result<S::Ok, S::Error>
72    where
73        R: RealField,
74        S: Serializer,
75        DefaultAllocator: Allocator<ROWS, COLS>,
76        ROWS: DimName,
77        COLS: DimName,
78    {
79        use serde::ser::SerializeSeq;
80
81        let nrows = arr.nrows();
82        let mut outer_seq = serializer.serialize_seq(Some(nrows))?;
83        for row in arr.row_iter() {
84            let inner_seq: Vec<f64> = row
85                .iter()
86                .map(|el| nalgebra::try_convert(el.clone()).unwrap())
87                .collect();
88            outer_seq.serialize_element(&inner_seq)?;
89        }
90        outer_seq.end()
91    }
92
93    /// Deserialize an array of arrays of floats to nalgebra::OMatrix
94    ///
95    /// The nalgebra deserialization does not work exactly like this, so here we
96    /// roll our own.
97    pub fn deserialize<'de, D, R: RealField, ROWS, COLS>(
98        deserializer: D,
99    ) -> Result<OMatrix<R, ROWS, COLS>, D::Error>
100    where
101        D: Deserializer<'de>,
102        DefaultAllocator: Allocator<ROWS, COLS>,
103        ROWS: DimName,
104        COLS: DimName,
105    {
106        // deserialize to JSON value and then extract the array.
107        let v = serde_json::Value::deserialize(deserializer)?;
108        let rows = v
109            .as_array()
110            .ok_or_else(|| serde::de::Error::custom("expected array"))?;
111
112        if rows.len() != ROWS::DIM {
113            return Err(serde::de::Error::custom(format!(
114                "expected {} rows, found {}",
115                ROWS::DIM,
116                rows.len()
117            )));
118        }
119
120        let mut values = Vec::<R>::with_capacity(3 * COLS::dim());
121        for (i, row_value) in rows.iter().enumerate() {
122            let row = row_value
123                .as_array()
124                .ok_or_else(|| serde::de::Error::custom("expected array"))?;
125
126            if row.len() != COLS::dim() {
127                return Err(serde::de::Error::custom(format!(
128                    "in row {}, expected {} cols found {}",
129                    i,
130                    COLS::dim(),
131                    row.len()
132                )));
133            }
134
135            for el_value in row {
136                let el = el_value
137                    .as_f64()
138                    .ok_or_else(|| serde::de::Error::custom("expected float"))?;
139                values.push(nalgebra::convert(el));
140            }
141        }
142
143        Ok(nalgebra::OMatrix::<R, ROWS, COLS>::from_row_slice(&values))
144    }
145}
146
147#[test]
148fn matrix3x4_roundtrip() {
149    #[derive(Debug, Serialize, Deserialize)]
150    pub struct Outer<R: RealField> {
151        #[serde(with = "array_of_arrays")]
152        pub(crate) inner: OMatrix<R, U3, U4>,
153    }
154
155    let orig: Outer<f64> = Outer {
156        inner: OMatrix::<f64, U3, U4>::from_row_slice(&[
157            1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0,
158        ]),
159    };
160
161    let buf = serde_json::to_vec(&orig).unwrap();
162    println!("buf: {}", std::str::from_utf8(&buf).unwrap());
163    let loaded: Outer<f64> = serde_json::from_slice(&buf).unwrap();
164
165    approx::assert_abs_diff_eq!(orig.inner, loaded.inner, epsilon = 1e-32);
166}
167
168#[cfg(test)]
169use nalgebra::U1;
170
171#[test]
172fn matrix4x1_roundtrip() {
173    #[derive(Debug, Serialize, Deserialize)]
174    pub struct Outer<R: RealField> {
175        #[serde(with = "array_of_arrays")]
176        pub(crate) inner: OMatrix<R, U4, U1>,
177    }
178
179    let orig: Outer<f64> = Outer {
180        inner: OMatrix::<f64, U4, U1>::from_row_slice(&[1.0, 2.0, 3.0, 4.0]),
181    };
182
183    let buf = serde_json::to_vec(&orig).unwrap();
184    println!("buf: {}", std::str::from_utf8(&buf).unwrap());
185    let loaded: Outer<f64> = serde_json::from_slice(&buf).unwrap();
186
187    approx::assert_abs_diff_eq!(orig.inner, loaded.inner, epsilon = 1e-32);
188}