///|
/// Summary of timestamp spacing in a force plate recording.
pub struct TimebaseSummary {
sample_count : Int
interval_count : Int
positive_intervals : Int
duration_s : Double
mean_dt_s : Double
median_dt_s : Double
min_dt_s : Double
max_dt_s : Double
nominal_rate_hz : Double
monotonic : Bool
} derive(Debug, Eq)
///|
/// Summarizes timestamp ordering and sampling interval statistics.
pub fn summarize_timebase(timestamps : Array[Double]) -> TimebaseSummary {
let n = timestamps.length()
if n == 0 {
return {
sample_count: 0,
interval_count: 0,
positive_intervals: 0,
duration_s: 0.0,
mean_dt_s: 0.0,
median_dt_s: 0.0,
min_dt_s: 0.0,
max_dt_s: 0.0,
nominal_rate_hz: 0.0,
monotonic: false,
}
}
let intervals : Array[Double] = Array::new(
capacity=if n > 1 { n - 1 } else { 0 },
)
let mut monotonic = true
let mut positive = 0
for i = 1; i < n; i = i + 1 {
let delta = timestamps[i] - timestamps[i - 1]
intervals.push(delta)
if delta <= 0.0 || delta.is_nan() || delta.is_inf() {
monotonic = false
} else {
positive = positive + 1
}
}
let duration = if n > 1 { timestamps[n - 1] - timestamps[0] } else { 0.0 }
let mean_dt = if positive == 0 {
0.0
} else {
let mut sum = 0.0
for interval in intervals {
if interval > 0.0 && !interval.is_nan() && !interval.is_inf() {
sum = sum + interval
}
}
sum / positive.to_double()
}
let median_dt = if positive == intervals.length() {
median(intervals)
} else {
let positive_intervals : Array[Double] = Array::new(capacity=positive)
for interval in intervals {
if interval > 0.0 && !interval.is_nan() && !interval.is_inf() {
positive_intervals.push(interval)
}
}
median(positive_intervals)
}
let min_dt = if positive == 0 {
0.0
} else {
let mut result = 1.0 / 0.0
for interval in intervals {
if interval > 0.0 && interval < result {
result = interval
}
}
result
}
let max_dt = if positive == 0 {
0.0
} else {
let mut result = 0.0
for interval in intervals {
if interval > result && !interval.is_nan() && !interval.is_inf() {
result = interval
}
}
result
}
{
sample_count: n,
interval_count: intervals.length(),
positive_intervals: positive,
duration_s: duration,
mean_dt_s: mean_dt,
median_dt_s: median_dt,
min_dt_s: min_dt,
max_dt_s: max_dt,
nominal_rate_hz: if monotonic && mean_dt > 0.0 {
1.0 / mean_dt
} else {
0.0
},
monotonic,
}
}
///|
/// Returns true when all positive intervals lie within an absolute tolerance.
pub fn timebase_is_regular(
summary : TimebaseSummary,
tolerance_s : Double,
) -> Bool {
let tolerance = if tolerance_s < 0.0 { 0.0 } else { tolerance_s }
summary.monotonic &&
summary.interval_count > 0 &&
summary.max_dt_s - summary.min_dt_s <= tolerance
}
///|
/// Estimates the sampling rate from a monotonic timestamp sequence.
pub fn estimate_sample_rate(timestamps : Array[Double]) -> Double {
summarize_timebase(timestamps).nominal_rate_hz
}