use crate::matrix::Matrix;
use crate::windows::hann;
pub struct PathEnhanceParams {
pub n: usize,
pub max_ratio: f64,
pub min_ratio: Option<f64>,
pub n_filters: usize,
pub zero_mean: bool,
pub clip: bool,
}
impl Default for PathEnhanceParams {
fn default() -> Self {
Self {
n: 51,
max_ratio: 2.0,
min_ratio: None,
n_filters: 7,
zero_mean: false,
clip: true,
}
}
}
pub fn path_enhance(rec: &Matrix, params: &PathEnhanceParams) -> Matrix {
let t = rec.rows();
assert_eq!(t, rec.cols(), "recurrence matrix must be square");
let min_ratio = params.min_ratio.unwrap_or(1.0 / params.max_ratio);
let max_ratio = params.max_ratio;
let n_filters = params.n_filters;
let ratios: Vec<f64> = if n_filters == 1 {
vec![(min_ratio * max_ratio).sqrt()]
} else {
let log_min = min_ratio.ln();
let log_max = max_ratio.ln();
(0..n_filters)
.map(|i| {
let t = i as f64 / (n_filters - 1) as f64;
(log_min + t * (log_max - log_min)).exp()
})
.collect()
};
let window = hann(params.n, false);
let mut output = vec![f64::NEG_INFINITY; t * t];
for &ratio in &ratios {
let kernel = diagonal_filter(&window, ratio, params.zero_mean);
let convolved = convolve2d_reflect(rec, &kernel);
for idx in 0..t * t {
if convolved[idx] > output[idx] {
output[idx] = convolved[idx];
}
}
}
for v in output.iter_mut() {
if v.is_infinite() && *v < 0.0 {
*v = 0.0;
}
}
if params.clip {
for v in output.iter_mut() {
if *v < 0.0 {
*v = 0.0;
}
}
}
Matrix::from_vec(output, t, t)
}
fn diagonal_filter(window: &[f64], ratio: f64, zero_mean: bool) -> (Vec<f64>, usize, usize) {
let n = window.len();
let mut row_offsets: Vec<isize> = (0..n)
.map(|k| (k as f64 * ratio).round() as isize)
.collect();
let col_offsets: Vec<isize> = (0..n).map(|k| k as isize).collect();
let min_row = *row_offsets.iter().min().unwrap();
for r in row_offsets.iter_mut() {
*r -= min_row;
}
let max_row = *row_offsets.iter().max().unwrap() as usize;
let max_col = *col_offsets.iter().max().unwrap() as usize;
let height = max_row + 1;
let width = max_col + 1;
let mut kernel = vec![0.0f64; height * width];
for k in 0..n {
let r = row_offsets[k] as usize;
let c = col_offsets[k] as usize;
kernel[r * width + c] = window[k];
}
if zero_mean {
let mean: f64 = kernel.iter().sum::<f64>() / (height * width) as f64;
for v in kernel.iter_mut() {
*v -= mean;
}
}
(kernel, height, width)
}
fn convolve2d_reflect(rec: &Matrix, kernel_tuple: &(Vec<f64>, usize, usize)) -> Vec<f64> {
let (kernel, kh, kw) = kernel_tuple;
let t = rec.rows();
let ph = kh / 2; let pw = kw / 2;
let mut result = vec![0.0f64; t * t];
for r in 0..t {
for c in 0..t {
let mut sum = 0.0f64;
for kr in 0..*kh {
for kc in 0..*kw {
let sr = reflect_index(r as isize + kr as isize - ph as isize, t);
let sc = reflect_index(c as isize + kc as isize - pw as isize, t);
sum += rec.get(sr, sc) * kernel[kr * kw + kc];
}
}
result[r * t + c] = sum;
}
}
result
}
fn reflect_index(i: isize, n: usize) -> usize {
if n == 0 {
return 0;
}
let n = n as isize;
let mut i = i;
if i < 0 {
i = -i - 1;
}
if i >= n {
i = 2 * n - i - 1;
}
i.clamp(0, n - 1) as usize
}