#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate static_assertions;
use ordered_float::NotNan;
use rust_cam_bui_types::{ClockModel, RecordingPath};
use std::net::SocketAddr;
use serde::{Deserialize, Deserializer, Serialize};
use bui_backend_session_types::AccessToken;
use withkey::WithKey;
pub const DEFAULT_MODEL_SERVER_ADDR: &str = "0.0.0.0:8397";
pub const BRAID_SCHEMA: u16 = 3; pub const KALMAN_ESTIMATES_CSV_FNAME: &str = "kalman_estimates.csv";
pub const DATA_ASSOCIATE_CSV_FNAME: &str = "data_association.csv";
pub const DATA2D_DISTORTED_CSV_FNAME: &str = "data2d_distorted.csv";
pub const CAM_INFO_CSV_FNAME: &str = "cam_info.csv";
pub const TRIGGER_CLOCK_INFO_CSV_FNAME: &str = "trigger_clock_info.csv";
pub const EXPERIMENT_INFO_CSV_FNAME: &str = "experiment_info.csv";
pub const TEXTLOG_CSV_FNAME: &str = "textlog.csv";
pub const CALIBRATION_XML_FNAME: &str = "calibration.xml";
pub const BRAID_METADATA_YML_FNAME: &str = "braid_metadata.yml";
pub const README_MD_FNAME: &str = "README.md";
pub const IMAGES_DIRNAME: &str = "images";
pub const CAM_SETTINGS_DIRNAME: &str = "cam_settings";
pub const FEATURE_DETECT_SETTINGS_DIRNAME: &str = "feature_detect_settings";
pub const RECONSTRUCT_LATENCY_HLOG_FNAME: &str = "reconstruct_latency_usec.hlog";
pub const REPROJECTION_DIST_HLOG_FNAME: &str = "reprojection_distance_100x_pixels.hlog";
pub const TRIGGERBOX_SYNC_SECONDS: u64 = 3;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CamInfoRow {
pub camn: CamNum,
pub cam_id: String,
}
#[allow(non_snake_case)]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct KalmanEstimatesRow {
pub obj_id: u32,
pub frame: SyncFno,
#[serde(with = "crate::timestamp_opt_f64")]
pub timestamp: Option<FlydraFloatTimestampLocal<Triggerbox>>,
pub x: f64,
pub y: f64,
pub z: f64,
pub xvel: f64,
pub yvel: f64,
pub zvel: f64,
pub P00: f64,
pub P01: f64,
pub P02: f64,
pub P11: f64,
pub P12: f64,
pub P22: f64,
pub P33: f64,
pub P44: f64,
pub P55: f64,
}
impl WithKey<SyncFno> for KalmanEstimatesRow {
fn key(&self) -> SyncFno {
self.frame
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DataAssocRow {
pub obj_id: u32,
pub frame: SyncFno,
pub cam_num: CamNum,
pub pt_idx: u8,
}
impl WithKey<SyncFno> for DataAssocRow {
fn key(&self) -> SyncFno {
self.frame
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct FlydraRawUdpPoint {
pub x0_abs: f64,
pub y0_abs: f64,
pub area: f64,
pub maybe_slope_eccentricty: Option<(f64, f64)>,
pub cur_val: u8,
pub mean_val: f64,
pub sumsqf_val: f64,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Eq, PartialOrd, Ord)]
pub struct RawCamName(String);
impl RawCamName {
pub fn new(s: String) -> Self {
RawCamName(s)
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for RawCamName {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
pub mod braid_http {
pub const REMOTE_CAMERA_INFO_PATH: &str = "remote-camera-info";
pub const CAM_PROXY_PATH: &str = "cam-proxy";
pub fn encode_cam_name(cam_name: &crate::RawCamName) -> String {
percent_encoding::utf8_percent_encode(&cam_name.0, percent_encoding::NON_ALPHANUMERIC)
.to_string()
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
pub enum StartSoftwareFrameRateLimit {
Enable(f64),
Disabled,
#[default]
NoChange,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct RemoteCameraInfoResponse {
pub camdata_udp_port: u16,
pub config: BraidCameraConfig,
pub force_camera_sync_mode: bool,
pub software_limit_framerate: StartSoftwareFrameRateLimit,
pub trig_config: TriggerType,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct PtpStamp(u64);
impl PtpStamp {
pub fn new(val: u64) -> Self {
PtpStamp(val)
}
pub fn get(&self) -> u64 {
self.0
}
pub fn duration_since(&self, other: &Self) -> Option<PtpStampDuration> {
if self.0 >= other.0 {
Some(PtpStampDuration(self.0 - other.0))
} else {
None
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct PtpStampDuration(u64);
impl PtpStampDuration {
pub fn nanos(&self) -> u64 {
self.0
}
}
impl<TZ> TryFrom<chrono::DateTime<TZ>> for PtpStamp
where
TZ: chrono::TimeZone,
{
type Error = &'static str;
fn try_from(orig: chrono::DateTime<TZ>) -> Result<Self, Self::Error> {
Ok(Self(
orig.to_utc()
.timestamp_nanos_opt()
.ok_or("could not convert DateTime to i64 nanosec")?
.try_into()
.map_err(|_| "could not convert i64 nanosec to u64")?,
))
}
}
impl TryFrom<PtpStamp> for chrono::DateTime<chrono::Utc> {
type Error = &'static str;
fn try_from(orig: PtpStamp) -> Result<Self, Self::Error> {
let secs = orig.0 / 1_000_000_000;
let nsecs = orig.0 % 1_000_000_000;
chrono::DateTime::from_timestamp(
secs.try_into()
.map_err(|_| "could not convert u64 nanosec to i64")?,
nsecs
.try_into()
.map_err(|_| "could not convert u64 nanosec to u32")?,
)
.ok_or("could not convert timestamp to DateTime")
}
}
impl TryFrom<PtpStamp> for chrono::DateTime<chrono::FixedOffset> {
type Error = &'static str;
fn try_from(orig: PtpStamp) -> Result<Self, Self::Error> {
let utc: chrono::DateTime<chrono::Utc> = orig.try_into()?;
Ok(utc.into())
}
}
impl TryFrom<PtpStamp> for chrono::DateTime<chrono::Local> {
type Error = &'static str;
fn try_from(orig: PtpStamp) -> Result<Self, Self::Error> {
let utc: chrono::DateTime<chrono::Utc> = orig.try_into()?;
Ok(utc.into())
}
}
pub const DEFAULT_ACQUISITION_DURATION_ALLOWED_IMPRECISION_MSEC: Option<f64> = Some(5.0);
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct BraidCameraConfig {
pub name: String,
pub camera_settings_filename: Option<std::path::PathBuf>,
pub pixel_format: Option<String>,
#[serde(default = "flydra_pt_detect_cfg::default_absdiff")]
pub point_detection_config: flydra_feature_detector_types::ImPtDetectCfg,
#[serde(default)]
pub start_backend: StartCameraBackend,
pub acquisition_duration_allowed_imprecision_msec: Option<f64>,
pub http_server_addr: Option<String>,
#[serde(default = "default_send_current_image_interval_msec")]
pub send_current_image_interval_msec: u64,
#[serde(
default,
skip_serializing,
rename = "raise_grab_thread_priority",
deserialize_with = "raise_grab_thread_priority_deser"
)]
_raise_grab_thread_priority: bool,
}
fn raise_grab_thread_priority_deser<'de, D>(de: D) -> Result<bool, D::Error>
where
D: Deserializer<'de>,
{
tracing::error!("The parameter 'raise_grab_thread_priority' is no longer used. Remove this parameter from your configuration.");
bool::deserialize(de)
}
const fn default_send_current_image_interval_msec() -> u64 {
2000
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum StartCameraBackend {
Remote,
#[default]
Pylon,
Vimba,
}
impl StartCameraBackend {
pub fn strand_cam_exe_name(&self) -> Option<&str> {
match self {
StartCameraBackend::Remote => None,
StartCameraBackend::Pylon => Some("strand-cam-pylon"),
StartCameraBackend::Vimba => Some("strand-cam-vimba"),
}
}
}
impl BraidCameraConfig {
pub fn default_absdiff_config(name: String) -> Self {
Self {
name,
camera_settings_filename: None,
pixel_format: None,
point_detection_config: flydra_pt_detect_cfg::default_absdiff(),
_raise_grab_thread_priority: Default::default(),
start_backend: Default::default(),
acquisition_duration_allowed_imprecision_msec:
DEFAULT_ACQUISITION_DURATION_ALLOWED_IMPRECISION_MSEC,
http_server_addr: None,
send_current_image_interval_msec: default_send_current_image_interval_msec(),
}
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct PerCamSaveData {
pub current_image_png: PngImageData,
pub cam_settings_data: Option<UpdateCamSettings>,
pub feature_detect_settings: Option<UpdateFeatureDetectSettings>,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct RegisterNewCamera {
pub raw_cam_name: RawCamName,
pub http_camserver_info: Option<BuiServerInfo>,
pub cam_settings_data: Option<UpdateCamSettings>,
pub current_image_png: PngImageData,
pub camera_periodic_signal_period_usec: Option<f64>,
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct UpdateImage {
pub current_image_png: PngImageData,
}
#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct PngImageData {
pub data: Vec<u8>,
}
impl From<Vec<u8>> for PngImageData {
fn from(data: Vec<u8>) -> Self {
Self { data }
}
}
impl PngImageData {
pub fn as_slice(&self) -> &[u8] {
self.data.as_slice()
}
}
impl std::fmt::Debug for PngImageData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "PngImageData{{..}}",)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct UpdateCamSettings {
pub current_cam_settings_buf: String,
pub current_cam_settings_extension: String,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct UpdateFeatureDetectSettings {
pub current_feature_detect_settings: flydra_feature_detector_types::ImPtDetectCfg,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConnectedCameraSyncState {
Unsynchronized,
Synchronized(u64),
}
impl ConnectedCameraSyncState {
pub fn is_synchronized(&self) -> bool {
match self {
ConnectedCameraSyncState::Unsynchronized => false,
ConnectedCameraSyncState::Synchronized(_) => true,
}
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct BraidHttpApiSharedState {
pub trigger_type: TriggerType,
pub needs_clock_model: bool,
pub clock_model: Option<ClockModel>,
pub csv_tables_dirname: Option<RecordingPath>,
pub fake_mp4_recording_path: Option<RecordingPath>,
pub post_trigger_buffer_size: usize,
pub calibration_filename: Option<String>,
pub connected_cameras: Vec<CamInfo>, pub model_server_addr: Option<SocketAddr>,
pub flydra_app_name: String,
pub all_expected_cameras_are_synced: bool,
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
pub struct RecentStats {
pub total_frames_collected: usize,
pub frames_collected: usize,
pub points_detected: usize,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum BuiServerInfo {
NoServer,
Server(BuiServerAddrInfo),
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct BuiServerAddrInfo {
addr: SocketAddr,
token: AccessToken,
}
impl BuiServerAddrInfo {
pub fn new(addr: SocketAddr, token: AccessToken) -> Self {
Self { addr, token }
}
pub fn addr(&self) -> &SocketAddr {
&self.addr
}
pub fn token(&self) -> &AccessToken {
&self.token
}
#[cfg(feature = "build-urls")]
pub fn build_urls(&self) -> std::io::Result<Vec<http::Uri>> {
let query = match &self.token {
AccessToken::NoToken => "".to_string(),
AccessToken::PreSharedToken(tok) => format!("?token={tok}"),
};
Ok(expand_unspecified_addr(self.addr())?
.into_iter()
.map(|specified_addr| {
let addr = specified_addr.addr();
http::uri::Builder::new()
.scheme("http")
.authority(format!("{}:{}", addr.ip(), addr.port()))
.path_and_query(format!("/{query}"))
.build()
.unwrap()
})
.collect())
}
pub fn parse_url_with_token(url: &str) -> Result<Self, FlydraTypesError> {
let stripped = url
.strip_prefix("http://")
.ok_or(FlydraTypesError::UrlParseError)?;
let first_slash = stripped.find('/');
let (addr_str, token) = if let Some(slash_idx) = first_slash {
let path = &stripped[slash_idx..];
if path == "/" || path == "/?" {
(&stripped[..slash_idx], AccessToken::NoToken)
} else {
let token_str = path[1..]
.strip_prefix("?token=")
.ok_or(FlydraTypesError::UrlParseError)?;
(
&stripped[..slash_idx],
AccessToken::PreSharedToken(token_str.to_string()),
)
}
} else {
(stripped, AccessToken::NoToken)
};
let addr = std::net::ToSocketAddrs::to_socket_addrs(addr_str)?
.next()
.ok_or(FlydraTypesError::UrlParseError)?;
if addr.ip().is_unspecified() {
return Err(FlydraTypesError::UrlParseError);
}
Ok(Self::new(addr, token))
}
pub fn base_url(&self) -> String {
format!("http://{}", self.addr)
}
}
pub fn is_loopback(url: &http::Uri) -> bool {
let authority = match url.authority() {
None => return false,
Some(authority) => authority,
};
match authority.host() {
"127.0.0.1" | "[::1]" => true,
_ => false,
}
}
#[derive(Debug, PartialEq, Clone, Serialize)]
#[serde(transparent)]
pub struct SpecifiedSocketAddr(SocketAddr);
impl std::fmt::Display for SpecifiedSocketAddr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
std::fmt::Display::fmt(&self.0, f)
}
}
impl SpecifiedSocketAddr {
fn make_err() -> std::io::Error {
std::io::ErrorKind::AddrNotAvailable.into()
}
pub fn new(addr: SocketAddr) -> std::io::Result<Self> {
if addr.ip().is_unspecified() {
return Err(Self::make_err());
}
Ok(Self(addr))
}
pub fn ip(&self) -> std::net::IpAddr {
self.0.ip()
}
pub fn addr(&self) -> &std::net::SocketAddr {
&self.0
}
}
impl<'de> serde::Deserialize<'de> for SpecifiedSocketAddr {
fn deserialize<D>(deserializer: D) -> std::result::Result<SpecifiedSocketAddr, D::Error>
where
D: serde::Deserializer<'de>,
{
let addr: SocketAddr = std::net::SocketAddr::deserialize(deserializer)?;
SpecifiedSocketAddr::new(addr).map_err(|_e| serde::de::Error::custom(Self::make_err()))
}
}
#[cfg(feature = "start-listener")]
pub async fn start_listener(
address_string: &str,
) -> eyre::Result<(tokio::net::TcpListener, BuiServerAddrInfo)> {
let socket_addr = std::net::ToSocketAddrs::to_socket_addrs(&address_string)?
.next()
.ok_or_else(|| eyre::eyre!("no address found for HTTP server"))?;
let listener = tokio::net::TcpListener::bind(socket_addr).await?;
let listener_local_addr = listener.local_addr()?;
let token_config = if !listener_local_addr.ip().is_loopback() {
Some(axum_token_auth::TokenConfig::new_token("token"))
} else {
None
};
let token = match token_config {
None => bui_backend_session_types::AccessToken::NoToken,
Some(cfg) => bui_backend_session_types::AccessToken::PreSharedToken(cfg.value.clone()),
};
let http_camserver_info = BuiServerAddrInfo::new(listener_local_addr, token);
Ok((listener, http_camserver_info))
}
#[cfg(feature = "build-urls")]
fn expand_unspecified_ip(ip: std::net::IpAddr) -> std::io::Result<Vec<std::net::IpAddr>> {
if ip.is_unspecified() {
Ok(if_addrs::get_if_addrs()?
.iter()
.filter_map(|x| {
let this_ip = x.addr.ip();
if ip.is_ipv4() == this_ip.is_ipv4() {
Some(this_ip)
} else {
None
}
})
.collect())
} else {
Ok(vec![ip])
}
}
#[cfg(feature = "build-urls")]
pub fn expand_unspecified_addr(addr: &SocketAddr) -> std::io::Result<Vec<SpecifiedSocketAddr>> {
if addr.ip().is_unspecified() {
expand_unspecified_ip(addr.ip())?
.into_iter()
.map(|ip| SpecifiedSocketAddr::new(SocketAddr::new(ip, addr.port())))
.collect()
} else {
Ok(vec![SpecifiedSocketAddr::new(*addr).unwrap()])
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct TextlogRow {
pub mainbrain_timestamp: f64,
pub cam_id: String,
pub host_timestamp: f64,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TrackingParams {
pub motion_noise_scale: f64,
pub initial_position_std_meters: f64,
pub initial_vel_std_meters_per_sec: f64,
pub ekf_observation_covariance_pixels: f64,
pub accept_observation_min_likelihood: f64,
pub max_position_std_meters: f32,
#[serde(skip_serializing_if = "Option::is_none")]
pub hypothesis_test_params: Option<HypothesisTestParams>,
#[serde(default = "default_num_observations_to_visibility")]
pub num_observations_to_visibility: u8,
#[serde(skip_serializing_if = "MiniArenaConfig::is_none", default)]
pub mini_arena_config: MiniArenaConfig,
}
pub struct MiniArenaLocator {
my_idx: Option<u8>,
}
impl MiniArenaLocator {
pub fn from_mini_arena_idx(val: u8) -> Self {
Self { my_idx: Some(val) }
}
pub fn new_none() -> Self {
Self { my_idx: None }
}
pub fn idx(&self) -> Option<u8> {
self.my_idx
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
#[serde(tag = "type")]
pub enum MiniArenaConfig {
#[default]
NoMiniArena,
XYGrid(XYGridConfig),
}
impl MiniArenaConfig {
pub fn is_none(&self) -> bool {
self == &Self::NoMiniArena
}
pub fn get_arena_index(&self, coords: &nalgebra::Point3<MyFloat>) -> MiniArenaLocator {
match self {
Self::NoMiniArena => MiniArenaLocator::from_mini_arena_idx(0),
Self::XYGrid(xy_grid_config) => xy_grid_config.get_arena_index(coords),
}
}
pub fn iter_locators(&self) -> impl Iterator<Item = MiniArenaLocator> {
let res = match self {
Self::NoMiniArena => vec![MiniArenaLocator::from_mini_arena_idx(0)],
Self::XYGrid(xy_grid_config) => {
let sz = xy_grid_config.x_centers.0.len() * xy_grid_config.y_centers.0.len();
(0..sz)
.map(|idx| MiniArenaLocator::from_mini_arena_idx(idx.try_into().unwrap()))
.collect()
}
};
res.into_iter()
}
pub fn len(&self) -> usize {
match self {
Self::NoMiniArena => 1,
Self::XYGrid(xy_grid_config) => {
xy_grid_config.x_centers.0.len() * xy_grid_config.y_centers.0.len()
}
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct Sorted(Vec<f64>);
impl Sorted {
fn new(vals: &[f64]) -> Self {
assert!(!vals.is_empty());
let mut vals: Vec<NotNan<f64>> = vals.iter().map(|v| NotNan::new(*v).unwrap()).collect();
vals.sort();
let vals = vals.iter().map(|v| v.into_inner()).collect();
Sorted(vals)
}
fn dist_and_argmin(&self, x: f64) -> (f64, usize) {
let mut best_dist = std::f64::INFINITY;
let mut prev_dist = std::f64::INFINITY;
let mut best_idx = 0;
for (i, selfi) in self.0.iter().enumerate() {
let dist = (selfi - x).abs();
if dist < best_dist {
best_dist = dist;
best_idx = i;
}
if dist > prev_dist {
break;
}
prev_dist = dist
}
(best_dist, best_idx)
}
}
#[test]
fn test_sorted() {
let x = Sorted::new(&[1.0, 2.0, 1.0]);
assert_eq!(x.0, vec![1.0, 1.0, 2.0]);
assert_eq!(x.dist_and_argmin(1.1).1, 0);
let x = Sorted::new(&[1.0, 2.0, 1.0, 3.0, 4.0]);
assert_eq!(x.dist_and_argmin(2.1).1, 2);
assert_eq!(x.dist_and_argmin(1.9).1, 2);
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct XYGridConfig {
x_centers: Sorted,
y_centers: Sorted,
radius: f64,
}
impl XYGridConfig {
pub fn new(x: &[f64], y: &[f64], radius: f64) -> Self {
Self {
x_centers: Sorted::new(x),
y_centers: Sorted::new(y),
radius,
}
}
pub fn iter_centers(&self) -> impl Iterator<Item = (f64, f64)> {
XYGridIter {
col_centers: self.x_centers.0.clone(),
row_centers: self.y_centers.0.clone(),
next_idx: 0,
}
}
pub fn get_arena_index(&self, coords: &nalgebra::Point3<MyFloat>) -> MiniArenaLocator {
if coords.z != 0.0 {
return MiniArenaLocator::new_none();
}
let obj_x = coords.x;
let obj_y = coords.y;
let (dist_x, idx_x) = self.x_centers.dist_and_argmin(obj_x);
let (dist_y, idx_y) = self.y_centers.dist_and_argmin(obj_y);
let dist = (dist_x * dist_x + dist_y * dist_y).sqrt();
if dist <= self.radius {
let idx = (idx_y * self.x_centers.0.len() + idx_x).try_into().unwrap();
MiniArenaLocator::from_mini_arena_idx(idx)
} else {
MiniArenaLocator::new_none()
}
}
}
struct XYGridIter {
row_centers: Vec<f64>,
col_centers: Vec<f64>,
next_idx: usize,
}
impl Iterator for XYGridIter {
type Item = (f64, f64);
fn next(&mut self) -> Option<Self::Item> {
let (row_idx, col_idx) = num_integer::div_rem(self.next_idx, self.col_centers.len());
if row_idx >= self.row_centers.len() {
None
} else {
let result = (self.col_centers[col_idx], self.row_centers[row_idx]);
self.next_idx += 1;
Some(result)
}
}
}
fn default_num_observations_to_visibility() -> u8 {
3
}
pub type MyFloat = f64;
pub fn default_tracking_params_full_3d() -> TrackingParams {
TrackingParams {
motion_noise_scale: 0.1,
initial_position_std_meters: 0.1,
initial_vel_std_meters_per_sec: 1.0,
accept_observation_min_likelihood: 1e-8,
ekf_observation_covariance_pixels: 1.0,
max_position_std_meters: 0.01212,
hypothesis_test_params: Some(make_hypothesis_test_full3d_default()),
num_observations_to_visibility: default_num_observations_to_visibility(),
mini_arena_config: MiniArenaConfig::NoMiniArena,
}
}
pub fn default_tracking_params_flat_3d() -> TrackingParams {
TrackingParams {
motion_noise_scale: 0.0005,
initial_position_std_meters: 0.001,
initial_vel_std_meters_per_sec: 0.02,
accept_observation_min_likelihood: 0.00001,
ekf_observation_covariance_pixels: 1.0,
max_position_std_meters: 0.003,
hypothesis_test_params: None,
num_observations_to_visibility: 10,
mini_arena_config: MiniArenaConfig::NoMiniArena,
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HypothesisTestParams {
pub minimum_number_of_cameras: u8,
pub hypothesis_test_max_acceptable_error: f64,
pub minimum_pixel_abs_zscore: f64,
}
pub fn make_hypothesis_test_full3d_default() -> HypothesisTestParams {
HypothesisTestParams {
minimum_number_of_cameras: 2,
hypothesis_test_max_acceptable_error: 5.0,
minimum_pixel_abs_zscore: 0.0,
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct CamInfo {
pub name: RawCamName,
pub state: ConnectedCameraSyncState,
pub strand_cam_http_server_info: BuiServerInfo,
pub recent_stats: RecentStats,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum BraidHttpApiCallback {
NewCamera(RegisterNewCamera),
UpdateCurrentImage(PerCam<UpdateImage>),
UpdateCamSettings(PerCam<UpdateCamSettings>),
UpdateFeatureDetectSettings(PerCam<UpdateFeatureDetectSettings>),
DoRecordCsvTables(bool),
DoRecordMp4Files(bool),
SetExperimentUuid(String),
SetPostTriggerBufferSize(usize),
PostTriggerMp4Recording,
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct PerCam<T> {
pub raw_cam_name: RawCamName,
pub inner: T,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct FlydraRawUdpPacket {
pub cam_name: String,
#[serde(with = "crate::timestamp_opt_f64")]
pub timestamp: Option<FlydraFloatTimestampLocal<Triggerbox>>,
#[serde(with = "crate::timestamp_f64")]
pub cam_received_time: FlydraFloatTimestampLocal<HostClock>,
pub device_timestamp: Option<std::num::NonZeroU64>,
pub block_id: Option<std::num::NonZeroU64>,
pub framenumber: i32,
pub n_frames_skipped: u32,
pub done_camnode_processing: f64,
pub preprocess_stamp: f64,
pub image_processing_steps: ImageProcessingSteps,
pub points: Vec<FlydraRawUdpPoint>,
}
mod synced_frame;
pub use synced_frame::SyncFno;
mod cam_num;
pub use cam_num::CamNum;
mod timestamp;
pub use crate::timestamp::{
triggerbox_time, FlydraFloatTimestampLocal, HostClock, Source, Triggerbox,
};
pub mod timestamp_f64;
pub mod timestamp_opt_f64;
#[cfg(feature = "with-tokio-codec")]
mod tokio_cbor;
#[cfg(feature = "with-tokio-codec")]
pub use crate::tokio_cbor::CborPacketCodec;
#[derive(thiserror::Error, Debug)]
pub enum FlydraTypesError {
#[error("CBOR data")]
CborDataError,
#[error("serde error")]
SerdeError,
#[error("unexpected hypothesis testing parameters")]
UnexpectedHypothesisTestingParameters,
#[error("input too long")]
InputTooLong,
#[error("long string not implemented")]
LongStringNotImplemented,
#[error("{0}")]
IoError(#[from] std::io::Error),
#[error("{0}")]
Utf8Error(#[from] std::str::Utf8Error),
#[error("URL parse error")]
UrlParseError,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct TriggerClockInfoRow {
#[serde(with = "crate::timestamp_f64")]
pub start_timestamp: FlydraFloatTimestampLocal<HostClock>,
pub framecount: i64,
pub tcnt: u8,
#[serde(with = "crate::timestamp_f64")]
pub stop_timestamp: FlydraFloatTimestampLocal<HostClock>,
}
bitflags! {
#[derive(Serialize, Deserialize)]
pub struct ImageProcessingSteps: u8 {
const BGINIT = 0b00000001;
const BGSTARTUP = 0b00000010;
const BGCLEARED = 0b00000100;
const BGUPDATE = 0b00001000;
const BGNORMAL = 0b00010000;
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct TriggerboxConfig {
pub device_fname: String,
pub framerate: f32,
#[serde(default = "default_query_dt")]
pub query_dt: std::time::Duration,
pub max_triggerbox_measurement_error: Option<std::time::Duration>,
}
impl std::default::Default for TriggerboxConfig {
fn default() -> Self {
Self {
device_fname: "/dev/trig1".to_string(),
framerate: 100.0,
query_dt: default_query_dt(),
max_triggerbox_measurement_error: Some(std::time::Duration::from_millis(20)),
}
}
}
const fn default_query_dt() -> std::time::Duration {
std::time::Duration::from_millis(1500)
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct PtpSyncConfig {
pub periodic_signal_period_usec: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct FakeSyncConfig {
pub framerate: f64,
}
impl Default for FakeSyncConfig {
fn default() -> Self {
Self { framerate: 95.0 }
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
#[serde(tag = "trigger_type")]
pub enum TriggerType {
TriggerboxV1(TriggerboxConfig),
PtpSync(PtpSyncConfig),
DeviceTimestamp,
FakeSync(FakeSyncConfig),
}
impl Default for TriggerType {
fn default() -> Self {
TriggerType::FakeSync(FakeSyncConfig::default())
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Data2dDistortedRow {
pub camn: CamNum,
pub frame: i64,
#[serde(with = "crate::timestamp_opt_f64")]
pub timestamp: Option<FlydraFloatTimestampLocal<Triggerbox>>,
#[serde(with = "crate::timestamp_f64")]
pub cam_received_timestamp: FlydraFloatTimestampLocal<HostClock>,
pub device_timestamp: Option<std::num::NonZeroU64>,
pub block_id: Option<std::num::NonZeroU64>,
#[serde(deserialize_with = "invalid_nan")]
pub x: f64,
#[serde(deserialize_with = "invalid_nan")]
pub y: f64,
#[serde(deserialize_with = "invalid_nan")]
pub area: f64,
#[serde(deserialize_with = "invalid_nan")]
pub slope: f64,
#[serde(deserialize_with = "invalid_nan")]
pub eccentricity: f64,
pub frame_pt_idx: u8,
pub cur_val: u8,
#[serde(deserialize_with = "invalid_nan")]
pub mean_val: f64,
#[serde(deserialize_with = "invalid_nan")]
pub sumsqf_val: f64,
}
#[derive(Debug, Serialize)]
pub struct Data2dDistortedRowF32 {
pub camn: CamNum,
pub frame: i64,
#[serde(with = "crate::timestamp_opt_f64")]
pub timestamp: Option<FlydraFloatTimestampLocal<Triggerbox>>,
#[serde(with = "crate::timestamp_f64")]
pub cam_received_timestamp: FlydraFloatTimestampLocal<HostClock>,
pub device_timestamp: Option<std::num::NonZeroU64>,
pub block_id: Option<std::num::NonZeroU64>,
pub x: f32,
pub y: f32,
pub area: f32,
pub slope: f32,
pub eccentricity: f32,
pub frame_pt_idx: u8,
pub cur_val: u8,
pub mean_val: f32,
pub sumsqf_val: f32,
}
impl From<Data2dDistortedRow> for Data2dDistortedRowF32 {
fn from(orig: Data2dDistortedRow) -> Self {
Self {
camn: orig.camn,
frame: orig.frame,
timestamp: orig.timestamp,
cam_received_timestamp: orig.cam_received_timestamp,
device_timestamp: orig.device_timestamp,
block_id: orig.block_id,
x: orig.x as f32,
y: orig.y as f32,
area: orig.area as f32,
slope: orig.slope as f32,
eccentricity: orig.eccentricity as f32,
frame_pt_idx: orig.frame_pt_idx,
cur_val: orig.cur_val,
mean_val: orig.mean_val as f32,
sumsqf_val: orig.sumsqf_val as f32,
}
}
}
impl WithKey<i64> for Data2dDistortedRow {
fn key(&self) -> i64 {
self.frame
}
}
fn invalid_nan<'de, D>(de: D) -> Result<f64, D::Error>
where
D: Deserializer<'de>,
{
f64::deserialize(de).or(
Ok(std::f64::NAN),
)
}
pub const BRAID_EVENTS_URL_PATH: &str = "braid-events";
pub const BRAID_EVENT_NAME: &str = "braid";