extern crate csv;
#[cfg(feature = "png")]
extern crate png;
mod builder;
mod colour_gradient;
mod errors;
mod freq_scales;
mod spec_core;
mod window_fn;
pub use builder::SpecOptionsBuilder;
pub use colour_gradient::{ColourGradient, ColourTheme, RGBAColour};
pub use errors::SonogramError;
pub use freq_scales::{FreqScaler, FrequencyScale};
use num_traits::NumCast;
pub use spec_core::SpecCompute;
pub use window_fn::*;
#[cfg(feature = "png")]
use std::fs::File;
#[cfg(feature = "png")]
use std::io::BufWriter;
use std::path::Path;
use resize::Pixel::GrayF32;
use resize::Type::Lanczos3;
use rgb::FromSlice;
#[cfg(feature = "png")]
use png::HasParameters;
#[cfg(feature = "rayon")]
use rayon::prelude::*;
pub struct Spectrogram {
spec: Vec<f32>,
width: usize,
height: usize,
}
impl Spectrogram {
pub fn from_raw<T: NumCast + Copy>(
buf: &[T],
width: usize,
height: usize,
) -> Result<Spectrogram, SonogramError> {
if buf.len() != width * height {
return Err(SonogramError::InvalidRawDataSize);
}
let spec = Spectrogram {
spec: buf.iter().map(|&x| NumCast::from(x).unwrap()).collect(),
width,
height,
};
Ok(spec)
}
#[cfg(feature = "png")]
pub fn to_png(
&mut self,
fname: &Path,
freq_scale: FrequencyScale,
gradient: &mut ColourGradient,
w_img: usize,
h_img: usize,
) -> Result<(), std::io::Error> {
let buf = self.to_buffer(freq_scale, w_img, h_img);
let mut img: Vec<u8> = vec![0u8; w_img * h_img * 4];
self.buf_to_img(&buf, &mut img, gradient);
let file = File::create(fname)?;
let w = &mut BufWriter::new(file);
let mut encoder = png::Encoder::new(w, w_img as u32, h_img as u32);
encoder.set(png::ColorType::RGBA).set(png::BitDepth::Eight);
let mut writer = encoder.write_header()?;
writer.write_image_data(&img)?;
Ok(())
}
#[cfg(feature = "png")]
pub fn to_png_in_memory(
&mut self,
freq_scale: FrequencyScale,
gradient: &mut ColourGradient,
w_img: usize,
h_img: usize,
) -> Result<Vec<u8>, std::io::Error> {
let buf = self.to_buffer(freq_scale, w_img, h_img);
let mut img: Vec<u8> = vec![0u8; w_img * h_img * 4];
self.buf_to_img(&buf, &mut img, gradient);
let mut pngbuf: Vec<u8> = Vec::new();
let mut encoder = png::Encoder::new(&mut pngbuf, w_img as u32, h_img as u32);
encoder.set(png::ColorType::RGBA).set(png::BitDepth::Eight);
let mut writer = encoder.write_header()?;
writer.write_image_data(&img)?;
drop(writer);
Ok(pngbuf)
}
pub fn to_rgba_in_memory(
&mut self,
freq_scale: FrequencyScale,
gradient: &mut ColourGradient,
w_img: usize,
h_img: usize,
) -> Vec<u8> {
let buf = self.to_buffer(freq_scale, w_img, h_img);
let mut img: Vec<u8> = vec![0u8; w_img * h_img * 4];
self.buf_to_img(&buf, &mut img, gradient);
img
}
fn buf_to_img(&self, buf: &[f32], img: &mut [u8], gradient: &mut ColourGradient) {
let (min, max) = get_min_max(buf);
gradient.set_min(min);
gradient.set_max(max);
buf.iter()
.map(|val| gradient.get_colour(*val))
.flat_map(|c| [c.r, c.g, c.b, c.a].into_iter())
.zip(img.iter_mut())
.for_each(|(val_rgba, img_rgba)| *img_rgba = val_rgba);
}
pub fn to_csv(
&mut self,
fname: &Path,
freq_scale: FrequencyScale,
cols: usize,
rows: usize,
) -> Result<(), std::io::Error> {
let result = self.to_buffer(freq_scale, cols, rows);
let mut writer = csv::Writer::from_path(fname)?;
let mut csv_record: Vec<String> = (0..cols).map(|x| x.to_string()).collect();
writer.write_record(&csv_record)?;
let mut i = 0;
for _ in 0..rows {
for c_rec in csv_record.iter_mut().take(cols) {
let val = result[i];
i += 1;
*c_rec = val.to_string();
}
writer.write_record(&csv_record)?;
}
writer.flush()?;
Ok(())
}
pub fn to_buffer(
&self,
freq_scale: FrequencyScale,
img_width: usize,
img_height: usize,
) -> Vec<f32> {
let mut buf = Vec::with_capacity(self.height * self.width);
match freq_scale {
FrequencyScale::Log => {
let scaler = FreqScaler::create(freq_scale, self.height, self.height);
let mut vert_slice = vec![0.0; self.height];
for h in 0..self.height {
let (f1, f2) = scaler.scale(h);
let (h1, mut h2) = (f1.floor() as usize, f2.ceil() as usize);
if h2 >= self.height {
h2 = self.height - 1;
}
for w in 0..self.width {
for (hh, val) in vert_slice.iter_mut().enumerate().take(h2).skip(h1) {
*val = self.spec[(hh * self.width) + w];
}
let value = integrate(f1, f2, &vert_slice);
buf.push(value);
}
}
}
FrequencyScale::Linear => {
buf.clone_from(&self.spec);
}
}
to_db(&mut buf);
resize(&buf, self.width, self.height, img_width, img_height)
}
pub fn get_min_max(&self) -> (f32, f32) {
get_min_max(&self.spec)
}
pub fn shape(&self) -> (usize, usize) {
(self.width, self.height)
}
pub fn row_iter<'a>(&'a self, row_idx: usize) -> impl Iterator<Item = &'a f32> + 'a {
self.spec
.chunks_exact(self.width)
.skip(row_idx)
.flatten()
.take(self.width)
}
}
pub fn get_min_max(data: &[f32]) -> (f32, f32) {
let mut min = f32::MAX;
let mut max = f32::MIN;
for val in data {
min = f32::min(*val, min);
max = f32::max(*val, max);
}
(min, max)
}
#[cfg(feature = "rayon")]
fn to_db(buf: &mut [f32]) {
let ref_db = buf
.par_chunks(1_000)
.fold(
|| f32::MIN,
|acc, chunk| {
let v = chunk.iter().fold(f32::MIN, |acc, &v| f32::max(acc, v));
if acc > v {
acc
} else {
v
}
},
)
.reduce(|| f32::MIN, f32::max);
let amp_ref = ref_db * ref_db;
let offset = 10.0 * (f32::max(1e-10, amp_ref)).log10();
let log_spec_max = buf
.par_iter_mut()
.map(|val| {
*val = 10.0 * (f32::max(1e-10, *val * *val)).log10() - offset;
*val
})
.fold(|| f32::MIN, f32::max)
.reduce(|| f32::MIN, f32::max);
let log_spec_max = log_spec_max - 80.0;
buf.par_chunks_mut(1_000).for_each(|chunk| {
for val in chunk.iter_mut() {
*val = f32::max(*val, log_spec_max);
}
});
}
#[cfg(not(feature = "rayon"))]
fn to_db(buf: &mut [f32]) {
let mut ref_db = f32::MIN;
buf.iter().for_each(|v| ref_db = f32::max(ref_db, *v));
let amp_ref = ref_db * ref_db;
let offset = 10.0 * (f32::max(1e-10, amp_ref)).log10();
let mut log_spec_max = f32::MIN;
for val in buf.iter_mut() {
*val = 10.0 * (f32::max(1e-10, *val * *val)).log10() - offset;
log_spec_max = f32::max(log_spec_max, *val);
}
for val in buf.iter_mut() {
*val = f32::max(*val, log_spec_max - 80.0);
}
}
fn resize(buf: &[f32], w_in: usize, h_in: usize, w_out: usize, h_out: usize) -> Vec<f32> {
if let Ok(mut resizer) = resize::new(w_in, h_in, w_out, h_out, GrayF32, Lanczos3) {
let mut resized_buf = vec![0.0; w_out * h_out];
let result = resizer.resize(buf.as_gray(), resized_buf.as_gray_mut());
if result.is_ok() {
return resized_buf;
}
}
vec![]
}
fn integrate(x1: f32, x2: f32, spec: &[f32]) -> f32 {
let mut i_x1 = x1.floor() as usize;
let i_x2 = (x2 - 0.000001).floor() as usize;
let area = |y, frac| y * frac;
if i_x1 >= i_x2 {
area(spec[i_x1], x2 - x1)
} else {
let mut result = area(spec[i_x1], (i_x1 + 1) as f32 - x1);
i_x1 += 1;
while i_x1 < i_x2 {
result += spec[i_x1];
i_x1 += 1;
}
if i_x1 >= spec.len() {
i_x1 = spec.len() - 1;
}
result += area(spec[i_x1], x2 - i_x1 as f32);
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_integrate() {
let v = vec![1.0, 2.0, 4.0, 1.123];
let c = integrate(0.0, 0.0, &v);
assert!((c - 0.0).abs() < 0.0001);
let c = integrate(0.25, 1.0, &v);
assert!((c - 0.75).abs() < 0.0001);
let c = integrate(0.0, 1.0, &v);
assert!((c - 1.0).abs() < 0.0001);
let c = integrate(3.75, 4.0, &v);
assert!((c - 1.123 / 4.0).abs() < 0.0001);
let c = integrate(0.5, 1.0, &v);
assert!((c - 0.5).abs() < 0.0001);
let c = integrate(0.75, 1.25, &v);
assert!((c - 0.75).abs() < 0.0001);
let c = integrate(1.8, 2.6, &v);
assert!((c - 2.8).abs() < 0.0001);
let c = integrate(0.0, 4.0, &v);
assert!((c - 8.123).abs() < 0.0001);
}
#[test]
fn test_from_raw() {
let raw = vec![-1i16, -2, -3, 4, 5, 6];
let spec = Spectrogram::from_raw(&raw, 2, 3).unwrap();
assert_eq!(spec.width, 2);
assert_eq!(spec.height, 3);
assert_eq!(spec.spec, vec![-1.0f32, -2.0, -3.0, 4.0, 5.0, 6.0]);
}
}