braid_mvg/
pymvg_support.rs

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