use std::sync::Arc;
use crate::error::{Mf4Error, Result};
use crate::model::{Channel, SignalValues};
pub fn validate_master_axis(
name: &str,
timestamps: &[f64],
validity: Option<&[bool]>,
offset: usize,
mut previous: Option<f64>,
) -> Result<()> {
if validity.is_some_and(|v| v.len() != timestamps.len()) {
return Err(Mf4Error::parse_error(format!(
"master channel '{name}' has a mismatched timestamp validity length"
)));
}
for (index, &value) in timestamps.iter().enumerate() {
let reason = if validity.is_some_and(|v| !v[index]) {
Some("invalid timestamp")
} else if !value.is_finite() {
Some("non-finite timestamp")
} else if previous.is_some_and(|p| value < p) {
Some("decreasing timestamp")
} else {
None
};
if let Some(reason) = reason {
return Err(Mf4Error::parse_error(format!(
"master channel '{name}' has a {reason} at sample {}",
offset + index
)));
}
previous = Some(value);
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum InterpolationMode {
#[default]
StepHold,
Linear,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Raster {
Step(f64),
Timestamps(Vec<f64>),
}
impl From<f64> for Raster {
fn from(step: f64) -> Self {
Raster::Step(step)
}
}
impl From<Vec<f64>> for Raster {
fn from(timestamps: Vec<f64>) -> Self {
Raster::Timestamps(timestamps)
}
}
impl From<&[f64]> for Raster {
fn from(timestamps: &[f64]) -> Self {
Raster::Timestamps(timestamps.to_vec())
}
}
#[derive(Debug, Clone)]
pub struct SignalSeries {
pub channel: Channel,
pub timestamps: Arc<Vec<f64>>,
pub values: SignalValues,
pub validity: Option<Vec<bool>>,
}
impl PartialEq for SignalSeries {
fn eq(&self, other: &Self) -> bool {
self.channel.name == other.channel.name
&& self.channel.unit == other.channel.unit
&& self.timestamps == other.timestamps
&& self.values == other.values
&& self.validity == other.validity
}
}
impl SignalSeries {
pub fn new(
channel: Channel,
timestamps: impl Into<Arc<Vec<f64>>>,
values: SignalValues,
validity: Option<Vec<bool>>,
) -> Result<Self> {
let timestamps = timestamps.into();
if timestamps.len() != values.len() {
return Err(Mf4Error::parse_error(format!(
"timestamps length ({}) does not match values length ({}) for channel '{}'",
timestamps.len(),
values.len(),
channel.name
)));
}
if let Some(v) = &validity {
if v.len() != values.len() {
return Err(Mf4Error::parse_error(format!(
"validity length ({}) does not match values length ({}) for channel '{}'",
v.len(),
values.len(),
channel.name
)));
}
}
Ok(Self {
channel,
timestamps,
values,
validity,
})
}
pub fn from_samples(
name: impl Into<String>,
unit: impl Into<String>,
timestamps: Vec<f64>,
values: SignalValues,
validity: Option<Vec<bool>>,
) -> Result<Self> {
let channel = Channel::synthetic(name, unit);
Self::new(channel, timestamps, values, validity)
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn name(&self) -> &str {
&self.channel.name
}
pub fn unit(&self) -> &str {
&self.channel.unit
}
pub fn timestamps(&self) -> &[f64] {
self.timestamps.as_slice()
}
pub fn timestamps_shared(&self) -> Arc<Vec<f64>> {
Arc::clone(&self.timestamps)
}
pub fn values(&self) -> &SignalValues {
&self.values
}
pub fn validity(&self) -> Option<&[bool]> {
self.validity.as_deref()
}
pub fn values_f64(&self) -> Vec<f64> {
self.values.to_f64()
}
pub fn cut(&self, start: f64, end: f64) -> Self {
if start > end || self.timestamps.is_empty() {
return Self {
channel: self.channel.clone(),
timestamps: Arc::new(Vec::new()),
values: slice_values(&self.values, 0..0),
validity: self.validity.as_ref().map(|_| Vec::new()),
};
}
let start_idx = self.timestamps.partition_point(|&t| t < start);
let end_idx = self.timestamps.partition_point(|&t| t <= end);
let cut_timestamps = self.timestamps[start_idx..end_idx].to_vec();
let cut_values = slice_values(&self.values, start_idx..end_idx);
let cut_validity = self
.validity
.as_ref()
.map(|v| v[start_idx..end_idx].to_vec());
Self {
channel: self.channel.clone(),
timestamps: cut_timestamps.into(),
values: cut_values,
validity: cut_validity,
}
}
pub fn resample(&self, raster: impl Into<Raster>, mode: InterpolationMode) -> Result<Self> {
let raster = raster.into();
let target_timestamps = match raster {
Raster::Step(dt) => {
if dt <= 0.0 || !dt.is_finite() {
return Err(Mf4Error::parse_error(format!(
"resample raster step must be positive and finite, got {dt}"
)));
}
if self.timestamps.is_empty() {
Vec::new()
} else {
let t_min = self.timestamps[0];
let t_max = *self.timestamps.last().unwrap();
generate_raster_grid(t_min, t_max, dt)
}
}
Raster::Timestamps(ts) => ts,
};
let resampled_values =
resample_values(&self.values, &self.timestamps, &target_timestamps, mode);
let resampled_validity = resample_validity(
self.validity.as_deref(),
&self.timestamps,
&target_timestamps,
);
Self::new(
self.channel.clone(),
target_timestamps,
resampled_values,
resampled_validity,
)
}
pub fn add(&self, other: &SignalSeries) -> Result<SignalSeries> {
self.apply_binary(other, |a, b| a + b)
}
pub fn sub(&self, other: &SignalSeries) -> Result<SignalSeries> {
self.apply_binary(other, |a, b| a - b)
}
pub fn mul(&self, other: &SignalSeries) -> Result<SignalSeries> {
self.apply_binary(other, |a, b| a * b)
}
pub fn div(&self, other: &SignalSeries) -> Result<SignalSeries> {
self.apply_binary(other, |a, b| a / b)
}
pub fn add_scalar(&self, scalar: f64) -> Result<SignalSeries> {
self.apply_scalar(scalar, |a, b| a + b)
}
pub fn sub_scalar(&self, scalar: f64) -> Result<SignalSeries> {
self.apply_scalar(scalar, |a, b| a - b)
}
pub fn mul_scalar(&self, scalar: f64) -> Result<SignalSeries> {
self.apply_scalar(scalar, |a, b| a * b)
}
pub fn div_scalar(&self, scalar: f64) -> Result<SignalSeries> {
self.apply_scalar(scalar, |a, b| a / b)
}
pub fn sub_from_scalar(&self, scalar: f64) -> Result<SignalSeries> {
self.apply_scalar(scalar, |a, b| b - a)
}
pub fn div_by_scalar(&self, scalar: f64) -> Result<SignalSeries> {
self.apply_scalar(scalar, |a, b| b / a)
}
pub fn neg(&self) -> SignalSeries {
let values: Vec<f64> = self.values.to_f64().iter().map(|&v| -v).collect();
SignalSeries {
channel: self.channel.clone(),
timestamps: self.timestamps.clone(),
values: SignalValues::F64(values),
validity: self.validity.clone(),
}
}
pub fn lt(&self, other: &SignalSeries) -> Result<SignalSeries> {
self.compare(other, |a, b| if a < b { 1.0 } else { 0.0 })
}
pub fn le(&self, other: &SignalSeries) -> Result<SignalSeries> {
self.compare(other, |a, b| if a <= b { 1.0 } else { 0.0 })
}
pub fn gt(&self, other: &SignalSeries) -> Result<SignalSeries> {
self.compare(other, |a, b| if a > b { 1.0 } else { 0.0 })
}
pub fn ge(&self, other: &SignalSeries) -> Result<SignalSeries> {
self.compare(other, |a, b| if a >= b { 1.0 } else { 0.0 })
}
pub fn eq_samples(&self, other: &SignalSeries) -> Result<SignalSeries> {
self.compare(other, |a, b| if a == b { 1.0 } else { 0.0 })
}
pub fn ne_samples(&self, other: &SignalSeries) -> Result<SignalSeries> {
self.compare(other, |a, b| if a != b { 1.0 } else { 0.0 })
}
fn apply_scalar<F>(&self, scalar: f64, op: F) -> Result<SignalSeries>
where
F: Fn(f64, f64) -> f64,
{
let values: Vec<f64> = self
.values
.to_f64()
.iter()
.map(|&v| op(v, scalar))
.collect();
Ok(SignalSeries {
channel: self.channel.clone(),
timestamps: self.timestamps.clone(),
values: SignalValues::F64(values),
validity: self.validity.clone(),
})
}
fn apply_binary<F>(&self, other: &SignalSeries, op: F) -> Result<SignalSeries>
where
F: Fn(f64, f64) -> f64,
{
let (s1, s2, time) = self.align(other)?;
let a = s1.values.to_f64();
let b = s2.values.to_f64();
let values: Vec<f64> = a.iter().zip(&b).map(|(&x, &y)| op(x, y)).collect();
let validity = combine_validity(s1.validity.as_deref(), s2.validity.as_deref());
Ok(SignalSeries {
channel: self.channel.clone(),
timestamps: time.into(),
values: SignalValues::F64(values),
validity,
})
}
fn compare<F>(&self, other: &SignalSeries, op: F) -> Result<SignalSeries>
where
F: Fn(f64, f64) -> f64,
{
self.apply_binary(other, op)
}
fn align(&self, other: &SignalSeries) -> Result<(SignalSeries, SignalSeries, Vec<f64>)> {
if self.timestamps.is_empty() || other.timestamps.is_empty() {
return Err(Mf4Error::parse_error(
"cannot combine signals: one of the operands is empty".to_string(),
));
}
let start = self.timestamps[0].max(other.timestamps[0]);
let end = self
.timestamps
.last()
.unwrap()
.min(*other.timestamps.last().unwrap());
if start > end {
return Ok((self.cut(start, start), other.cut(start, start), Vec::new()));
}
let time = union_timestamps_in_range(&self.timestamps, &other.timestamps, start, end);
let s1 = self.resample(Raster::Timestamps(time.clone()), InterpolationMode::Linear)?;
let s2 = other.resample(Raster::Timestamps(time.clone()), InterpolationMode::Linear)?;
Ok((s1, s2, time))
}
}
impl std::ops::Add for &SignalSeries {
type Output = SignalSeries;
fn add(self, other: Self) -> Self::Output {
self.add(other).expect("SignalSeries addition failed")
}
}
impl std::ops::Sub for &SignalSeries {
type Output = SignalSeries;
fn sub(self, other: Self) -> Self::Output {
self.sub(other).expect("SignalSeries subtraction failed")
}
}
impl std::ops::Mul for &SignalSeries {
type Output = SignalSeries;
fn mul(self, other: Self) -> Self::Output {
self.mul(other).expect("SignalSeries multiplication failed")
}
}
impl std::ops::Div for &SignalSeries {
type Output = SignalSeries;
fn div(self, other: Self) -> Self::Output {
self.div(other).expect("SignalSeries division failed")
}
}
impl std::ops::Add<f64> for &SignalSeries {
type Output = SignalSeries;
fn add(self, other: f64) -> Self::Output {
self.add_scalar(other)
.expect("SignalSeries scalar addition failed")
}
}
impl std::ops::Sub<f64> for &SignalSeries {
type Output = SignalSeries;
fn sub(self, other: f64) -> Self::Output {
self.sub_scalar(other)
.expect("SignalSeries scalar subtraction failed")
}
}
impl std::ops::Mul<f64> for &SignalSeries {
type Output = SignalSeries;
fn mul(self, other: f64) -> Self::Output {
self.mul_scalar(other)
.expect("SignalSeries scalar multiplication failed")
}
}
impl std::ops::Div<f64> for &SignalSeries {
type Output = SignalSeries;
fn div(self, other: f64) -> Self::Output {
self.div_scalar(other)
.expect("SignalSeries scalar division failed")
}
}
impl std::ops::Neg for &SignalSeries {
type Output = SignalSeries;
fn neg(self) -> Self::Output {
self.neg()
}
}
fn union_timestamps_in_range(a: &[f64], b: &[f64], start: f64, end: f64) -> Vec<f64> {
let mut out = Vec::with_capacity(a.len() + b.len() + 2);
out.push(start);
let mut i = a.partition_point(|&t| t < start);
let mut j = b.partition_point(|&t| t < start);
while i < a.len() && j < b.len() {
let ta = a[i];
let tb = b[j];
if ta > end && tb > end {
break;
}
let next = if ta < tb { ta } else { tb };
if next > start && next < end && *out.last().unwrap() != next {
out.push(next);
}
if ta <= tb {
i += 1;
}
if tb <= ta {
j += 1;
}
}
while i < a.len() {
let t = a[i];
if t >= end {
break;
}
if t > start && *out.last().unwrap() != t {
out.push(t);
}
i += 1;
}
while j < b.len() {
let t = b[j];
if t >= end {
break;
}
if t > start && *out.last().unwrap() != t {
out.push(t);
}
j += 1;
}
if *out.last().unwrap() != end {
out.push(end);
}
out
}
fn combine_validity(a: Option<&[bool]>, b: Option<&[bool]>) -> Option<Vec<bool>> {
match (a, b) {
(None, None) => None,
(Some(v), None) => Some(v.to_vec()),
(None, Some(v)) => Some(v.to_vec()),
(Some(va), Some(vb)) => Some(va.iter().zip(vb).map(|(&x, &y)| x || y).collect()),
}
}
pub(crate) fn generate_raster_grid(t_min: f64, t_max: f64, dt: f64) -> Vec<f64> {
if t_min > t_max {
return Vec::new();
}
if (t_max - t_min).abs() < 1e-15 {
return vec![t_min];
}
let eps = dt * 1e-6;
let steps = ((t_max - t_min) / dt + eps).floor() as usize;
(0..=steps).map(|i| t_min + i as f64 * dt).collect()
}
pub(crate) fn slice_values(values: &SignalValues, range: std::ops::Range<usize>) -> SignalValues {
match values {
SignalValues::U8(v) => SignalValues::U8(v[range].to_vec()),
SignalValues::U16(v) => SignalValues::U16(v[range].to_vec()),
SignalValues::U32(v) => SignalValues::U32(v[range].to_vec()),
SignalValues::U64(v) => SignalValues::U64(v[range].to_vec()),
SignalValues::I8(v) => SignalValues::I8(v[range].to_vec()),
SignalValues::I16(v) => SignalValues::I16(v[range].to_vec()),
SignalValues::I32(v) => SignalValues::I32(v[range].to_vec()),
SignalValues::I64(v) => SignalValues::I64(v[range].to_vec()),
SignalValues::F32(v) => SignalValues::F32(v[range].to_vec()),
SignalValues::F64(v) => SignalValues::F64(v[range].to_vec()),
SignalValues::Str(v) => SignalValues::Str(v[range].to_vec()),
SignalValues::Bytes { data, width } => {
let start = range.start * *width;
let end = range.end * *width;
SignalValues::Bytes {
data: data[start..end].to_vec(),
width: *width,
}
}
SignalValues::VarBytes { data, starts } => {
if range.start >= range.end {
return SignalValues::VarBytes {
data: Vec::new(),
starts: vec![0],
};
}
let byte_start = starts[range.start];
let byte_end = starts[range.end];
let new_starts: Vec<usize> = starts[range.start..=range.end]
.iter()
.map(|&s| s - byte_start)
.collect();
SignalValues::VarBytes {
data: data[byte_start..byte_end].to_vec(),
starts: new_starts,
}
}
SignalValues::Complex { re, im } => SignalValues::Complex {
re: re[range.clone()].to_vec(),
im: im[range].to_vec(),
},
SignalValues::CanopenDate(v) => SignalValues::CanopenDate(v[range].to_vec()),
SignalValues::CanopenTime(v) => SignalValues::CanopenTime(v[range].to_vec()),
SignalValues::Array {
values: v,
elements_per_sample,
} => {
let start = range.start * *elements_per_sample;
let end = range.end * *elements_per_sample;
SignalValues::Array {
values: v[start..end].to_vec(),
elements_per_sample: *elements_per_sample,
}
}
SignalValues::ArrayVarLen { values: v, starts } => {
if range.start >= range.end {
return SignalValues::ArrayVarLen {
values: Vec::new(),
starts: vec![0],
};
}
let val_start = starts[range.start];
let val_end = starts[range.end];
let new_starts: Vec<usize> = starts[range.start..=range.end]
.iter()
.map(|&s| s - val_start)
.collect();
SignalValues::ArrayVarLen {
values: v[val_start..val_end].to_vec(),
starts: new_starts,
}
}
}
}
fn step_hold_index(src_t: &[f64], t: f64) -> usize {
if src_t.is_empty() {
return 0;
}
let idx = src_t.partition_point(|&st| st <= t);
if idx == 0 {
0
} else {
idx - 1
}
}
struct LinearWeights {
i0: usize,
i1: usize,
alpha: f64,
}
fn linear_weights(src_t: &[f64], t: f64) -> LinearWeights {
let n = src_t.len();
if n == 0 {
return LinearWeights {
i0: 0,
i1: 0,
alpha: 0.0,
};
}
if n == 1 || t <= src_t[0] {
return LinearWeights {
i0: 0,
i1: 0,
alpha: 0.0,
};
}
if t >= src_t[n - 1] {
return LinearWeights {
i0: n - 1,
i1: n - 1,
alpha: 0.0,
};
}
let idx = src_t.partition_point(|&st| st <= t);
let i0 = idx - 1;
let i1 = idx;
let dt = src_t[i1] - src_t[i0];
let alpha = if dt.abs() < 1e-15 {
0.0
} else {
(t - src_t[i0]) / dt
};
LinearWeights { i0, i1, alpha }
}
pub(crate) fn resample_values(
values: &SignalValues,
src_t: &[f64],
target_t: &[f64],
mode: InterpolationMode,
) -> SignalValues {
let m = target_t.len();
if src_t.is_empty() || m == 0 {
return slice_values(values, 0..0);
}
match mode {
InterpolationMode::StepHold => resample_values_step_hold(values, src_t, target_t),
InterpolationMode::Linear => {
match values {
SignalValues::F64(v) => {
let mut out = Vec::with_capacity(m);
for &t in target_t {
let w = linear_weights(src_t, t);
let val = v[w.i0] * (1.0 - w.alpha) + v[w.i1] * w.alpha;
out.push(val);
}
SignalValues::F64(out)
}
SignalValues::F32(v) => {
let mut out = Vec::with_capacity(m);
for &t in target_t {
let w = linear_weights(src_t, t);
let val =
(v[w.i0] as f64 * (1.0 - w.alpha) + v[w.i1] as f64 * w.alpha) as f32;
out.push(val);
}
SignalValues::F32(out)
}
_ => resample_values_step_hold(values, src_t, target_t),
}
}
}
}
fn resample_values_step_hold(
values: &SignalValues,
src_t: &[f64],
target_t: &[f64],
) -> SignalValues {
let m = target_t.len();
let indices: Vec<usize> = target_t
.iter()
.map(|&t| step_hold_index(src_t, t))
.collect();
match values {
SignalValues::U8(v) => SignalValues::U8(indices.iter().map(|&k| v[k]).collect()),
SignalValues::U16(v) => SignalValues::U16(indices.iter().map(|&k| v[k]).collect()),
SignalValues::U32(v) => SignalValues::U32(indices.iter().map(|&k| v[k]).collect()),
SignalValues::U64(v) => SignalValues::U64(indices.iter().map(|&k| v[k]).collect()),
SignalValues::I8(v) => SignalValues::I8(indices.iter().map(|&k| v[k]).collect()),
SignalValues::I16(v) => SignalValues::I16(indices.iter().map(|&k| v[k]).collect()),
SignalValues::I32(v) => SignalValues::I32(indices.iter().map(|&k| v[k]).collect()),
SignalValues::I64(v) => SignalValues::I64(indices.iter().map(|&k| v[k]).collect()),
SignalValues::F32(v) => SignalValues::F32(indices.iter().map(|&k| v[k]).collect()),
SignalValues::F64(v) => SignalValues::F64(indices.iter().map(|&k| v[k]).collect()),
SignalValues::Str(v) => SignalValues::Str(indices.iter().map(|&k| v[k].clone()).collect()),
SignalValues::Bytes { data, width } => {
let mut out = Vec::with_capacity(m * *width);
for &k in &indices {
let start = k * *width;
let end = start + *width;
out.extend_from_slice(&data[start..end]);
}
SignalValues::Bytes {
data: out,
width: *width,
}
}
SignalValues::VarBytes { data, starts } => {
let mut out_data = Vec::new();
let mut out_starts = Vec::with_capacity(m + 1);
out_starts.push(0);
for &k in &indices {
let s_start = starts[k];
let s_end = starts[k + 1];
out_data.extend_from_slice(&data[s_start..s_end]);
out_starts.push(out_data.len());
}
SignalValues::VarBytes {
data: out_data,
starts: out_starts,
}
}
SignalValues::Complex { re, im } => SignalValues::Complex {
re: indices.iter().map(|&k| re[k]).collect(),
im: indices.iter().map(|&k| im[k]).collect(),
},
SignalValues::CanopenDate(v) => {
SignalValues::CanopenDate(indices.iter().map(|&k| v[k]).collect())
}
SignalValues::CanopenTime(v) => {
SignalValues::CanopenTime(indices.iter().map(|&k| v[k]).collect())
}
SignalValues::Array {
values: v,
elements_per_sample,
} => {
let mut out = Vec::with_capacity(m * *elements_per_sample);
for &k in &indices {
let start = k * *elements_per_sample;
let end = start + *elements_per_sample;
out.extend_from_slice(&v[start..end]);
}
SignalValues::Array {
values: out,
elements_per_sample: *elements_per_sample,
}
}
SignalValues::ArrayVarLen { values: v, starts } => {
let mut out_data = Vec::new();
let mut out_starts = Vec::with_capacity(m + 1);
out_starts.push(0);
for &k in &indices {
let s_start = starts[k];
let s_end = starts[k + 1];
out_data.extend_from_slice(&v[s_start..s_end]);
out_starts.push(out_data.len());
}
SignalValues::ArrayVarLen {
values: out_data,
starts: out_starts,
}
}
}
}
pub(crate) fn resample_validity(
validity: Option<&[bool]>,
src_t: &[f64],
target_t: &[f64],
) -> Option<Vec<bool>> {
let v = validity?;
if src_t.is_empty() || target_t.is_empty() {
return Some(Vec::new());
}
let resampled = target_t
.iter()
.map(|&t| {
let idx = step_hold_index(src_t, t);
v.get(idx).copied().unwrap_or(true)
})
.collect();
Some(resampled)
}