use crate::error::{CvError, CvResult};
use oximedia_codec::VideoFrame;
#[derive(Debug, Clone, PartialEq)]
pub struct TemporalInfo {
pub ti: f64,
pub tpi: f64,
pub std_dev: f64,
pub max_diff: f64,
pub mean_diff: f64,
}
impl TemporalInfo {
#[must_use]
pub fn new() -> Self {
Self {
ti: 0.0,
tpi: 0.0,
std_dev: 0.0,
max_diff: 0.0,
mean_diff: 0.0,
}
}
#[must_use]
pub fn is_high_motion(&self) -> bool {
self.ti > 50.0
}
#[must_use]
pub fn is_static(&self) -> bool {
self.ti < 10.0
}
}
impl Default for TemporalInfo {
fn default() -> Self {
Self::new()
}
}
pub fn calculate_temporal_info(frame: &VideoFrame) -> CvResult<TemporalInfo> {
if frame.planes.is_empty() {
return Err(CvError::insufficient_data(1, 0));
}
let plane = &frame.planes[0];
let width = frame.width as usize;
let height = frame.height as usize;
let stride = plane.stride;
let mut differences = Vec::new();
let mut sum_diff = 0.0;
let mut max_diff: f64 = 0.0;
for y in 1..height {
for x in 0..width {
let idx = y * stride + x;
let prev_idx = (y - 1) * stride + x;
if idx < plane.data.len() && prev_idx < plane.data.len() {
let diff = (i32::from(plane.data[idx]) - i32::from(plane.data[prev_idx])).abs();
let diff_f = diff as f64;
differences.push(diff_f);
sum_diff += diff_f;
max_diff = max_diff.max(diff_f);
}
}
}
let count = differences.len() as f64;
if count == 0.0 {
return Ok(TemporalInfo::new());
}
let mean_diff = sum_diff / count;
let variance = differences
.iter()
.map(|&d| (d - mean_diff) * (d - mean_diff))
.sum::<f64>()
/ count;
let std_dev = variance.sqrt();
let ti = std_dev;
let spatial_activity = calculate_spatial_activity(frame)?;
let tpi = ti * (1.0 + spatial_activity / 100.0);
Ok(TemporalInfo {
ti,
tpi,
std_dev,
max_diff,
mean_diff,
})
}
pub fn calculate_temporal_metrics(
frame1: &VideoFrame,
frame2: &VideoFrame,
) -> CvResult<TemporalMetrics> {
validate_frames(frame1, frame2)?;
let plane1 = &frame1.planes[0];
let plane2 = &frame2.planes[0];
let width = frame1.width as usize;
let height = frame1.height as usize;
let stride = plane1.stride;
let mut differences = Vec::new();
let mut sum_diff = 0.0;
let mut sum_abs_diff = 0.0;
let mut max_diff: f64 = 0.0;
for y in 0..height {
for x in 0..width {
let idx = y * stride + x;
if idx < plane1.data.len() && idx < plane2.data.len() {
let diff = i32::from(plane1.data[idx]) - i32::from(plane2.data[idx]);
let abs_diff = diff.abs() as f64;
differences.push(diff as f64);
sum_diff += diff as f64;
sum_abs_diff += abs_diff;
max_diff = max_diff.max(abs_diff);
}
}
}
let count = differences.len() as f64;
if count == 0.0 {
return Ok(TemporalMetrics::new());
}
let mean_diff = sum_diff / count;
let mean_abs_diff = sum_abs_diff / count;
let variance = differences
.iter()
.map(|&d| (d - mean_diff) * (d - mean_diff))
.sum::<f64>()
/ count;
let std_dev = variance.sqrt();
let coherence = 1.0 - (mean_abs_diff / 255.0).clamp(0.0, 1.0);
let flicker = if mean_abs_diff < 10.0 && std_dev > 5.0 {
(std_dev / 50.0).clamp(0.0, 1.0)
} else {
0.0
};
let motion = (mean_abs_diff / 128.0).clamp(0.0, 1.0);
let judder = detect_judder(&differences)?;
Ok(TemporalMetrics {
coherence,
flicker,
motion,
judder,
mean_diff,
std_dev,
max_diff,
})
}
#[derive(Debug, Clone, PartialEq)]
pub struct TemporalMetrics {
pub coherence: f64,
pub flicker: f64,
pub motion: f64,
pub judder: f64,
pub mean_diff: f64,
pub std_dev: f64,
pub max_diff: f64,
}
impl TemporalMetrics {
#[must_use]
pub fn new() -> Self {
Self {
coherence: 1.0,
flicker: 0.0,
motion: 0.0,
judder: 0.0,
mean_diff: 0.0,
std_dev: 0.0,
max_diff: 0.0,
}
}
#[must_use]
pub fn is_good_quality(&self) -> bool {
self.coherence > 0.9 && self.flicker < 0.1 && self.judder < 0.1
}
#[must_use]
pub fn has_artifacts(&self) -> bool {
self.flicker > 0.3 || self.judder > 0.3
}
}
impl Default for TemporalMetrics {
fn default() -> Self {
Self::new()
}
}
fn detect_judder(differences: &[f64]) -> CvResult<f64> {
if differences.len() < 3 {
return Ok(0.0);
}
let mut accelerations = Vec::new();
for i in 1..differences.len() - 1 {
let accel = differences[i + 1] - 2.0 * differences[i] + differences[i - 1];
accelerations.push(accel.abs());
}
let mean_accel = accelerations.iter().sum::<f64>() / accelerations.len() as f64;
let variance = accelerations
.iter()
.map(|&a| (a - mean_accel) * (a - mean_accel))
.sum::<f64>()
/ accelerations.len() as f64;
let judder = (variance.sqrt() / 100.0).clamp(0.0, 1.0);
Ok(judder)
}
fn calculate_spatial_activity(frame: &VideoFrame) -> CvResult<f64> {
if frame.planes.is_empty() {
return Ok(0.0);
}
let plane = &frame.planes[0];
let width = frame.width as usize;
let height = frame.height as usize;
let stride = plane.stride;
let mut gradient_sum = 0.0;
let mut count = 0;
for y in 1..height - 1 {
for x in 1..width - 1 {
let grad = calculate_gradient_magnitude(&plane.data, x, y, stride);
gradient_sum += grad;
count += 1;
}
}
if count == 0 {
return Ok(0.0);
}
Ok(gradient_sum / count as f64)
}
fn calculate_gradient_magnitude(data: &[u8], x: usize, y: usize, stride: usize) -> f64 {
let get_pixel = |dx: isize, dy: isize| -> f64 {
let px = (x as isize + dx) as usize;
let py = (y as isize + dy) as usize;
let idx = py * stride + px;
if idx < data.len() {
f64::from(data[idx])
} else {
0.0
}
};
let gx = -get_pixel(-1, -1)
+ 1.0 * get_pixel(1, -1)
+ -2.0 * get_pixel(-1, 0)
+ 2.0 * get_pixel(1, 0)
+ -get_pixel(-1, 1)
+ 1.0 * get_pixel(1, 1);
let gy = -get_pixel(-1, -1)
+ -2.0 * get_pixel(0, -1)
+ -get_pixel(1, -1)
+ 1.0 * get_pixel(-1, 1)
+ 2.0 * get_pixel(0, 1)
+ 1.0 * get_pixel(1, 1);
(gx * gx + gy * gy).sqrt()
}
pub fn calculate_temporal_stability(frames: &[VideoFrame]) -> CvResult<f64> {
if frames.len() < 2 {
return Err(CvError::invalid_parameter(
"frame_count",
"Need at least 2 frames for temporal stability",
));
}
let mut coherence_scores = Vec::new();
for i in 0..frames.len() - 1 {
let metrics = calculate_temporal_metrics(&frames[i], &frames[i + 1])?;
coherence_scores.push(metrics.coherence);
}
let mean_coherence = coherence_scores.iter().sum::<f64>() / coherence_scores.len() as f64;
let variance = coherence_scores
.iter()
.map(|&c| (c - mean_coherence) * (c - mean_coherence))
.sum::<f64>()
/ coherence_scores.len() as f64;
let stability = mean_coherence * (1.0 - variance).max(0.0);
Ok(stability.clamp(0.0, 1.0))
}
pub fn detect_scene_changes(frames: &[VideoFrame], threshold: f64) -> CvResult<Vec<usize>> {
if frames.len() < 2 {
return Ok(Vec::new());
}
let mut scene_changes = Vec::new();
for i in 0..frames.len() - 1 {
let metrics = calculate_temporal_metrics(&frames[i], &frames[i + 1])?;
if metrics.coherence < threshold {
scene_changes.push(i + 1);
}
}
Ok(scene_changes)
}
pub fn analyze_flicker_sequence(frames: &[VideoFrame]) -> CvResult<(f64, f64)> {
if frames.len() < 3 {
return Ok((0.0, 0.0));
}
let mut flicker_scores = Vec::new();
for i in 0..frames.len() - 1 {
let metrics = calculate_temporal_metrics(&frames[i], &frames[i + 1])?;
flicker_scores.push(metrics.flicker);
}
let avg_flicker = flicker_scores.iter().sum::<f64>() / flicker_scores.len() as f64;
let max_flicker = flicker_scores.iter().copied().fold(0.0_f64, f64::max);
Ok((avg_flicker, max_flicker))
}
fn validate_frames(frame1: &VideoFrame, frame2: &VideoFrame) -> CvResult<()> {
if frame1.width != frame2.width || frame1.height != frame2.height {
return Err(CvError::invalid_parameter(
"dimensions",
format!(
"{}x{} vs {}x{}",
frame1.width, frame1.height, frame2.width, frame2.height
),
));
}
if frame1.format != frame2.format {
return Err(CvError::invalid_parameter(
"pixel_format",
"Frames must have the same pixel format",
));
}
if frame1.planes.is_empty() || frame2.planes.is_empty() {
return Err(CvError::insufficient_data(1, 0));
}
Ok(())
}
pub fn calculate_motion_vectors(
frame1: &VideoFrame,
frame2: &VideoFrame,
) -> CvResult<Vec<Vec<f64>>> {
validate_frames(frame1, frame2)?;
let plane1 = &frame1.planes[0];
let plane2 = &frame2.planes[0];
let width = frame1.width as usize;
let height = frame1.height as usize;
const BLOCK_SIZE: usize = 16;
let blocks_x = width / BLOCK_SIZE;
let blocks_y = height / BLOCK_SIZE;
let mut motion_map = vec![vec![0.0; blocks_x]; blocks_y];
for by in 0..blocks_y {
for bx in 0..blocks_x {
let sad = calculate_block_sad(
&plane1.data,
&plane2.data,
bx * BLOCK_SIZE,
by * BLOCK_SIZE,
BLOCK_SIZE,
plane1.stride,
);
motion_map[by][bx] = (sad / (BLOCK_SIZE * BLOCK_SIZE) as f64).min(255.0);
}
}
Ok(motion_map)
}
fn calculate_block_sad(
data1: &[u8],
data2: &[u8],
x: usize,
y: usize,
block_size: usize,
stride: usize,
) -> f64 {
let mut sad = 0.0;
for dy in 0..block_size {
for dx in 0..block_size {
let idx = (y + dy) * stride + (x + dx);
if idx < data1.len() && idx < data2.len() {
let diff = (i32::from(data1[idx]) - i32::from(data2[idx])).abs();
sad += diff as f64;
}
}
}
sad
}