fully implemented AvSweepPowerSpectra
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in -5m53s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped

This commit is contained in:
Thijs Hekman 2024-12-03 10:47:10 +01:00
parent 6cdc182b57
commit 4cd390871a
4 changed files with 131 additions and 75 deletions

View File

@ -158,7 +158,8 @@ ccube AvPowerSpectra::get_est() const
AvSweepPowerSpectra::AvSweepPowerSpectra(const us nfft, const Window::WindowType w, AvSweepPowerSpectra::AvSweepPowerSpectra(const us nfft, const Window::WindowType w,
const d overlap_percentage, const d overlap_percentage,
const d time_constant_times_fs) const d time_constant_times_fs,
const vd *A)
: _nfft(nfft), _ps(nfft, w) : _nfft(nfft), _ps(nfft, w)
{ {
@ -167,6 +168,14 @@ AvSweepPowerSpectra::AvSweepPowerSpectra(const us nfft, const Window::WindowType
{ {
throw rte("Overlap percentage should be >= 0 and < 100"); throw rte("Overlap percentage should be >= 0 and < 100");
} }
if(A == nullptr) {
_A = vd(nfft, arma::fill::ones);
} else {
if(A->n_elem != (nfft / 2 + 1)) {
throw rte("Invalid length given for amplitude coefficients");
}
_A = arma::square(*A);
}
_overlap_keep = (nfft * overlap_percentage) / 100; _overlap_keep = (nfft * overlap_percentage) / 100;
DEBUGTRACE_PRINT(_overlap_keep); DEBUGTRACE_PRINT(_overlap_keep);
@ -235,25 +244,24 @@ ccube AvSweepPowerSpectra::compute(const dmat &timedata)
_n_averages = vd(nfreq, arma::fill::zeros); _n_averages = vd(nfreq, arma::fill::zeros);
} }
// d threshold = arma::mean(arma::real(temp.slice(0).col(0))); vd corr_pow = arma::real(temp.slice(0).col(0)) / _A;
d threshold = 1e-13;
d threshold = pow(10.0, log10(arma::max(corr_pow)*arma::median(corr_pow))/2.0);
// d threshold = 1e-13;
for (us f=0; f < nfreq; f++) for (us f=0; f < nfreq; f++)
{ {
if (real(temp(f, 0, 0)) > threshold) if (real(temp(f, 0, 0)) / _A(f) > threshold)
{ {
_n_averages[f]++; _n_averages[f]++;
if (_n_averages[f] == 1) if (_n_averages[f] == 1)
{ {
_est.row(f) = temp.row(f); _est.row(f) = temp.row(f);
// _est.subcube(f, 0, 0, f, ncols - 1, ncols - 1) = temp.subcube(f, 0, 0, f, ncols - 1, ncols - 1);
} }
else else
{ {
_est.row(f) = (static_cast<d>(_n_averages[f] - 1) / _n_averages[f]) * _est.row(f) + _est.row(f) = (static_cast<d>(_n_averages[f] - 1) / _n_averages[f]) * _est.row(f) +
temp.row(f) / _n_averages[f]; temp.row(f) / _n_averages[f];
// _est.subcube(f, 0, 0, f, ncols - 1, ncols - 1) = (static_cast<d>(_n_averages[f] - 1) / _n_averages[f]) * _est.subcube(f, 0, 0, f, ncols - 1, ncols - 1) +
// temp.subcube(f, 0, 0, f, ncols - 1, ncols - 1) / _n_averages[f];
} }
} }
} }
@ -284,3 +292,8 @@ ccube AvSweepPowerSpectra::get_est() const
{ {
return _est; return _est;
} }
vd AvSweepPowerSpectra::get_A() const
{
return _A;
}

View File

@ -186,6 +186,7 @@ class AvSweepPowerSpectra {
d _alpha; // Only valid in case of 'Leaking' d _alpha; // Only valid in case of 'Leaking'
us _nfft; us _nfft;
vd _n_averages { 1, arma::fill::zeros}; // Only valid in case of 'Averaging' vd _n_averages { 1, arma::fill::zeros}; // Only valid in case of 'Averaging'
vd _A; // squared amplitude envelope
PowerSpectra _ps; PowerSpectra _ps;
/** /**
@ -225,10 +226,12 @@ public:
AvSweepPowerSpectra(const us nfft = 2048, AvSweepPowerSpectra(const us nfft = 2048,
const Window::WindowType w = Window::WindowType::Hann, const Window::WindowType w = Window::WindowType::Hann,
const d overlap_percentage = 50., const d overlap_percentage = 50.,
const d fs_tau = -1); const d fs_tau = -1,
const vd *A = nullptr); // amplitude envelope
AvSweepPowerSpectra(const AvSweepPowerSpectra &) = delete; AvSweepPowerSpectra(const AvSweepPowerSpectra &) = default;
AvSweepPowerSpectra &operator=(const AvSweepPowerSpectra &) = delete; AvSweepPowerSpectra &operator=(const AvSweepPowerSpectra &) = default;
AvSweepPowerSpectra (AvSweepPowerSpectra&& ) = default;
/** /**
* @brief Reset to empty state. Clears the time buffer and sets estimator to * @brief Reset to empty state. Clears the time buffer and sets estimator to
@ -260,6 +263,8 @@ public:
* */ * */
ccube get_est() const; ccube get_est() const;
vd get_A() const;
/** /**
* @brief The overlap is rounded to a certain amount of time samples. This * @brief The overlap is rounded to a certain amount of time samples. This
* function returns that value. * function returns that value.

View File

@ -29,27 +29,28 @@ using rte = std::runtime_error;
* @param m The Python module to add classes and methods to * @param m The Python module to add classes and methods to
*/ */
void init_dsp(py::module &m) { void init_dsp(py::module &m)
{
py::class_<Fft> fft(m, "Fft"); py::class_<Fft> fft(m, "Fft");
fft.def(py::init<us>()); fft.def(py::init<us>());
fft.def("fft", [](Fft &f, dpyarray dat) { fft.def("fft", [](Fft &f, dpyarray dat)
{
if (dat.ndim() == 1) { if (dat.ndim() == 1) {
return ColToNpy<c>(f.fft(NpyToCol<d, false>(dat))); return ColToNpy<c>(f.fft(NpyToCol<d, false>(dat)));
} else if (dat.ndim() == 2) { } else if (dat.ndim() == 2) {
return MatToNpy<c>(f.fft(NpyToMat<d, false>(dat))); return MatToNpy<c>(f.fft(NpyToMat<d, false>(dat)));
} else { } else {
throw rte("Invalid dimensions of array"); throw rte("Invalid dimensions of array");
} } });
}); fft.def("ifft", [](Fft &f, cpyarray dat)
fft.def("ifft", [](Fft &f, cpyarray dat) { {
if (dat.ndim() == 1) { if (dat.ndim() == 1) {
return ColToNpy<d>(f.ifft(NpyToCol<c, false>(dat))); return ColToNpy<d>(f.ifft(NpyToCol<c, false>(dat)));
} else if (dat.ndim() == 2) { } else if (dat.ndim() == 2) {
return MatToNpy<d>(f.ifft(NpyToMat<c, false>(dat))); return MatToNpy<d>(f.ifft(NpyToMat<c, false>(dat)));
} else { } else {
throw rte("Invalid dimensions of array"); throw rte("Invalid dimensions of array");
} } });
});
fft.def_static("load_fft_wisdom", &Fft::load_fft_wisdom); fft.def_static("load_fft_wisdom", &Fft::load_fft_wisdom);
fft.def_static("store_fft_wisdom", &Fft::store_fft_wisdom); fft.def_static("store_fft_wisdom", &Fft::store_fft_wisdom);
@ -71,41 +72,39 @@ void init_dsp(py::module &m) {
/// SeriesBiquad /// SeriesBiquad
py::class_<SeriesBiquad, std::shared_ptr<SeriesBiquad>> sbq(m, "SeriesBiquad", py::class_<SeriesBiquad, std::shared_ptr<SeriesBiquad>> sbq(m, "SeriesBiquad",
filter); filter);
sbq.def(py::init([](dpyarray filter) { sbq.def(py::init([](dpyarray filter)
return std::make_shared<SeriesBiquad>(NpyToCol<d, false>(filter)); { return std::make_shared<SeriesBiquad>(NpyToCol<d, false>(filter)); }));
})); sbq.def("filter", [](SeriesBiquad &s, dpyarray input)
sbq.def("filter", [](SeriesBiquad &s, dpyarray input) { {
vd res = NpyToCol<d, true>(input); vd res = NpyToCol<d, true>(input);
s.filter(res); s.filter(res);
return ColToNpy<d>(res); return ColToNpy<d>(res); });
});
/// BiquadBank /// BiquadBank
py::class_<BiquadBank, Filter, std::shared_ptr<BiquadBank>> bqb(m, py::class_<BiquadBank, Filter, std::shared_ptr<BiquadBank>> bqb(m,
"BiquadBank"); "BiquadBank");
bqb.def(py::init([](dpyarray coefs) { bqb.def(py::init([](dpyarray coefs)
return std::make_shared<BiquadBank>(NpyToMat<d, false>(coefs)); { return std::make_shared<BiquadBank>(NpyToMat<d, false>(coefs)); }));
})); bqb.def(py::init([](dpyarray coefs, dpyarray gains)
bqb.def(py::init([](dpyarray coefs, dpyarray gains) { {
vd gains_arma = NpyToMat<d, false>(gains); vd gains_arma = NpyToMat<d, false>(gains);
return std::make_shared<BiquadBank>(NpyToMat<d, false>(coefs), &gains_arma); return std::make_shared<BiquadBank>(NpyToMat<d, false>(coefs), &gains_arma); }));
}));
bqb.def("setGains", bqb.def("setGains",
[](BiquadBank &b, dpyarray gains) { b.setGains(NpyToCol(gains)); }); [](BiquadBank &b, dpyarray gains)
bqb.def("filter", [](BiquadBank &b, dpyarray input) { { b.setGains(NpyToCol(gains)); });
bqb.def("filter", [](BiquadBank &b, dpyarray input)
{
// Yes, a copy here // Yes, a copy here
vd inout = NpyToCol<d, true>(input); vd inout = NpyToCol<d, true>(input);
b.filter(inout); b.filter(inout);
return ColToNpy(inout); return ColToNpy(inout); });
});
/// PowerSpectra /// PowerSpectra
py::class_<PowerSpectra> ps(m, "PowerSpectra"); py::class_<PowerSpectra> ps(m, "PowerSpectra");
ps.def(py::init<const us, const Window::WindowType>()); ps.def(py::init<const us, const Window::WindowType>());
ps.def("compute", [](PowerSpectra &ps, dpyarray input) { ps.def("compute", [](PowerSpectra &ps, dpyarray input)
return CubeToNpy<c>(ps.compute(NpyToMat<d, false>(input))); { return CubeToNpy<c>(ps.compute(NpyToMat<d, false>(input))); });
});
/// AvPowerSpectra /// AvPowerSpectra
py::class_<AvPowerSpectra> aps(m, "AvPowerSpectra"); py::class_<AvPowerSpectra> aps(m, "AvPowerSpectra");
@ -114,7 +113,8 @@ void init_dsp(py::module &m) {
py::arg("windowType") = Window::WindowType::Hann, py::arg("windowType") = Window::WindowType::Hann,
py::arg("overlap_percentage") = 50.0, py::arg("time_constant") = -1); py::arg("overlap_percentage") = 50.0, py::arg("time_constant") = -1);
aps.def("compute", [](AvPowerSpectra &aps, dpyarray timedata) { aps.def("compute", [](AvPowerSpectra &aps, dpyarray timedata)
{
std::optional<ccube> res; std::optional<ccube> res;
dmat timedata_mat = NpyToMat<d, false>(timedata); dmat timedata_mat = NpyToMat<d, false>(timedata);
{ {
@ -122,22 +122,36 @@ void init_dsp(py::module &m) {
res = aps.compute(timedata_mat); res = aps.compute(timedata_mat);
} }
return CubeToNpy<c>(res.value_or(ccube(0, 0, 0))); return CubeToNpy<c>(res.value_or(ccube(0, 0, 0))); });
}); aps.def("get_est", [](const AvPowerSpectra &ps)
aps.def("get_est", [](const AvPowerSpectra &ps) { {
ccube est = ps.get_est(); ccube est = ps.get_est();
return CubeToNpy<c>(est); return CubeToNpy<c>(est); });
});
/// AvSweepPowerSpectra /// AvSweepPowerSpectra
py::class_<AvSweepPowerSpectra> asps(m, "AvSweepPowerSpectra"); py::class_<AvSweepPowerSpectra, std::unique_ptr<AvSweepPowerSpectra>> asps(m, "AvSweepPowerSpectra");
asps.def(py::init<const us, const Window::WindowType, const d, const d>(), asps.def(py::init([](const us nfft,
py::arg("nfft") = 2048, const Window::WindowType windowtype,
py::arg("windowType") = Window::WindowType::Hann, const d overlap_percentage,
py::arg("overlap_percentage") = 50.0, const d time_constant,
py::arg("time_constant") = -1); const dpyarray A)
{
std::unique_ptr<vd> theA = std::make_unique<vd>(NpyToCol<d, false>(A));
asps.def("compute", [](AvSweepPowerSpectra &asps, dpyarray timedata) { return std::make_unique<AvSweepPowerSpectra>(
nfft,
windowtype,
overlap_percentage,
time_constant,
theA.get());
}
));
asps.def("compute", [](AvSweepPowerSpectra &asps, dpyarray timedata)
{
std::optional<ccube> res; std::optional<ccube> res;
dmat timedata_mat = NpyToMat<d, false>(timedata); dmat timedata_mat = NpyToMat<d, false>(timedata);
{ {
@ -145,44 +159,60 @@ void init_dsp(py::module &m) {
res = asps.compute(timedata_mat); res = asps.compute(timedata_mat);
} }
return CubeToNpy<c>(res.value_or(ccube(0, 0, 0))); return CubeToNpy<c>(res.value_or(ccube(0, 0, 0))); });
}); asps.def("get_est", [](const AvSweepPowerSpectra &sps)
asps.def("get_est", [](const AvSweepPowerSpectra &sps) { {
ccube est = sps.get_est(); ccube est = sps.get_est();
return CubeToNpy<c>(est); return CubeToNpy<c>(est); });
});
asps.def("get_A", [](const AvSweepPowerSpectra &sps)
{
vd A = sps.get_A();
return ColToNpy<d>(A); });
py::class_<SLM> slm(m, "cppSLM"); py::class_<SLM> slm(m, "cppSLM");
slm.def_static("fromBiquads", [](const d fs, const d Lref, const us ds, slm.def_static("fromBiquads", [](const d fs, const d Lref, const us ds,
const d tau, dpyarray bandpass) { const d tau, dpyarray bandpass)
return SLM::fromBiquads(fs, Lref, ds, tau, NpyToMat<d, false>(bandpass)); {
}); return SLM::fromBiquads(fs, Lref, ds, tau, NpyToMat<d, false>(bandpass)); });
slm.def_static("fromBiquads", [](const d fs, const d Lref, const us ds, slm.def_static("fromBiquads", [](const d fs, const d Lref, const us ds,
const d tau, dpyarray prefilter, const d tau, dpyarray prefilter,
py::array_t<d> bandpass) { py::array_t<d> bandpass)
{
return SLM::fromBiquads(fs, Lref, ds, tau, NpyToCol<d, false>(prefilter), return SLM::fromBiquads(fs, Lref, ds, tau, NpyToCol<d, false>(prefilter),
NpyToMat<d, false>(bandpass)); NpyToMat<d, false>(bandpass)); });
});
slm.def("run", [](SLM &slm, dpyarray in) { slm.def("run", [](SLM &slm, dpyarray in)
return MatToNpy<d>(slm.run(NpyToCol<d, false>(in))); {
}); return MatToNpy<d>(slm.run(NpyToCol<d, false>(in))); });
slm.def("Pm", [](const SLM &slm) { return ColToNpy<d>(slm.Pm); }); slm.def("Pm", [](const SLM &slm)
slm.def("Pmax", [](const SLM &slm) { return ColToNpy<d>(slm.Pmax); }); {
slm.def("Ppeak", [](const SLM &slm) { return ColToNpy<d>(slm.Ppeak); }); return ColToNpy<d>(slm.Pm); });
slm.def("Pmax", [](const SLM &slm)
{
return ColToNpy<d>(slm.Pmax); });
slm.def("Ppeak", [](const SLM &slm)
{
return ColToNpy<d>(slm.Ppeak); });
slm.def("Leq", [](const SLM &slm) { return ColToNpy<d>(slm.Leq()); }); slm.def("Leq", [](const SLM &slm)
slm.def("Lmax", [](const SLM &slm) { return ColToNpy<d>(slm.Lmax()); }); {
slm.def("Lpeak", [](const SLM &slm) { return ColToNpy<d>(slm.Lpeak()); }); return ColToNpy<d>(slm.Leq()); });
slm.def("Lmax", [](const SLM &slm)
{
return ColToNpy<d>(slm.Lmax()); });
slm.def("Lpeak", [](const SLM &slm)
{
return ColToNpy<d>(slm.Lpeak()); });
slm.def_static("suggestedDownSamplingFac", &SLM::suggestedDownSamplingFac); slm.def_static("suggestedDownSamplingFac", &SLM::suggestedDownSamplingFac);
// Frequency smoother // Frequency smoother
m.def("freqSmooth", [](dpyarray freq, dpyarray X, unsigned w) { m.def("freqSmooth", [](dpyarray freq, dpyarray X, unsigned w)
{
vd freqa = NpyToCol<d, false>(freq); vd freqa = NpyToCol<d, false>(freq);
vd Xa = NpyToCol<d, false>(X); vd Xa = NpyToCol<d, false>(X);
return ColToNpy(freqSmooth(freqa, Xa, w)); return ColToNpy(freqSmooth(freqa, Xa, w)); });
});
} }
/** @} */ /** @} */

View File

@ -19,6 +19,9 @@ from .lasp_cpp import Window, DaqChannel, AvPowerSpectra, AvSweepPowerSpectra
from typing import List from typing import List
from functools import lru_cache from functools import lru_cache
from .lasp_config import ones from .lasp_config import ones
from scipy.interpolate import CubicSpline
"""! """!
Author: J.A. de Jong - ASCEE Author: J.A. de Jong - ASCEE
@ -842,10 +845,15 @@ class Measurement:
nchannels = len(channels) nchannels = len(channels)
# aps = AvPowerSpectra(nfft, window, overlap)
aps = AvSweepPowerSpectra(nfft, window, overlap)
freq = getFreq(self.samplerate, nfft) freq = getFreq(self.samplerate, nfft)
if self._siggen['Type'] == 'Sweep':
iA = CubicSpline(self._siggen['Data']['fA'], self._siggen['Data']['A'])
aps = AvSweepPowerSpectra(nfft, window, overlap, -1, iA(freq))
else:
aps = AvPowerSpectra(nfft, window, overlap)
for data in self.iterData(channels, **kwargs): for data in self.iterData(channels, **kwargs):
CS = aps.compute(data) CS = aps.compute(data)