///|
pub(all) struct TimeSeriesStats {
count : Int
first : Stamp?
last : Stamp?
span_ns : Int64
min_period_ns : Int64?
max_period_ns : Int64?
mean_period_ns : Double
monotonic : Bool
} derive(Debug, ToJson)
///|
pub fn empty_time_series_stats() -> TimeSeriesStats {
{
count: 0,
first: None,
last: None,
span_ns: 0L,
min_period_ns: None,
max_period_ns: None,
mean_period_ns: 0.0,
monotonic: true,
}
}
///|
pub fn[T] time_series_stats(
items : ArrayView[T],
stamp_of : (T) -> Stamp,
) -> TimeSeriesStats {
if items.is_empty() {
return empty_time_series_stats()
}
let first = stamp_of(items[0])
let last = stamp_of(items[items.length() - 1])
let mut min_period : Int64? = None
let mut max_period : Int64? = None
let mut sum = 0L
let mut monotonic = true
for i in 1.. min_period = Some(period)
Some(value) => if period < value { min_period = Some(period) }
}
match max_period {
None => max_period = Some(period)
Some(value) => if period > value { max_period = Some(period) }
}
sum += period
}
{
count: items.length(),
first: Some(first),
last: Some(last),
span_ns: last.to_nanoseconds() - first.to_nanoseconds(),
min_period_ns: min_period,
max_period_ns: max_period,
mean_period_ns: if items.length() > 1 {
sum.to_double() / (items.length() - 1).to_double()
} else {
0.0
},
monotonic,
}
}
///|
pub fn period_jitter_ns(periods : ArrayView[Int64]) -> (Int64, Int64, Double) {
if periods.is_empty() {
return (0L, 0L, 0.0)
}
let mut min_value = periods[0]
let mut max_value = periods[0]
let mut sum = 0L
for value in periods {
if value < min_value {
min_value = value
}
if value > max_value {
max_value = value
}
sum += value
}
(min_value, max_value, sum.to_double() / periods.length().to_double())
}
///|
pub fn expected_period_ns(rate_hz : Double) -> Int64 raise VisionFormatError {
if rate_hz <= 0.0 {
raise VisionFormatError::InvalidNumber("rate must be positive")
}
(1000000000.0 / rate_hz).to_int64()
}
///|
pub fn missing_periods_ns(
periods : ArrayView[Int64],
expected : Int64,
tolerance : Int64,
) -> Int {
if expected <= 0L || tolerance < 0L {
return 0
}
let mut count = 0
for period in periods {
if period > expected + tolerance {
count += ((period + expected / 2L) / expected).to_int() - 1
}
}
count
}
///|
pub fn[T] nearest_stamp_index(
stamp : Stamp,
items : ArrayView[T],
stamp_of : (T) -> Stamp,
) -> Int? {
if items.is_empty() {
return None
}
let mut best = 0
let mut distance = abs_i64(
stamp.to_nanoseconds() - stamp_of(items[0]).to_nanoseconds(),
)
for i in 1.. Stamp,
) -> Array[T] {
let result = Array::new()
let a = start.to_nanoseconds()
let b = end.to_nanoseconds()
for item in items {
let value = stamp_of(item).to_nanoseconds()
if value >= a && value <= b {
result.push(item)
}
}
result
}
///|
pub fn[T] split_by_gap(
items : ArrayView[T],
stamp_of : (T) -> Stamp,
gap_ns : Int64,
) -> Array[Array[T]] {
let groups : Array[Array[T]] = []
if items.is_empty() {
return groups
}
let mut current = Array::new()
current.push(items[0])
for i in 1.. gap_ns {
groups.push(current)
let next = Array::new()
next.push(items[i])
current = next
} else {
current.push(items[i])
}
}
groups.push(current)
groups
}