braid_config_data/
lib.rs

1// Copyright (C) The Strand-Braid Authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! type definitions for Braid configuration
5use serde::{Deserialize, Serialize};
6
7use braid_types::{BraidCameraConfig, FakeSyncConfig, TriggerType, TriggerboxConfig};
8
9/// The Braid configuration error type.
10#[derive(thiserror::Error, Debug)]
11pub enum Error {
12    #[error("lookup error on variable: {source}")]
13    ShellExpandLookupVarError {
14        #[from]
15        source: shellexpand::LookupError<std::env::VarError>,
16    },
17    #[error("IO error: {source}")]
18    IoError {
19        #[from]
20        source: std::io::Error,
21    },
22    #[error("TOML deserialization error: {source}")]
23    TomlDeError {
24        #[from]
25        source: toml::de::Error,
26    },
27}
28
29type Result<T> = std::result::Result<T, Error>;
30
31/// The default value for [MainbrainConfig::http_api_server_addr].
32pub const DEFAULT_HTTP_API_SERVER_ADDR: &str = "127.0.0.1:0";
33
34fn default_http_api_server_addr() -> String {
35    DEFAULT_HTTP_API_SERVER_ADDR.to_string()
36}
37
38/// The default value for [MainbrainConfig::output_base_dirname].
39pub const DEFAULT_OUTPUT_BASE_DIRNAME: &str = "~/BRAID-DATA";
40
41fn default_output_base_dirname() -> std::path::PathBuf {
42    DEFAULT_OUTPUT_BASE_DIRNAME.into()
43}
44
45fn default_model_server_addr() -> std::net::SocketAddr {
46    braid_types::DEFAULT_MODEL_SERVER_ADDR.parse().unwrap()
47}
48
49fn default_true() -> bool {
50    true
51}
52
53/// Split `path` (which must be a file) into directory and filename component.
54fn split_path<P: AsRef<std::path::Path>>(path: P) -> (std::path::PathBuf, std::path::PathBuf) {
55    let path = path.as_ref();
56    assert!(path.is_file());
57    let mut components = path.components();
58    let filename = components.next_back().unwrap().as_os_str().into();
59    let dirname = components.as_path().into();
60    (dirname, filename)
61}
62
63/// If `path` is relative, make it relative to `dirname`.
64///
65/// `path` must be utf-8 encoded and can start with a tilde, which is expanded
66/// to the home directory.
67fn fixup_relative_path(path: &mut std::path::PathBuf, dirname: &std::path::Path) -> Result<()> {
68    let pathstr = path.as_os_str().to_str().unwrap();
69    let expanded = shellexpand::full(&pathstr)?;
70    *path = std::path::PathBuf::from(expanded.to_string());
71
72    if path.is_relative() {
73        *path = dirname.join(&path);
74    }
75    Ok(())
76}
77
78/// The sub-configuration of [BraidConfig] for `mainbrain` - the central
79/// component of Braid that integrates information from multiple cameras and
80/// performs tracking.
81#[derive(Debug, Clone, Serialize, Deserialize)]
82#[serde(deny_unknown_fields)]
83pub struct MainbrainConfig {
84    /// Filename of the camera calibration, optional.
85    ///
86    /// Can contain shell variables such as `~`, `$A`, or `${B}`.
87    ///
88    /// If the filename ends with .pymvg or .json, it will be treated as a pymvg
89    /// calibration file. Else it will be treated considered in the flydra XML
90    /// calibration format.
91    pub cal_fname: Option<std::path::PathBuf>,
92    /// Directory where data should be saved. Can contain shell variables.
93    /// Defaults to [DEFAULT_OUTPUT_BASE_DIRNAME].
94    #[serde(default = "default_output_base_dirname")]
95    pub output_base_dirname: std::path::PathBuf,
96    /// Parameters for Kalman filter and data association
97    #[serde(default = "braid_types::default_tracking_params_full_3d")]
98    pub tracking_params: braid_types::TrackingParams,
99    // Raising the mainbrain thread priority is currently disabled.
100    // /// Parameters to potentially raise the mainbrain thread priority.
101    // sched_policy_priority: Option<(i32, i32)>,
102    /// Address of UDP port to send low-latency detection data
103    pub lowlatency_camdata_udp_addr: Option<String>,
104    #[serde(default)]
105    pub lowlatency_camdata_udp_port: u16,
106    /// Address of HTTP port for control API. This is specified in the format
107    /// `IP:PORT` where:
108    ///
109    /// `IP` can be:
110    ///  - a numerical IPv4 address:
111    ///    - e.g. `1.1.1.1` uses the specific IP
112    ///    - `127.0.0.1` for the loopback interface
113    ///    - `0.0.0.0` to open the server on all available IPv4 interfaces
114    ///  - a numerical IPv6 address:
115    ///    - e.g. `[2001:db8:3333:4444:5555:6666:7777:8888]` uses the specific
116    ///      IP
117    ///    - `[::1]` for the loopback interface
118    ///    - `[::]` to open the server on all available IPv6 interfaces
119    ///  - a hostname which resolves to an IP address (depending on your DNS
120    ///    configuration, resolves to either IPv4 or IPv6):
121    ///    - `localhost` resolves to the IP address of the loopback interface
122    ///    - e.g. `hostname` for a specific IP address
123    ///
124    /// `PORT` can be:
125    ///  - `0` allows the operating system to choose an unassigned port
126    ///    dynamically
127    ///  - e.g. `1234` uses the specific port
128    ///
129    /// Set to `0.0.0.0:0` to be automatically assigned a public IP address with
130    /// a dynamically assigned port.
131    ///
132    /// The default value is set to [DEFAULT_HTTP_API_SERVER_ADDR].
133    #[serde(default = "default_http_api_server_addr")]
134    pub http_api_server_addr: String,
135    /// Address of HTTP port for model server emitting realtime tracking results
136    #[serde(default = "default_model_server_addr")]
137    pub model_server_addr: std::net::SocketAddr,
138    /// Save rows to data2d_distorted where nothing detected (saves timestamps)
139    #[serde(default = "default_true")]
140    pub save_empty_data2d: bool,
141    /// Secret to use for signing HTTP cookies (base64 encoded)
142    pub secret_base64: Option<String>,
143    /// Threshold duration before logging error (msec).
144    ///
145    /// If the image acquisition timestamp precedes the computed trigger
146    /// timestamp, clearly an error has happened. This error must lie in the
147    /// computation of the trigger timestamp. This specifies the threshold error
148    /// at which an error is logged. (The underlying source of such errors
149    /// remains unknown.)
150    pub acquisition_duration_allowed_imprecision_msec: Option<f64>,
151    /// The size of the buffer, in number of messages, used by the channel for
152    /// sending data to disk.
153    #[serde(default = "default_write_buffer_size_num_messages")]
154    pub write_buffer_size_num_messages: usize,
155}
156
157impl std::default::Default for MainbrainConfig {
158    fn default() -> Self {
159        Self {
160            cal_fname: None,
161            output_base_dirname: default_output_base_dirname(),
162            tracking_params: braid_types::default_tracking_params_full_3d(),
163            // Raising the mainbrain thread priority is currently disabled.
164            // sched_policy_priority: None,
165            lowlatency_camdata_udp_addr: None,
166            lowlatency_camdata_udp_port: Default::default(),
167            http_api_server_addr: default_http_api_server_addr(),
168            model_server_addr: default_model_server_addr(),
169            save_empty_data2d: true,
170            secret_base64: None,
171            acquisition_duration_allowed_imprecision_msec:
172                braid_types::DEFAULT_ACQUISITION_DURATION_ALLOWED_IMPRECISION_MSEC,
173            write_buffer_size_num_messages: default_write_buffer_size_num_messages(),
174        }
175    }
176}
177
178pub const fn default_write_buffer_size_num_messages() -> usize {
179    10000
180}
181
182/// The Braid configuration format used in [the Braid configuration `TOML`
183/// file](https://strawlab.github.io/strand-braid/braid_configuration_and_launching.html).
184///
185/// This is the new configuration format. Backwards compatibility is maintained
186/// by first attempting to deserialize with this definition.
187///
188/// See the types of each field for sub-configuration values.
189#[derive(Debug, Clone, Serialize, Deserialize)]
190#[serde(deny_unknown_fields)]
191pub struct BraidConfig {
192    /// Mainbrain configuration
193    #[serde(default = "MainbrainConfig::default")]
194    pub mainbrain: MainbrainConfig,
195    /// Triggerbox configuration.
196    #[serde(default)]
197    pub trigger: TriggerType,
198    pub cameras: Vec<BraidCameraConfig>,
199}
200
201impl From<BraidConfig1> for BraidConfig {
202    fn from(orig: BraidConfig1) -> BraidConfig {
203        let trigger = match orig.trigger {
204            None => TriggerType::FakeSync(FakeSyncConfig::default()),
205            Some(x) => TriggerType::TriggerboxV1(x),
206        };
207        BraidConfig {
208            mainbrain: orig.mainbrain,
209            trigger,
210            cameras: orig.cameras,
211        }
212    }
213}
214
215/// The old configuration format.
216#[derive(Debug, Clone, Serialize, Deserialize)]
217#[serde(deny_unknown_fields)]
218#[doc(hidden)]
219pub struct BraidConfig1 {
220    pub mainbrain: MainbrainConfig,
221    pub trigger: Option<TriggerboxConfig>,
222    pub cameras: Vec<BraidCameraConfig>,
223}
224
225impl BraidConfig {
226    /// For all paths which are relative, make them relative to the
227    /// config file location.
228    fn fixup_relative_paths(&mut self, orig_path: &std::path::Path) -> Result<()> {
229        let (dirname, _orig_path) = split_path(orig_path);
230
231        // fixup self.mainbrain.cal_fname
232        if let Some(cal_fname) = self.mainbrain.cal_fname.as_mut() {
233            fixup_relative_path(cal_fname, &dirname)?;
234        }
235
236        // fixup self.mainbrain.output_base_dirname
237        fixup_relative_path(&mut self.mainbrain.output_base_dirname, &dirname)?;
238
239        // fixup self.cameras.camera_settings_filename
240        for camera_config in self.cameras.iter_mut() {
241            if let Some(ref mut camera_settings_filename) =
242                camera_config.camera_settings_filename.as_mut()
243            {
244                fixup_relative_path(camera_settings_filename, &dirname)?;
245            }
246        }
247
248        Ok(())
249    }
250}
251
252impl std::default::Default for BraidConfig {
253    fn default() -> Self {
254        Self {
255            mainbrain: MainbrainConfig::default(),
256            // This `trigger` field has a different default than
257            // TriggerType::default() in order to show the user (who will query
258            // this with the `braid default-config` command) how to configure
259            // the trigger box.
260            trigger: TriggerType::TriggerboxV1(TriggerboxConfig::default()),
261            cameras: vec![
262                BraidCameraConfig::default_absdiff_config("fake-camera-1".to_string()),
263                BraidCameraConfig::default_absdiff_config("fake-camera-2".to_string()),
264                BraidCameraConfig::default_absdiff_config("fake-camera-3".to_string()),
265            ],
266        }
267    }
268}
269
270/// Parse a `.toml` file and return a [BraidConfig] structure.
271pub fn parse_config_file<P: AsRef<std::path::Path>>(fname: P) -> Result<BraidConfig> {
272    use std::io::Read;
273
274    let mut file = std::fs::File::open(fname.as_ref())?;
275    let mut contents = String::new();
276    file.read_to_string(&mut contents)?;
277    let mut cfg: BraidConfig = match toml::from_str(&contents) {
278        Ok(cfg) => cfg,
279        Err(err_cfg2) => {
280            let cfg1: BraidConfig1 = match toml::from_str(&contents) {
281                Ok(cfg1) => cfg1,
282                Err(err_cfg1) => {
283                    tracing::error!(
284                        "parsing config file first as BraidConfig failed \
285                    and then again as BraidConfig1 failed. The parse error for \
286                    BraidConfig1 is: {}\n The original error when parsing \
287                    BraidConfig will now be raised.",
288                        err_cfg1
289                    );
290                    return Err(err_cfg2.into());
291                }
292            };
293            BraidConfig::from(cfg1)
294        }
295    };
296    // let mut cfg: BraidConfig = toml::from_str(&contents)?;
297    cfg.fixup_relative_paths(fname.as_ref())?;
298    Ok(cfg)
299}