Compare commits
2 Commits
7662ff176c
...
eb94785a89
Author | SHA1 | Date | |
---|---|---|---|
eb94785a89 | |||
476479b693 |
40
Cargo.toml
40
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "lasprs"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
edition = "2021"
|
||||
authors = ["J.A. de Jong <j.a.dejong@ascee.nl>"]
|
||||
description = "Library for Acoustic Signal Processing (Rust edition, with optional Python bindings via pyo3)"
|
||||
@ -12,7 +12,7 @@ categories = ["multimedia::audio", "science", "mathematics"]
|
||||
|
||||
[lib]
|
||||
name = "lasprs"
|
||||
crate-type = ["cdylib", "rlib",]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
# Error handling
|
||||
@ -29,8 +29,12 @@ num = "0.4.3"
|
||||
rayon = "1.10.0"
|
||||
|
||||
# Python bindings
|
||||
pyo3 = { version = "0.22.2", optional = true, features = ["extension-module", "anyhow"]}
|
||||
numpy = { git = "https://github.com/JRRudy1/rust-numpy", branch = "pyo3-0.22.0" , optional = true}
|
||||
pyo3 = { version = "0.21.2", optional = true, features = [
|
||||
"extension-module",
|
||||
"anyhow",
|
||||
] }
|
||||
# Python bindings for Numpy arrays
|
||||
numpy = { version = "0.21.0", optional = true }
|
||||
|
||||
# White noise etc
|
||||
rand = "0.8.5"
|
||||
@ -74,12 +78,17 @@ itertools = "0.13.0"
|
||||
approx = "0.5.1"
|
||||
|
||||
# For getting timestamps. Only useful when recording.
|
||||
chrono = {version = "0.4.38", optional = true}
|
||||
chrono = { version = "0.4.38", optional = true }
|
||||
# For getting UUIDs in recording
|
||||
uuid = { version = "1.10.0", features = ["v4"] , optional = true}
|
||||
uuid = { version = "1.10.0", features = ["v4"], optional = true }
|
||||
|
||||
# Command line argument parser, for CLI apps
|
||||
clap = { version = "4.5.13", features = ["derive", "color", "help", "suggestions"] }
|
||||
clap = { version = "4.5.13", features = [
|
||||
"derive",
|
||||
"color",
|
||||
"help",
|
||||
"suggestions",
|
||||
] }
|
||||
|
||||
# FFT's
|
||||
realfft = "3.3.0"
|
||||
@ -104,8 +113,21 @@ default = ["f64", "cpal-api", "record"]
|
||||
# Use this to test if everything works well in f32
|
||||
# default = ["f32", "cpal-api", "record"]
|
||||
|
||||
# Use this for debugging extensions
|
||||
# default = ["f64", "python-bindings", "record", "cpal-api"]
|
||||
# When building Python bindings of Lasprs, you should enable the feature flag
|
||||
# "python-bindings". When debugging these, to safe the flag specification you
|
||||
# could just uncomment the line below here and comment the one above. If only
|
||||
# testing the implementations, or building the extension module, you should
|
||||
# call:
|
||||
|
||||
# $ maturin develop -F python-bindings
|
||||
|
||||
# Or:
|
||||
|
||||
# $ maturin develop --release -F python-bindings
|
||||
|
||||
# For faster code
|
||||
|
||||
#default = ["f64", "python-bindings", "record", "cpal-api"]
|
||||
|
||||
pulse-api = []
|
||||
cpal-api = ["dep:cpal"]
|
||||
|
@ -12,11 +12,12 @@ and processing of (multi) sensor data in real time on a PC and output results.
|
||||
|
||||
Documentation is provided at [doc.rs](https://docs.rs/lasprs/latest/lasprs).
|
||||
|
||||
## Python bindings
|
||||
|
||||
## Python bindings and examples
|
||||
|
||||
The library has Python bindings (via [pyo3](https://pyo3.rs), which can be installed via:
|
||||
|
||||
```
|
||||
$ pip install git+https://code.ascee.nl/ascee/lasprs --install-option "--all-features"
|
||||
$ pip install git+https://code.ascee.nl/ascee/lasprs --install-option "python-bindings"
|
||||
```
|
||||
|
||||
|
@ -32,7 +32,10 @@ pub trait Stream {
|
||||
}
|
||||
|
||||
/// Stream API descriptor: type and corresponding text
|
||||
#[cfg_attr(feature = "python-bindings", pyclass(eq, eq_int))]
|
||||
// Do the following when Pyo3 0.22 can finally be used combined with rust-numpy:
|
||||
//#[cfg_attr(feature = "python-bindings", pyclass(eq, eq_int))]
|
||||
// For now:
|
||||
#[cfg_attr(feature = "python-bindings", pyclass)]
|
||||
#[derive(strum_macros::EnumMessage, Debug, Clone, PartialEq, Serialize, Deserialize, strum_macros::Display)]
|
||||
#[allow(dead_code)]
|
||||
pub enum StreamApiDescr {
|
||||
|
@ -7,8 +7,11 @@ use crate::config::*;
|
||||
|
||||
/// Data type description for samples coming from a stream
|
||||
#[derive(strum_macros::EnumMessage, PartialEq, Copy, Debug, Clone, Serialize, Deserialize)]
|
||||
// Do the following when Pyo3 0.22 can finally be used combined with rust-numpy:
|
||||
//#[cfg_attr(feature = "python-bindings", pyclass(eq, eq_int))]
|
||||
// For now:
|
||||
#[cfg_attr(feature = "python-bindings", pyclass)]
|
||||
#[allow(dead_code)]
|
||||
#[cfg_attr(feature = "python-bindings", pyclass(eq, eq_int))]
|
||||
pub enum DataType {
|
||||
/// 32-bit floats
|
||||
#[strum(message = "F32", detailed_message = "32-bits floating points")]
|
||||
|
@ -50,7 +50,10 @@ use api::*;
|
||||
use crate::config::*;
|
||||
/// Stream types that can be started
|
||||
///
|
||||
#[cfg_attr(feature = "python-bindings", pyclass(eq, eq_int))]
|
||||
// Do the following when Pyo3 0.22 can finally be used combined with rust-numpy:
|
||||
// #[cfg_attr(feature = "python-bindings", pyclass(eq, eq_int))]
|
||||
// For now:
|
||||
#[cfg_attr(feature = "python-bindings", pyclass)]
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub enum StreamType {
|
||||
/// Input-only stream
|
||||
|
@ -7,8 +7,10 @@ use strum_macros;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
/// Physical quantities that are I/O of a Daq device.
|
||||
// Do the following when Pyo3 0.22 can finally be used combined with rust-numpy:
|
||||
// #[cfg_attr(feature = "python-bindings", pyclass(eq, eq_int))]
|
||||
#[cfg_attr(feature = "python-bindings", pyclass)]
|
||||
#[derive(PartialEq, Serialize, Deserialize, strum_macros::EnumMessage, Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "python-bindings", pyclass(eq, eq_int))]
|
||||
#[allow(dead_code)]
|
||||
pub enum Qty {
|
||||
/// Number
|
||||
|
@ -3,7 +3,12 @@ use crate::config::*;
|
||||
|
||||
/// Errors that happen in a stream
|
||||
#[derive(strum_macros::EnumMessage, PartialEq, Debug, Clone, Display, Copy)]
|
||||
#[cfg_attr(feature = "python-bindings", pyclass(eq, eq_int))]
|
||||
|
||||
// Do the following when Pyo3 0.22 can finally be used combined with rust-numpy:
|
||||
//#[cfg_attr(feature = "python-bindings", pyclass(eq, eq_int))]
|
||||
// For now:
|
||||
#[cfg_attr(feature = "python-bindings", pyclass)]
|
||||
|
||||
pub enum StreamError {
|
||||
/// Input overrun
|
||||
#[strum(
|
||||
|
@ -478,6 +478,7 @@ impl BandDescriptor for ThirdOctaveBandDescriptor {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "python-bindings")]
|
||||
#[cfg_attr(feature = "python-bindings", pymethods)]
|
||||
impl StandardFilterDescriptor {
|
||||
#[pyo3(name = "genFilter")]
|
||||
@ -511,7 +512,7 @@ impl StandardFilterDescriptor {
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!{"{:#?}", self}
|
||||
format! {"{:#?}", self}
|
||||
}
|
||||
|
||||
fn __str__(&self) -> String {
|
||||
|
@ -34,12 +34,14 @@ impl FFT {
|
||||
nfftF: nfft as Flt,
|
||||
}
|
||||
}
|
||||
pub fn process<'a, T>(&mut self, time: &[Flt], freq: T)
|
||||
pub fn process<'a, T, U>(&mut self, time: T, freq: U)
|
||||
where
|
||||
T: Into<ArrayViewMut<'a, Cflt, Ix1>>,
|
||||
T: Into<ArrayView<'a, Flt, Ix1>>,
|
||||
U: Into<ArrayViewMut<'a, Cflt, Ix1>>,
|
||||
{
|
||||
let mut freq = freq.into();
|
||||
self.timescratch.copy_from_slice(time);
|
||||
let time = time.into();
|
||||
self.timescratch.copy_from_slice(time.as_slice().unwrap());
|
||||
let _ = self
|
||||
.fft
|
||||
.process(&mut self.timescratch, freq.as_slice_mut().unwrap());
|
||||
@ -49,4 +51,4 @@ impl FFT {
|
||||
freq.slice_mut(s![1..self.half_nfft_rounded])
|
||||
.par_mapv_inplace(|x| 2. * x / self.nfftF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
use crate::config::*;
|
||||
use strum_macros::{Display, EnumMessage};
|
||||
/// Sound level frequency weighting type (A, C, Z)
|
||||
#[cfg_attr(feature = "python-bindings", pyclass(eq, eq_int))]
|
||||
|
||||
// Do the following when Pyo3 0.22 can finally be used combined with rust-numpy:
|
||||
// #[cfg_attr(feature = "python-bindings", pyclass(eq, eq_int))]
|
||||
// For now:
|
||||
#[cfg_attr(feature = "python-bindings", pyclass)]
|
||||
#[derive(Display, Debug, EnumMessage, Default, Clone, PartialEq)]
|
||||
pub enum FreqWeighting {
|
||||
/// A-weighting
|
||||
|
45
src/ps/ps.rs
45
src/ps/ps.rs
@ -7,17 +7,17 @@ use std::usize;
|
||||
|
||||
use crate::Dcol;
|
||||
|
||||
use super::window::*;
|
||||
use super::fft::FFT;
|
||||
use super::window::*;
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
use realfft::{RealFftPlanner, RealToComplex};
|
||||
|
||||
/// Cross power spectra, which is a 3D array, with the following properties:
|
||||
///
|
||||
///
|
||||
/// - The first index is the frequency index, starting at DC, ending at nfft/2.
|
||||
/// - The second, and third index result in `[i,j]` = C_ij = p_i * conj(p_j)
|
||||
///
|
||||
///
|
||||
pub type CPSResult = Array3<Cflt>;
|
||||
|
||||
/// Extra typical methods that are of use for 3D-arrays of complex numbers, that
|
||||
@ -46,7 +46,6 @@ pub trait CrossPowerSpecra {
|
||||
fn tf(&self, chi: usize, chj: usize, chRef: Option<usize>) -> Array1<Cflt>;
|
||||
}
|
||||
|
||||
|
||||
impl CrossPowerSpecra for CPSResult {
|
||||
fn ap(&self, ch: usize) -> Array1<Flt> {
|
||||
// Slice out one value for all frequencies, map to only real part, and
|
||||
@ -87,10 +86,10 @@ impl CrossPowerSpecra for CPSResult {
|
||||
/// estimation.
|
||||
///
|
||||
pub struct PowerSpectra {
|
||||
/// Window used in estimator
|
||||
pub window: Window,
|
||||
/// The window power, is corrected for in power spectra estimants
|
||||
pub sqrt_win_pwr: Flt,
|
||||
/// Window used in estimator. The actual Window in here is normalized with
|
||||
/// the square root of the Window power. This safes one division when
|
||||
/// processing time data.
|
||||
pub window_normalized: Window,
|
||||
|
||||
ffts: Vec<FFT>,
|
||||
|
||||
@ -103,7 +102,7 @@ pub struct PowerSpectra {
|
||||
impl PowerSpectra {
|
||||
/// Returns the FFT length used in power spectra computations
|
||||
pub fn nfft(&self) -> usize {
|
||||
self.window.win.len()
|
||||
self.window_normalized.win.len()
|
||||
}
|
||||
/// Create new power spectra estimator. Uses FFT size from window length
|
||||
///
|
||||
@ -116,9 +115,12 @@ impl PowerSpectra {
|
||||
///
|
||||
/// - `window` - A `Window` struct, from which NFFT is also used.
|
||||
///
|
||||
pub fn newFromWindow(window: Window) -> PowerSpectra {
|
||||
pub fn newFromWindow(mut window: Window) -> PowerSpectra {
|
||||
let nfft = window.win.len();
|
||||
let win_pwr = window.win.mapv(|w| w.powi(2)).sum() / (nfft as Flt);
|
||||
let sqrt_win_pwr = Flt::sqrt(win_pwr);
|
||||
window.win.mapv_inplace(|v| v / sqrt_win_pwr);
|
||||
|
||||
assert!(nfft > 0);
|
||||
assert!(nfft % 2 == 0);
|
||||
|
||||
@ -128,8 +130,7 @@ impl PowerSpectra {
|
||||
let Fft = FFT::new(fft);
|
||||
|
||||
PowerSpectra {
|
||||
window,
|
||||
sqrt_win_pwr: Flt::sqrt(win_pwr),
|
||||
window_normalized: window,
|
||||
ffts: vec![Fft],
|
||||
timedata: Array2::zeros((nfft, 1)),
|
||||
freqdata: Array2::zeros((nfft / 2 + 1, 1)),
|
||||
@ -138,7 +139,7 @@ impl PowerSpectra {
|
||||
|
||||
/// Compute FFTs of input channel data. Stores the scaled FFT data in
|
||||
/// self.freqdata.
|
||||
fn compute_ffts(&mut self, timedata: ArrayView2<Flt>) -> &Array2<Cflt> {
|
||||
fn compute_ffts(&mut self, timedata: ArrayView2<Flt>) -> ArrayView2<Cflt> {
|
||||
let (n, nch) = timedata.dim();
|
||||
let nfft = self.nfft();
|
||||
assert!(n == nfft);
|
||||
@ -153,25 +154,27 @@ impl PowerSpectra {
|
||||
}
|
||||
|
||||
assert!(n == self.nfft());
|
||||
assert!(n == self.window.win.len());
|
||||
let sqrt_win_pwr = self.sqrt_win_pwr;
|
||||
assert!(n == self.window_normalized.win.len());
|
||||
|
||||
// Multiply signals with window function, and compute fft's for each channel
|
||||
Zip::from(timedata.axis_iter(Axis(1)))
|
||||
.and(self.timedata.axis_iter_mut(Axis(1)))
|
||||
.and(&mut self.ffts)
|
||||
.and(self.freqdata.axis_iter_mut(Axis(1)))
|
||||
.par_for_each(|time_in,mut time, fft, mut freq| {
|
||||
.par_for_each(|time_in, mut time_tmp_storage, fft, mut freq| {
|
||||
let DC = time_in.mean().unwrap();
|
||||
|
||||
azip!((t in &mut time_tmp_storage, &tin in time_in, &win in &self.window_normalized.win) {
|
||||
// Substract DC value from time data, as this leaks into
|
||||
// positive frequencies due to windowing.
|
||||
// Multiply with window and copy over to local time data buffer
|
||||
azip!((t in &mut time, &tin in time_in, &win in &self.window.win) *t=tin*win/sqrt_win_pwr);
|
||||
*t=(tin-DC)*win});
|
||||
|
||||
let tslice = time.as_slice().unwrap();
|
||||
let fslice = freq.as_slice_mut().unwrap();
|
||||
fft.process(tslice, fslice);
|
||||
fft.process(&time_tmp_storage, &mut freq);
|
||||
freq[0] = DC + 0. * I;
|
||||
});
|
||||
|
||||
&self.freqdata
|
||||
self.freqdata.view()
|
||||
}
|
||||
|
||||
/// Compute cross power spectra from input time data. First axis is
|
||||
|
@ -24,7 +24,7 @@
|
||||
//! let settings = SLMSettingsBuilder::default()
|
||||
//! .fs(48e3)
|
||||
//! .freqWeighting(FreqWeighting::A)
|
||||
//! .timeWeighting(TimeWeighting::Fast)
|
||||
//! .timeWeighting(TimeWeighting::Fast{})
|
||||
//! .filterDescriptors(&[desc]).build().unwrap();
|
||||
//!
|
||||
//! let mut slm = SLM::new(settings);
|
||||
@ -33,7 +33,7 @@
|
||||
//! data[0] = 1.;
|
||||
//!
|
||||
//! // Now apply some data. This is a kind of the SLM-s impulse response
|
||||
//! let res = slm.run(&data.as_slice().unwrap()).unwrap();
|
||||
//! let res = slm.run(data.as_slice().unwrap(), true).unwrap();
|
||||
//!
|
||||
//! // Only one channel of result data
|
||||
//! assert_eq!(res.len(), 1);
|
||||
|
@ -28,6 +28,7 @@ pub struct SLMSettings {
|
||||
pub filterDescriptors: Vec<StandardFilterDescriptor>,
|
||||
}
|
||||
|
||||
#[cfg(feature="python-bindings")]
|
||||
#[cfg_attr(feature = "python-bindings", pymethods)]
|
||||
impl SLMSettings {
|
||||
#[new]
|
||||
|
@ -205,6 +205,7 @@ impl SLM {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature="python-bindings")]
|
||||
#[cfg_attr(feature = "python-bindings", pymethods)]
|
||||
impl SLM {
|
||||
#[new]
|
||||
|
@ -1,6 +1,10 @@
|
||||
use crate::config::*;
|
||||
/// Time weighting to use in level detection of Sound Level Meter.
|
||||
#[cfg_attr(feature = "python-bindings", pyclass(eq))]
|
||||
///
|
||||
// Do the following when Pyo3 0.22 can finally be used combined with rust-numpy:
|
||||
// #[cfg_attr(feature = "python-bindings", pyclass(eq))]
|
||||
// For now:
|
||||
#[cfg_attr(feature = "python-bindings", pyclass)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum TimeWeighting {
|
||||
// I know that the curly braces here are not required and add some
|
||||
|
Loading…
Reference in New Issue
Block a user