Skip to content

Commit c2e4e93

Browse files
committed
Add triangle window
1 parent 7b743b3 commit c2e4e93

File tree

2 files changed

+206
-1
lines changed

2 files changed

+206
-1
lines changed

sci-rs/src/signal/windows/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ fn truncate<'a, W>(mut w: Vec<W>, needed: bool) -> Vec<W> {
5757
}
5858

5959
mod boxcar;
60+
mod triangle;
6061
pub use boxcar::Boxcar;
62+
pub use triangle::Triangle;
6163

6264
/// todo
6365
// Ordering is as in accordance with
@@ -66,7 +68,8 @@ pub enum Window {
6668
/// Boxcar window, also known as a rectangular window or Dirichlet window; This is equivalent
6769
/// to no window at all.
6870
Boxcar(Boxcar),
69-
// Triangle,
71+
/// Triangle window.
72+
Triangle(Triangle),
7073
// Blackman,
7174
// Hamming,
7275
// Hann,
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
use super::{extend, len_guard, truncate};
2+
use num_traits::{real::Real, Float};
3+
4+
#[cfg(feature = "alloc")]
5+
use super::GetWindow;
6+
#[cfg(feature = "alloc")]
7+
use alloc::{vec, vec::Vec};
8+
9+
/// Collection of arguments for window `Triangle` for use in [GetWindow].
10+
#[derive(Debug, Clone, PartialEq)]
11+
pub struct Triangle {
12+
/// Number of points in the output window. If zero, an empty array is returned in [GetWindow].
13+
pub m: usize,
14+
/// Whether the window is symmetric.
15+
///
16+
/// When true, generates a symmetric window, for use in filter design.
17+
/// When false, generates a periodic window, for use in spectral analysis.
18+
pub sym: bool,
19+
}
20+
21+
impl Triangle {
22+
/// Returns a Triangle struct.
23+
///
24+
/// # Parameters
25+
/// * `m`:
26+
/// Number of points in the output window. If zero, an empty array is returned.
27+
/// * `sym`:
28+
/// When true, generates a symmetric window, for use in filter design.
29+
/// When false, generates a periodic window, for use in spectral analysis.
30+
pub fn new(m: usize, sym: bool) -> Self {
31+
Triangle { m, sym }
32+
}
33+
}
34+
35+
#[cfg(feature = "alloc")]
36+
impl GetWindow for Triangle {
37+
/// Return a window of type: Triangle.
38+
///
39+
/// This is the not the same as the Bartlett window.
40+
///
41+
/// # Parameters
42+
/// `self`: [Triangle]
43+
///
44+
/// # Returns
45+
/// `w`: `vec<F>`
46+
/// The window, with the maximum value normalized to 1 (though the value 1 does not appear
47+
/// if `M` is even and `sym` is True).
48+
///
49+
/// # Example
50+
/// ```
51+
/// use sci_rs::signal::windows::{Triangle, GetWindow};
52+
///
53+
/// let nx = 8;
54+
/// let tri = Triangle::new(nx, true);
55+
/// assert_eq!(vec![0.125, 0.375, 0.625, 0.875, 0.875, 0.625, 0.375, 0.125], tri.get_window::<f32>());
56+
/// assert_eq!(vec![0.125, 0.375, 0.625, 0.875, 0.875, 0.625, 0.375, 0.125], tri.get_window());
57+
///
58+
/// let nx = 9;
59+
/// let tri = Triangle::new(nx, false);
60+
/// assert_eq!(vec![0.1, 0.3, 0.5, 0.7, 0.9, 0.9, 0.7, 0.5, 0.3], tri.get_window::<f32>());
61+
/// assert_eq!(vec![0.1, 0.3, 0.5, 0.7, 0.9, 0.9, 0.7, 0.5, 0.3], tri.get_window());
62+
/// ```
63+
///
64+
/// # References
65+
/// <https://en.wikipedia.org/wiki/Window_function#Triangular_window>
66+
#[cfg(feature = "alloc")]
67+
fn get_window<F>(&self) -> Vec<F>
68+
where
69+
F: Real + Float,
70+
{
71+
if len_guard(self.m) {
72+
return Vec::<F>::new();
73+
}
74+
let (m, needs_trunc) = extend(self.m, self.sym);
75+
76+
let mut n: Vec<F> = (1..=((m + 1) / 2)).map(|x| F::from(x).unwrap()).collect();
77+
let m_f: F = F::from(m).unwrap();
78+
let w: Vec<F> = match m % 2 {
79+
0 => {
80+
let mut w: Vec<F> = n
81+
.iter()
82+
.map(|&n| (F::from(2).unwrap() * n - F::from(1).unwrap()) / m_f)
83+
.collect();
84+
w.extend(w.clone().iter().rev());
85+
w
86+
}
87+
1 => {
88+
let mut w: Vec<F> = n
89+
.iter()
90+
.map(|&n| F::from(2).unwrap() * n / (m_f + F::from(1).unwrap()))
91+
.collect();
92+
w.extend(w.clone().iter().rev().skip(1));
93+
w
94+
}
95+
_ => panic!(),
96+
};
97+
98+
return truncate(w, needs_trunc); // this works even though w isn't mut?
99+
}
100+
}
101+
102+
#[cfg(test)]
103+
mod tests {
104+
use super::*;
105+
use approx::assert_relative_eq;
106+
107+
#[test]
108+
#[cfg(feature = "alloc")]
109+
fn case_even_true() {
110+
// from scipy.signal.windows import triangle
111+
// triangle(n)
112+
113+
let upper = 1_000;
114+
for i in 0..upper {
115+
let nx = 2 * i;
116+
let tri = Triangle::new(nx, true);
117+
let expected: Vec<f64> = (0..nx)
118+
.into_iter()
119+
.chain((0..nx).rev())
120+
.filter(|n| n % 2 == 1)
121+
.map(|n| n as f64 / nx as f64)
122+
.collect();
123+
124+
assert_eq!(expected, tri.get_window::<f64>());
125+
// for (&e, t) in expected.iter().zip(tri.get_window::<f64>()) {
126+
// assert_relative_eq!(e, t, max_relative = 1e-7)
127+
// }
128+
}
129+
}
130+
131+
#[test]
132+
#[cfg(feature = "alloc")]
133+
fn case_even_false() {
134+
// from scipy.signal import get_window
135+
// get_window('triangle', 4)
136+
137+
let upper = 1_000;
138+
for i in 0..upper {
139+
let nx = 2 * i;
140+
let tri = Triangle::new(nx, false);
141+
let expected: Vec<f64> = (1..=(nx / 2) + 1)
142+
.into_iter()
143+
.chain((1..(nx / 2) + 1).rev())
144+
.take(nx)
145+
.map(|n| n as f64 / ((nx / 2) + 1) as f64)
146+
.collect();
147+
148+
assert_eq!(expected, tri.get_window::<f64>());
149+
// for (&e, t) in expected.iter().zip(tri.get_window::<f64>()) {
150+
// assert_relative_eq!(e, t, max_relative = 1e-7)
151+
// }
152+
}
153+
}
154+
155+
#[test]
156+
#[cfg(feature = "alloc")]
157+
fn case_odd_true() {
158+
// from scipy.signal.windows import triangle
159+
// triangle(nx)
160+
161+
let upper = 1_000;
162+
for i in 1..upper {
163+
let nx = 2 * i + 1;
164+
let tri = Triangle::new(nx, true);
165+
let expected: Vec<_> = (1..=(nx + 1) / 2)
166+
.into_iter()
167+
.chain((1..=(nx + 1) / 2).rev().skip(1))
168+
.map(|n| (n as f64) / ((nx + 1) as f64 / 2.))
169+
.collect();
170+
171+
assert_eq!(expected, tri.get_window::<f64>());
172+
// for (&e, t) in expected.iter().zip(tri.get_window::<f64>()) {
173+
// assert_relative_eq!(e, t);
174+
// }
175+
}
176+
}
177+
178+
#[test]
179+
#[cfg(feature = "alloc")]
180+
fn case_odd_false() {
181+
// from scipy.signal import get_window
182+
// get_window('triangle', 5)
183+
184+
let upper = 1_000;
185+
for i in 1..upper {
186+
let nx = 2 * i + 1;
187+
let tri = Triangle::new(nx, false);
188+
let expected: Vec<_> = (0..=nx)
189+
.into_iter()
190+
.chain((0..=nx).into_iter().rev())
191+
.filter(|n| n % 2 == 1)
192+
.take(nx)
193+
.map(|n| n as f64 / (nx as f64 + 1.))
194+
.collect();
195+
196+
assert_eq!(expected, tri.get_window::<f64>());
197+
// for (&e, t) in expected.iter().zip(tri.get_window::<f64>()) {
198+
// assert_relative_eq!(e, t);
199+
// }
200+
}
201+
}
202+
}

0 commit comments

Comments
 (0)