add ClPotentials derivation from LSV cathodic peaks
This commit is contained in:
parent
01edb88e0b
commit
c6bbaa5bc4
|
|
@ -91,6 +91,69 @@ pub fn detect_qhq_peak(points: &[LsvPoint]) -> Option<f32> {
|
||||||
.map(|&(idx, _)| v_vals[idx])
|
.map(|&(idx, _)| v_vals[idx])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct ClPotentials {
|
||||||
|
pub v_free: f32,
|
||||||
|
pub v_free_detected: bool,
|
||||||
|
pub v_total: f32,
|
||||||
|
pub v_total_detected: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn derive_cl_potentials(points: &[LsvPoint]) -> ClPotentials {
|
||||||
|
let default = ClPotentials {
|
||||||
|
v_free: 100.0,
|
||||||
|
v_free_detected: false,
|
||||||
|
v_total: -200.0,
|
||||||
|
v_total_detected: false,
|
||||||
|
};
|
||||||
|
if points.len() < 5 {
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
let i_vals: Vec<f32> = points.iter().map(|p| p.i_ua).collect();
|
||||||
|
let v_vals: Vec<f32> = points.iter().map(|p| p.v_mv).collect();
|
||||||
|
|
||||||
|
let window = 5.max(points.len() / 50);
|
||||||
|
let smoothed = smooth(&i_vals, window);
|
||||||
|
|
||||||
|
let i_min = smoothed.iter().copied().fold(f32::INFINITY, f32::min);
|
||||||
|
let i_max = smoothed.iter().copied().fold(f32::NEG_INFINITY, f32::max);
|
||||||
|
let prominence = (i_max - i_min) * 0.05;
|
||||||
|
|
||||||
|
let extrema = find_extrema(&v_vals, &smoothed, prominence);
|
||||||
|
|
||||||
|
// v_free: most prominent cathodic peak (is_max==false) in +300 to -300 mV
|
||||||
|
let free_peak = extrema.iter()
|
||||||
|
.filter(|&&(idx, is_max)| !is_max && v_vals[idx] >= -300.0 && v_vals[idx] <= 300.0)
|
||||||
|
.min_by(|a, b| smoothed[a.0].partial_cmp(&smoothed[b.0]).unwrap_or(std::cmp::Ordering::Equal));
|
||||||
|
|
||||||
|
let (v_free, v_free_detected, free_idx) = match free_peak {
|
||||||
|
Some(&(idx, _)) => (v_vals[idx], true, Some(idx)),
|
||||||
|
None => (100.0, false, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
// v_total: secondary cathodic peak between (v_free - 100) and -500 mV, excluding free peak
|
||||||
|
let total_lo = -500.0_f32;
|
||||||
|
let total_hi = v_free - 100.0;
|
||||||
|
let total_peak = extrema.iter()
|
||||||
|
.filter(|&&(idx, is_max)| {
|
||||||
|
!is_max
|
||||||
|
&& v_vals[idx] >= total_lo
|
||||||
|
&& v_vals[idx] <= total_hi
|
||||||
|
&& Some(idx) != free_idx
|
||||||
|
})
|
||||||
|
.min_by(|a, b| smoothed[a.0].partial_cmp(&smoothed[b.0]).unwrap_or(std::cmp::Ordering::Equal));
|
||||||
|
|
||||||
|
let (v_total, v_total_detected) = match total_peak {
|
||||||
|
Some(&(idx, _)) => (v_vals[idx], true),
|
||||||
|
None => (v_free - 300.0, false),
|
||||||
|
};
|
||||||
|
|
||||||
|
let v_total = v_total.max(-400.0);
|
||||||
|
|
||||||
|
ClPotentials { v_free, v_free_detected, v_total, v_total_detected }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn detect_peaks(points: &[LsvPoint]) -> Vec<LsvPeak> {
|
pub fn detect_peaks(points: &[LsvPoint]) -> Vec<LsvPeak> {
|
||||||
if points.len() < 5 {
|
if points.len() < 5 {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue