Compare commits

..

10 Commits

Author SHA1 Message Date
5a051d21a1 Measurement.fromnpy(): accept sensitivity as scalar or 0-dim numpy.ndarray
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in -3m20s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-09-10 13:40:47 +02:00
6b0442fe90 Version bump 1.6.8
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in -1m5s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Successful in -5m36s
2024-07-02 10:57:16 +02:00
e542f805e9 Removed old unused Fir filterbank code. 2024-07-02 10:56:42 +02:00
d28875cdcc Bump 1.6.7
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in -1m1s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-06-28 09:16:23 +02:00
f6ea790071 Bugfix for overwriting array initialization functions, such that it can also handle complex numbers and different ordering 2024-06-28 09:15:55 +02:00
7cd3dcffa8 Merge remote-tracking branch 'refs/remotes/origin/develop' into develop
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in -59s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-06-26 15:06:09 +02:00
cdc3ffb84a Fixed an error where the ylabel of SPL plots would be indicated as 2 micropascal 2024-06-26 15:05:34 +02:00
838a0f7cc1 Silence portaudio alsa errors when querying device info AND when starting stream. Do not know whether this solves the problem of its verbosity, but at least the code is where it belongs
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in -59s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-06-26 12:17:43 +02:00
bf5d006aef Merge branch 'master' into develop
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in -54s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-06-24 16:34:29 +02:00
a443be6e39 Updated readme for RPI install
Some checks failed
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Blocked by required conditions
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Has been cancelled
2024-06-24 16:33:31 +02:00
11 changed files with 93 additions and 216 deletions

View File

@ -4,6 +4,7 @@ project(LASP LANGUAGES C CXX VERSION 1.6.3)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED)
# Experimental: support for Raspberry Pi (64-bit architectures)
if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64")
set(RPI TRUE)
else()
@ -55,7 +56,6 @@ cmake_policy(SET CMP0079 NEW)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake")
include_directories(/usr/include/python3.8)
include("BuildType")
include("QueryPythonForPybind11")

View File

@ -44,15 +44,17 @@ in a sister repository [lasp-doc](https://code.ascee.nl/ascee/lasp-doc).
If you have any question(s), please feel free to contact us: [email](info@ascee.nl).
# Installation - Linux (Ubuntu-based)
## Prerequisites
Run the following on the command line to install all prerequisites on
Debian-based Linux:
Debian-based Linux, x86-64:
- `sudo apt install python3-pip libfftw3-3 libopenblas-base libusb-1.0-0 libpulse0`
## Installation from wheel (recommended for non-developers)
Go to: [LASP releases](https://code.ascee.nl/ASCEE/lasp/releases/latest/) and
@ -82,6 +84,28 @@ If building RtAudio with the Jack Audio Connection Kit (JACK) backend, you will
- `$ cd lasp`
- `pip install -e .`
# Building and installation for Raspberry Pi (Raspberry Pi OS)
Run the following on the command line to install all prerequisites on
Raspberry Pi OS:
- `sudo apt install libfftw3-dev libopenblas64-dev libhdf5-dev libclalsadrv-dev`
In a virtualenv: install `build`
- `$ pip install build`
Then run:
- `$ git clone --recursive https://code.ascee.nl/ASCEE/lasp.git`
- `$ cd lasp`
- `$ pyproject-build`
Which will generate a `whl` in the `dist` folder, that is redistributable for Raspberry Pis that run Raspberry Pi OS.
When installing the `whl`, it appears that H5PY takes quite some time to install. To follow this process, run it it verbose mode.
# Installation - (x86_64) Windows (with WinPython), build with MSYS2
## Prerequisites

View File

@ -60,4 +60,4 @@ if(DEFINED PY_BUILD_CMAKE_MODULE_NAME)
COMPONENT python_modules
DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME})
endif()
endif()

View File

@ -107,37 +107,12 @@ void StreamMgr::rescanDAQDevices(bool background,
_pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback);
}
}
#if LASP_HAS_PORTAUDIO && LASP_HAS_PA_ALSA
#include <alsa/asoundlib.h>
void empty_handler(const char *file, int line, const char *function, int err,
const char *fmt, ...) {}
// Temporarily set the ALSA eror handler to something that does nothing, to
// prevent ALSA from spitting out all kinds of misconfiguration errors.
class MuteErrHandler {
private:
snd_lib_error_handler_t _default_handler;
public:
explicit MuteErrHandler() {
_default_handler = snd_lib_error;
snd_lib_error_set_handler(empty_handler);
}
~MuteErrHandler() { snd_lib_error_set_handler(_default_handler); }
};
#else
// Does nothin in case of no ALSA
class MuteErrHandler {};
#endif
void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) {
DEBUGTRACE_ENTER;
assert(!_inputStream && !_outputStream);
Lck lck(_mtx);
// Alsa spits out annoying messages that are not useful
{
MuteErrHandler guard;
_devices = DeviceInfo::getDeviceInfo();
}

View File

@ -16,6 +16,33 @@ using std::endl;
using std::string;
using std::to_string;
#if LASP_HAS_PA_ALSA
#include <alsa/asoundlib.h>
void empty_handler(const char *file, int line, const char *function, int err,
const char *fmt, ...) {
// cerr << "Test empty error handler...\n";
}
// Temporarily set the ALSA eror handler to something that does nothing, to
// prevent ALSA from spitting out all kinds of misconfiguration errors.
class MuteErrHandler {
private:
snd_lib_error_handler_t _default_handler;
public:
explicit MuteErrHandler() {
_default_handler = snd_lib_error;
snd_lib_error_set_handler(empty_handler);
}
~MuteErrHandler() { snd_lib_error_set_handler(_default_handler); }
};
#else
// Does nothin in case of no ALSA
class MuteErrHandler {};
#endif
inline void throwIfError(PaError e) {
DEBUGTRACE_ENTER;
if (e != paNoError) {
@ -44,6 +71,7 @@ class OurPaDeviceInfo : public DeviceInfo {
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
DEBUGTRACE_ENTER;
bool shouldPaTerminate = false;
MuteErrHandler guard;
try {
PaError err = Pa_Initialize();
/// PortAudio says that Pa_Terminate() should not be called whenever there
@ -348,6 +376,7 @@ PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
DEBUGTRACE_ENTER;
assert(_stream);
MuteErrHandler guard;
if (Pa_IsStreamActive(_stream)) {
throw rte("Stream is already running");

View File

@ -5,7 +5,7 @@ requires-python = ">=3.10"
description = "Library for Acoustic Signal Processing"
license = { "file" = "LICENSE" }
authors = [{ "name" = "J.A. de Jong", "email" = "j.a.dejong@ascee.nl" }]
version = "1.6.6"
version = "1.6.8"
keywords = ["DSP", "DAQ", "Signal processing"]

View File

@ -10,11 +10,11 @@ from .lasp_cpp import *
# from .lasp_imptube import * # TwoMicImpedanceTube
from .lasp_measurement import * # Measurement, scaleBlockSens
from .lasp_octavefilter import *
from .lasp_octavefilter import * # OverallFilterBank, SosOctaveFilterBank, SosThirdOctaveFilterBank
from .lasp_slm import * # SLM, Dummy
from .lasp_record import * # RecordStatus, Recording
from .lasp_daqconfigs import *
from .lasp_measurementset import *
from .lasp_daqconfigs import * # DaqConfigurations
from .lasp_measurementset import * # MeasurementSet
# from .lasp_siggen import * # SignalType, NoiseType, SiggenMessage, SiggenData, Siggen
# from .lasp_weighcal import * # WeighCal

View File

@ -121,7 +121,7 @@ class SIQtys(Enum):
unit_name='Pascal',
unit_symb='Pa',
level_unit=('dB SPL','dBPa'),
level_ref_name=('2 micropascal', '1 pascal',),
level_ref_name=('20 micropascal', '1 pascal',),
level_ref_value=(P_REF, 1),
cpp_enum = DaqChannel.Qty.AcousticPressure
)

View File

@ -16,13 +16,28 @@ else:
LASP_NUMPY_COMPLEX_TYPE = np.float64
def zeros(shape):
return np.zeros(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order='F')
def zeros(shape, dtype=float, order='F'):
if dtype == float:
return np.zeros(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order=order)
elif dtype == complex:
return np.zeros(shape, dtype=LASP_NUMPY_COMPLEX_TYPE, order=order)
else:
raise RuntimeError(f"Unknown dtype: {dtype}")
def ones(shape):
return np.ones(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order='F')
def ones(shape, dtype=float, order='F'):
if dtype == float:
return np.ones(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order=order)
elif dtype == complex:
return np.ones(shape, dtype=LASP_NUMPY_COMPLEX_TYPE, order=order)
else:
raise RuntimeError(f"Unknown dtype: {dtype}")
def empty(shape, dtype=float, order='F'):
if dtype == float:
return np.empty(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order=order)
elif dtype == complex:
return np.empty(shape, dtype=LASP_NUMPY_COMPLEX_TYPE, order=order)
else:
raise RuntimeError(f"Unknown dtype: {dtype}")
def empty(shape):
return np.empty(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order='F')

View File

@ -77,9 +77,9 @@ class MeasurementType(Enum):
Measurement flags related to the measurement. Stored as bit flags in the measurement file. This is for possible changes in the API later.
"""
# Not specific measurement type
# Not specific measurement type
NotSpecific = 0
# Measurement serves as an insertion loss reference measurement
ILReference = 1 << 0
@ -1156,11 +1156,8 @@ class Measurement:
if data.ndim != 2:
data = data[:, np.newaxis]
try:
len(sensitivity)
except:
raise ValueError("Sensitivity should be given as array-like data type")
sensitivity = np.asarray(sensitivity)
if not (isinstance(sensitivity, np.ndarray) and sensitivity.ndim >= 1):
sensitivity = np.asarray(sensitivity)[np.newaxis]
nchannels = data.shape[1]
if nchannels != sensitivity.shape[0]:
@ -1173,7 +1170,7 @@ class Measurement:
raise RuntimeError("Illegal length of channelNames list given")
if qtys is None:
qtys = [SIQtys.AP] * nchannels
qtys = [SIQtys.fromInt(1)] * nchannels # Acoustic pressure is default
else:
if len(qtys) != nchannels:
raise RuntimeError("Illegal length of qtys list given")

View File

@ -6,12 +6,10 @@ Author: J.A. de Jong - ASCEE
Provides the implementations of (fractional) octave filter banks
"""
__all__ = ['FirOctaveFilterBank', 'FirThirdOctaveFilterBank',
'OverallFilterBank', 'SosOctaveFilterBank',
'SosThirdOctaveFilterBank']
from .filter.filterbank_design import (OctaveBankDesigner,
ThirdOctaveBankDesigner)
__all__ = ["OverallFilterBank", "SosOctaveFilterBank", "SosThirdOctaveFilterBank"]
from .filter.filterbank_design import OctaveBankDesigner, ThirdOctaveBankDesigner
from .lasp_cpp import BiquadBank
import numpy as np
@ -19,7 +17,7 @@ import numpy as np
class OverallFilterBank:
"""
Dummy type filter bank. Does nothing special, only returns output in a
sensible way
way compatible with SosFilterBank.
"""
def __init__(self, fs):
@ -51,174 +49,13 @@ class OverallFilterBank:
t = np.linspace(tstart, tend, Ncur, endpoint=False)
self.N += Ncur
output['Overall'] = {'t': t, 'data': data, 'x': 0}
output["Overall"] = {"t": t, "data": data, "x": 0}
return output
def decimation(self, x):
return [1]
class FirFilterBank:
"""
Single channel (fractional) octave filter bank implementation, based on FIR
filters and sample rate decimation.
"""
def __init__(self, fs, xmin, xmax):
"""
Initialize a OctaveFilterBank object.
Args:
fs: Sampling frequency of base signal
"""
assert np.isclose(fs, 48000), "Only sampling frequency" \
" available is 48 kHz"
self.fs = fs
self.xs = list(range(xmin, xmax + 1))
raise RuntimeError('Not working code anymore')
maxdecimation = self.designer.firDecimation(self.xs[0])
self.decimators = []
for dec in maxdecimation:
self.decimators.append(Decimator(1, dec))
xs_d1 = []
xs_d4 = []
xs_d16 = []
xs_d64 = []
xs_d256 = []
self.filterbanks = []
# Sort the x values in categories according to the required decimation
for x in self.xs:
dec = self.designer.firDecimation(x)
if len(dec) == 1 and dec[0] == 1:
xs_d1.append(x)
elif len(dec) == 1 and dec[0] == 4:
xs_d4.append(x)
elif len(dec) == 2:
xs_d16.append(x)
elif len(dec) == 3:
xs_d64.append(x)
elif len(dec) == 4:
xs_d256.append(x)
else:
raise ValueError(f'No decimation found for x={x}')
xs_all = [xs_d1, xs_d4, xs_d16, xs_d64, xs_d256]
for xs in xs_all:
nominals_txt = []
if len(xs) > 0:
firs = np.empty((self.designer.firFilterLength, len(xs)), order='F')
for i, x in enumerate(xs):
# These are the filters that do not require lasp_decimation
# prior to filtering
nominals_txt.append(self.designer.nominal_txt(x))
firs[:, i] = self.designer.createFirFilter(x)
filterbank = {'fb': pyxFilterBank(firs, 1024),
'xs': xs,
'nominals': nominals_txt}
self.filterbanks.append(filterbank)
# Sample input counter.
self.N = 0
self.dec = [1, 4, 16, 64, 256]
# Filter output counters
# These intial delays are found 'experimentally' using a toneburst
# response.
self.Nf = [915, 806, 780, 582, 338]
def filterd(self, dec_stage, data):
"""
Filter data for a given decimation stage
Args:
dec_stage: decimation stage
data: Pre-filtered data
"""
output = {}
if data.shape[0] == 0:
return output
filtered = self.filterbanks[dec_stage]['fb'].filter_(data)
Nf = filtered.shape[0]
if Nf > 0:
dec = self.dec[dec_stage]
fd = self.fs/dec
oldNf = self.Nf[dec_stage]
tstart = oldNf/fd
tend = tstart + Nf/fd
t = np.linspace(tstart, tend, Nf, endpoint=False)
self.Nf[dec_stage] += Nf
for i, nom_txt in enumerate(self.filterbanks[dec_stage]['nominals']):
x = self.designer.nominal_txt_tox(nom_txt)
output[nom_txt] = {'t': t, 'data': filtered[:, [i]], 'x': x}
return output
def filter_(self, data):
"""
Filter input data
"""
assert data.ndim == 2
assert data.shape[1] == 1, "invalid number of channels, should be 1"
if data.shape[0] == 0:
return {}
# Output given as a dictionary with x as the key
output = {}
output_unsorted = {}
self.N += data.shape[0]
output_unsorted = {**output_unsorted, **self.filterd(0, data)}
for i in range(len(self.decimators)):
dec_stage = i+1
if data.shape[0] > 0:
# Apply a decimation stage
data = self.decimators[i].decimate(data)
output_unsorted = {**output_unsorted,
**self.filterd(dec_stage, data)}
# Create sorted output
for x in self.xs:
nom_txt = self.designer.nominal_txt(x)
output[nom_txt] = output_unsorted[nom_txt]
return output
def decimation(self, x):
return self.designer.firDecimation(x)
class FirOctaveFilterBank(FirFilterBank):
"""
Filter bank which uses FIR filtering for each octave frequency band
"""
def __init__(self, fs, xmin, xmax):
self.designer = OctaveBankDesigner(fs)
FirFilterBank.__init__(self, fs, xmin, xmax)
class FirThirdOctaveFilterBank(FirFilterBank):
"""
Filter bank which uses FIR filtering for each one-third octave frequency
band.
"""
def __init__(self, fs, xmin, xmax):
self.designer = ThirdOctaveBankDesigner(fs)
FirFilterBank.__init__(self, fs, xmin, xmax)
class SosFilterBank:
def __init__(self, fs, xmin, xmax):
"""
@ -247,7 +84,7 @@ class SosFilterBank:
channel = self.designer.createSOSFilter(x)
if sos is None:
sos = empty((channel.size, len(self.xs)))
sos[:, i] = channel.flatten()
sos[:, i] = channel.flatten()
self._fb = BiquadBank(sos)