Merge remote-tracking branch 'refs/remotes/origin/develop' into develop
This commit is contained in:
commit
7cd3dcffa8
@ -12,7 +12,7 @@ jobs:
|
|||||||
- lasp_dist:/dist
|
- lasp_dist:/dist
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
|
@ -1,17 +1,27 @@
|
|||||||
cmake_minimum_required (VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
project(LASP LANGUAGES C CXX VERSION 1.1)
|
project(LASP LANGUAGES C CXX VERSION 1.6.3)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED)
|
set(CMAKE_CXX_STANDARD_REQUIRED)
|
||||||
|
|
||||||
option(LASP_DOUBLE_PRECISION "Compile as double precision floating point" ON)
|
if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64")
|
||||||
|
set(RPI TRUE)
|
||||||
|
else()
|
||||||
|
set(RPI FALSE)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Setting defaults for PortAudio and RtAudio backend, depending on Linux /
|
# Setting defaults for PortAudio and RtAudio backend, depending on Linux /
|
||||||
# Windows.
|
# Windows.
|
||||||
|
set(DEFAULT_DOUBLE_PRECISION ON)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(DEFAULT_RTAUDIO OFF)
|
set(DEFAULT_RTAUDIO OFF)
|
||||||
set(DEFAULT_PORTAUDIO ON)
|
set(DEFAULT_PORTAUDIO ON)
|
||||||
set(DEFAULT_ULDAQ OFF)
|
set(DEFAULT_ULDAQ OFF)
|
||||||
|
elseif(${RPI})
|
||||||
|
set(DEFAULT_RTAUDIO OFF)
|
||||||
|
set(DEFAULT_PORTAUDIO ON)
|
||||||
|
set(DEFAULT_ULDAQ OFF)
|
||||||
|
set(DEFAULT_DOUBLE_PRECISION OFF)
|
||||||
else()
|
else()
|
||||||
set(DEFAULT_RTAUDIO OFF)
|
set(DEFAULT_RTAUDIO OFF)
|
||||||
set(DEFAULT_PORTAUDIO ON)
|
set(DEFAULT_PORTAUDIO ON)
|
||||||
@ -19,6 +29,7 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
option(LASP_DOUBLE_PRECISION "Compile as double precision floating point" ${DEFAULT_DOUBLE_PRECISION})
|
||||||
option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ${DEFAULT_RTAUDIO})
|
option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ${DEFAULT_RTAUDIO})
|
||||||
option(LASP_HAS_PORTAUDIO "Compile with PortAudio Daq backend" ${DEFAULT_PORTAUDIO})
|
option(LASP_HAS_PORTAUDIO "Compile with PortAudio Daq backend" ${DEFAULT_PORTAUDIO})
|
||||||
if(LASP_HAS_PORTAUDIO AND LASP_HAS_RTAUDIO)
|
if(LASP_HAS_PORTAUDIO AND LASP_HAS_RTAUDIO)
|
||||||
@ -34,7 +45,7 @@ option(LASP_BUILD_CPP_TESTS "Build CPP test code" OFF)
|
|||||||
find_program(CCACHE_PROGRAM ccache)
|
find_program(CCACHE_PROGRAM ccache)
|
||||||
if(CCACHE_PROGRAM)
|
if(CCACHE_PROGRAM)
|
||||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
|
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
|
||||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CCACHE_PROGRAM}")
|
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CCACHE_PROGRAM}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# To allow linking to static libs from other directories
|
# To allow linking to static libs from other directories
|
||||||
@ -80,7 +91,7 @@ endif()
|
|||||||
# Tune for current machine
|
# Tune for current machine
|
||||||
if(LASP_BUILD_TUNED)
|
if(LASP_BUILD_TUNED)
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
message(FATAL_ERROR
|
message(FATAL_ERROR
|
||||||
"Cannot build optimized and tuned code when debug is switched on")
|
"Cannot build optimized and tuned code when debug is switched on")
|
||||||
endif()
|
endif()
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -mtune=native")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -mtune=native")
|
||||||
@ -105,13 +116,17 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# ###################################### Compilation flags
|
# ###################################### Compilation flags
|
||||||
set(CMAKE_C_FLAGS_RELEASE "-O3 -flto -mfpmath=sse -march=x86-64 -mtune=native \
|
set(CMAKE_C_FLAGS_RELEASE "-O3 -flto -mtune=native \
|
||||||
-fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions")
|
-fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits -Werror=return-type")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits -Werror=return-type")
|
||||||
|
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -flto -mfpmath=sse -march=x86-64 -mtune=native \
|
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -flto -mtune=native \
|
||||||
-fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions")
|
-fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions")
|
||||||
|
|
||||||
|
if(NOT ${RPI})
|
||||||
|
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -mfpmath=sse -march=x86-64")
|
||||||
|
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mfpmath=sse -march=x86-64")
|
||||||
|
endif()
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -Wall ")
|
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -Wall ")
|
||||||
|
|
||||||
# ############################# End compilation flags
|
# ############################# End compilation flags
|
||||||
@ -121,7 +136,7 @@ include(OSSpecific)
|
|||||||
include(rtaudio)
|
include(rtaudio)
|
||||||
include(portaudio)
|
include(portaudio)
|
||||||
include(uldaq)
|
include(uldaq)
|
||||||
#
|
#
|
||||||
add_definitions(-Dgsl_CONFIG_DEFAULTS_VERSION=1)
|
add_definitions(-Dgsl_CONFIG_DEFAULTS_VERSION=1)
|
||||||
add_subdirectory(cpp_src)
|
add_subdirectory(cpp_src)
|
||||||
if(LASP_BUILD_CPP_TESTS)
|
if(LASP_BUILD_CPP_TESTS)
|
||||||
|
48
README.md
48
README.md
@ -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).
|
If you have any question(s), please feel free to contact us: [email](info@ascee.nl).
|
||||||
|
|
||||||
|
|
||||||
# Installation - Linux (Ubuntu-based)
|
# Installation - Linux (Ubuntu-based)
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
Run the following on the command line to install all prerequisites on
|
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`
|
- `sudo apt install python3-pip libfftw3-3 libopenblas-base libusb-1.0-0 libpulse0`
|
||||||
|
|
||||||
|
|
||||||
## Installation from wheel (recommended for non-developers)
|
## Installation from wheel (recommended for non-developers)
|
||||||
|
|
||||||
Go to: [LASP releases](https://code.ascee.nl/ASCEE/lasp/releases/latest/) and
|
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`
|
- `$ cd lasp`
|
||||||
- `pip install -e .`
|
- `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
|
# Installation - (x86_64) Windows (with WinPython), build with MSYS2
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
@ -144,3 +168,25 @@ This will build the documentation. It can be read by:
|
|||||||
|
|
||||||
- See examples directories for IPython notebooks.
|
- See examples directories for IPython notebooks.
|
||||||
- Please refer to the [documentation](https://lasp.ascee.nl/) for features.
|
- Please refer to the [documentation](https://lasp.ascee.nl/) for features.
|
||||||
|
|
||||||
|
|
||||||
|
# Development docs
|
||||||
|
|
||||||
|
## Bumping version number
|
||||||
|
|
||||||
|
When bumping the version number, please update the number in
|
||||||
|
|
||||||
|
- `pyproject.toml`
|
||||||
|
- `CMakeLists.txt`
|
||||||
|
|
||||||
|
Then, create a commit with tag `vX.X.X`, and push it.
|
||||||
|
|
||||||
|
## Updating to latest version (editable mode)
|
||||||
|
|
||||||
|
When updating to the latest version of LASP in editable mode:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
- $ git pull
|
||||||
|
- $ git submodule update
|
||||||
|
- $ pip install -e . -v
|
||||||
|
```
|
@ -107,37 +107,12 @@ void StreamMgr::rescanDAQDevices(bool background,
|
|||||||
_pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback);
|
_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) {
|
void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
assert(!_inputStream && !_outputStream);
|
assert(!_inputStream && !_outputStream);
|
||||||
Lck lck(_mtx);
|
Lck lck(_mtx);
|
||||||
// Alsa spits out annoying messages that are not useful
|
// Alsa spits out annoying messages that are not useful
|
||||||
{
|
{
|
||||||
MuteErrHandler guard;
|
|
||||||
|
|
||||||
_devices = DeviceInfo::getDeviceInfo();
|
_devices = DeviceInfo::getDeviceInfo();
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,33 @@ using std::endl;
|
|||||||
using std::string;
|
using std::string;
|
||||||
using std::to_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) {
|
inline void throwIfError(PaError e) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
if (e != paNoError) {
|
if (e != paNoError) {
|
||||||
@ -44,6 +71,7 @@ class OurPaDeviceInfo : public DeviceInfo {
|
|||||||
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
bool shouldPaTerminate = false;
|
bool shouldPaTerminate = false;
|
||||||
|
MuteErrHandler guard;
|
||||||
try {
|
try {
|
||||||
PaError err = Pa_Initialize();
|
PaError err = Pa_Initialize();
|
||||||
/// PortAudio says that Pa_Terminate() should not be called whenever there
|
/// 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) {
|
void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
assert(_stream);
|
assert(_stream);
|
||||||
|
MuteErrHandler guard;
|
||||||
|
|
||||||
if (Pa_IsStreamActive(_stream)) {
|
if (Pa_IsStreamActive(_stream)) {
|
||||||
throw rte("Stream is already running");
|
throw rte("Stream is already running");
|
||||||
|
@ -11,7 +11,7 @@ using std::cerr;
|
|||||||
using std::endl;
|
using std::endl;
|
||||||
|
|
||||||
// Safe some typing. Linspace form 0 up to (and NOT including N).
|
// Safe some typing. Linspace form 0 up to (and NOT including N).
|
||||||
#define lin0N arma::linspace(0, N - 1, N)
|
#define lin0N arma::linspace<vd>(0, N - 1, N)
|
||||||
|
|
||||||
vd Window::hann(const us N) {
|
vd Window::hann(const us N) {
|
||||||
return arma::pow(arma::sin((arma::datum::pi/N) * lin0N), 2);
|
return arma::pow(arma::sin((arma::datum::pi/N) * lin0N), 2);
|
||||||
@ -24,8 +24,8 @@ vd Window::blackman(const us N) {
|
|||||||
d a0 = 7938. / 18608.;
|
d a0 = 7938. / 18608.;
|
||||||
d a1 = 9240. / 18608.;
|
d a1 = 9240. / 18608.;
|
||||||
d a2 = 1430. / 18608.;
|
d a2 = 1430. / 18608.;
|
||||||
return a0 - a1 * d_cos((2 * number_pi/N) * lin0N) +
|
return a0 - a1 * arma::cos((2 * number_pi/N) * lin0N) +
|
||||||
a2 * d_cos((4 * number_pi / N)* lin0N );
|
a2 * arma::cos((4 * number_pi / N)* lin0N );
|
||||||
}
|
}
|
||||||
|
|
||||||
vd Window::rectangular(const us N) { return arma::ones(N); }
|
vd Window::rectangular(const us N) { return arma::ones(N); }
|
||||||
|
@ -44,6 +44,12 @@ void init_siggen(py::module &m);
|
|||||||
|
|
||||||
PYBIND11_MODULE(lasp_cpp, m) {
|
PYBIND11_MODULE(lasp_cpp, m) {
|
||||||
|
|
||||||
|
#if LASP_DOUBLE_PRECISION == 1
|
||||||
|
m.attr("LASP_DOUBLE_PRECISION") = true;
|
||||||
|
#else
|
||||||
|
m.attr("LASP_DOUBLE_PRECISION") = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
init_dsp(m);
|
init_dsp(m);
|
||||||
init_deviceinfo(m);
|
init_deviceinfo(m);
|
||||||
init_daqconfiguration(m);
|
init_daqconfiguration(m);
|
||||||
@ -51,6 +57,5 @@ PYBIND11_MODULE(lasp_cpp, m) {
|
|||||||
init_streammgr(m);
|
init_streammgr(m);
|
||||||
init_datahandler(m);
|
init_datahandler(m);
|
||||||
init_siggen(m);
|
init_siggen(m);
|
||||||
|
|
||||||
}
|
}
|
||||||
/** @} */
|
/** @} */
|
@ -5,7 +5,7 @@ requires-python = ">=3.10"
|
|||||||
description = "Library for Acoustic Signal Processing"
|
description = "Library for Acoustic Signal Processing"
|
||||||
license = { "file" = "LICENSE" }
|
license = { "file" = "LICENSE" }
|
||||||
authors = [{ "name" = "J.A. de Jong", "email" = "j.a.dejong@ascee.nl" }]
|
authors = [{ "name" = "J.A. de Jong", "email" = "j.a.dejong@ascee.nl" }]
|
||||||
version = "1.6.1"
|
version = "1.6.6"
|
||||||
|
|
||||||
keywords = ["DSP", "DAQ", "Signal processing"]
|
keywords = ["DSP", "DAQ", "Signal processing"]
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ classifiers = [
|
|||||||
urls = { "Documentation" = "https://lasp.ascee.nl" }
|
urls = { "Documentation" = "https://lasp.ascee.nl" }
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"scipy==1.12",
|
"scipy>=1.13.1",
|
||||||
"matplotlib>=3.7.2",
|
"matplotlib>=3.7.2",
|
||||||
"appdirs",
|
"appdirs",
|
||||||
"dataclasses_json",
|
"dataclasses_json",
|
||||||
|
@ -11,7 +11,9 @@ __all__ = ['freqResponse', 'bandpass_fir_design', 'lowpass_fir_design',
|
|||||||
'arbitrary_fir_design']
|
'arbitrary_fir_design']
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from scipy.signal import freqz, hann, firwin2
|
from scipy.signal import freqz, firwin2
|
||||||
|
from scipy.signal.windows import hann
|
||||||
|
from ..lasp_config import empty
|
||||||
|
|
||||||
|
|
||||||
def freqResponse(fs, freq, coefs_b, coefs_a=1.):
|
def freqResponse(fs, freq, coefs_b, coefs_a=1.):
|
||||||
@ -44,7 +46,7 @@ def bandpass_fir_design(L, fs, fl, fu, window=hann):
|
|||||||
Omg2 = 2*np.pi*fu/fs
|
Omg2 = 2*np.pi*fu/fs
|
||||||
Omg1 = 2*np.pi*fl/fs
|
Omg1 = 2*np.pi*fl/fs
|
||||||
|
|
||||||
fir = np.empty(L, dtype=float)
|
fir = empty(L, dtype=float)
|
||||||
|
|
||||||
# First Create ideal band-pass filter
|
# First Create ideal band-pass filter
|
||||||
fir[L//2] = (Omg2-Omg1)/np.pi
|
fir[L//2] = (Omg2-Omg1)/np.pi
|
||||||
@ -64,7 +66,7 @@ def lowpass_fir_design(L, fs, fc, window=hann):
|
|||||||
" than upper cut-off"
|
" than upper cut-off"
|
||||||
|
|
||||||
Omgc = 2*np.pi*fc/fs
|
Omgc = 2*np.pi*fc/fs
|
||||||
fir = np.empty(L, dtype=float)
|
fir = empty(L, dtype=float)
|
||||||
|
|
||||||
# First Create ideal band-pass filter
|
# First Create ideal band-pass filter
|
||||||
fir[L//2] = Omgc/np.pi
|
fir[L//2] = Omgc/np.pi
|
||||||
|
@ -6,8 +6,14 @@ Author: J.A. de Jong - ASCEE
|
|||||||
Description: LASP configuration
|
Description: LASP configuration
|
||||||
"""
|
"""
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from .lasp_cpp import LASP_DOUBLE_PRECISION
|
||||||
|
|
||||||
LASP_NUMPY_FLOAT_TYPE = np.float64
|
if LASP_DOUBLE_PRECISION:
|
||||||
|
LASP_NUMPY_FLOAT_TYPE = np.float64
|
||||||
|
LASP_NUMPY_COMPLEX_TYPE = np.complex128
|
||||||
|
else:
|
||||||
|
LASP_NUMPY_FLOAT_TYPE = np.float32
|
||||||
|
LASP_NUMPY_COMPLEX_TYPE = np.float64
|
||||||
|
|
||||||
|
|
||||||
def zeros(shape):
|
def zeros(shape):
|
||||||
|
@ -1,173 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""!
|
|
||||||
Author: J.A. de Jong - ASCEE
|
|
||||||
|
|
||||||
Description: Two-microphone impedance tube methods
|
|
||||||
"""
|
|
||||||
__all__ = ['TwoMicImpedanceTube']
|
|
||||||
# from lrftubes import Air
|
|
||||||
from .lasp_measurement import Measurement
|
|
||||||
from numpy import pi, sqrt, exp
|
|
||||||
import numpy as np
|
|
||||||
from scipy.interpolate import UnivariateSpline
|
|
||||||
# from lrftubes import PrsDuct
|
|
||||||
from functools import lru_cache
|
|
||||||
|
|
||||||
class TwoMicImpedanceTube:
|
|
||||||
def __init__(self, mnormal: Measurement,
|
|
||||||
mswitched: Measurement,
|
|
||||||
s: float,
|
|
||||||
d1: float,
|
|
||||||
d2: float,
|
|
||||||
fl: float = None,
|
|
||||||
fu: float = None,
|
|
||||||
periodic_method=False,
|
|
||||||
# mat= Air(),
|
|
||||||
D_imptube = 50e-3,
|
|
||||||
thermoviscous = True,
|
|
||||||
**kwargs):
|
|
||||||
|
|
||||||
"""
|
|
||||||
Initialize two-microphone impedance tube methods
|
|
||||||
|
|
||||||
Args:
|
|
||||||
mnormal: Measurement in normal configuration
|
|
||||||
mswitched: Measurement in normal configuration
|
|
||||||
s: Microphone distance
|
|
||||||
fl: Lower evaluation frequency
|
|
||||||
fu: Lower evaluation frequency
|
|
||||||
|
|
||||||
kwargs: tuple with extra arguments, of which:
|
|
||||||
N: Period length of periodic excitation *obligatory*
|
|
||||||
chan0: Measurement channel index of mic 0
|
|
||||||
chan1: Measurement channel index of mic 1
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.mnormal = mnormal
|
|
||||||
self.mswitched = mswitched
|
|
||||||
|
|
||||||
self.mat = mat
|
|
||||||
self.s = s
|
|
||||||
self.d1 = d1
|
|
||||||
self.d2 = d2
|
|
||||||
|
|
||||||
self.fl = fl
|
|
||||||
if fl is None:
|
|
||||||
ksmin = 0.1*pi
|
|
||||||
kmin = ksmin/s
|
|
||||||
self.fl = kmin*mat.c0/2/pi
|
|
||||||
|
|
||||||
self.fu = fu
|
|
||||||
if fu is None:
|
|
||||||
ksmax = 0.8*pi
|
|
||||||
kmax = ksmax/s
|
|
||||||
self.fu = kmax*mat.c0/2/pi
|
|
||||||
|
|
||||||
self.thermoviscous = thermoviscous
|
|
||||||
self.D_imptube = D_imptube
|
|
||||||
|
|
||||||
self.periodic_method = periodic_method
|
|
||||||
self.channels = [kwargs.pop('chan0', 0), kwargs.pop('chan1', 1)]
|
|
||||||
# Compute calibration correction
|
|
||||||
if periodic_method:
|
|
||||||
self.N = kwargs.pop('N')
|
|
||||||
freq, C1 = mnormal.periodicCPS(self.N, channels=self.channels)
|
|
||||||
freq, C2 = mswitched.periodicCPS(self.N, channels=self.channels)
|
|
||||||
else:
|
|
||||||
self.nfft = kwargs.pop('nfft', 16000)
|
|
||||||
freq, C1 = mnormal.CPS(nfft=self.nfft, channels=self.channels)
|
|
||||||
freq, C2 = mswitched.CPS(nfft=self.nfft, channels=self.channels)
|
|
||||||
|
|
||||||
# Store this, as it is often used for just a single sample.
|
|
||||||
self.C1 = C1
|
|
||||||
self.freq = freq
|
|
||||||
|
|
||||||
self.il = np.where(self.freq<= self.fl)[0][-1]
|
|
||||||
self.ul = np.where(self.freq > self.fu)[0][0]
|
|
||||||
|
|
||||||
# Calibration correction factor
|
|
||||||
# self.K = 0*self.freq + 1.0
|
|
||||||
K = sqrt(C2[:,0,1]*C1[:,0,0]/(C2[:,1,1]*C1[:,1,0]))
|
|
||||||
# self.K = UnivariateSpline(self.freq, K.real)(self.freq) +\
|
|
||||||
# 1j*UnivariateSpline(self.freq, K.imag)(self.freq)
|
|
||||||
self.K = K
|
|
||||||
|
|
||||||
def cut_to_limits(self, ar):
|
|
||||||
return ar[self.il:self.ul]
|
|
||||||
|
|
||||||
def getFreq(self):
|
|
||||||
"""
|
|
||||||
Array of frequencies, cut to limits of validity
|
|
||||||
"""
|
|
||||||
return self.cut_to_limits(self.freq)
|
|
||||||
|
|
||||||
@lru_cache
|
|
||||||
def G_AB(self, meas):
|
|
||||||
if meas is self.mnormal:
|
|
||||||
C = self.C1
|
|
||||||
freq = self.freq
|
|
||||||
else:
|
|
||||||
if self.periodic_method:
|
|
||||||
freq, C = meas.periodicCPS(self.N, self.channels)
|
|
||||||
else:
|
|
||||||
freq, C = meas.CPS(nfft=self.nfft, channels=self.channels)
|
|
||||||
|
|
||||||
# Microphone transfer function
|
|
||||||
G_AB = self.K*C[:,1,0]/C[:,0,0]
|
|
||||||
return self.getFreq(), self.cut_to_limits(G_AB)
|
|
||||||
|
|
||||||
def k(self, freq):
|
|
||||||
"""
|
|
||||||
Wave number, or thermoviscous wave number
|
|
||||||
"""
|
|
||||||
if self.thermoviscous:
|
|
||||||
D = self.D_imptube
|
|
||||||
S = pi/4*D**2
|
|
||||||
d = PrsDuct(0, S=S, rh=D/4, cs='circ')
|
|
||||||
d.mat = self.mat
|
|
||||||
omg = 2*pi*freq
|
|
||||||
G, Z = d.GammaZc(omg)
|
|
||||||
return G
|
|
||||||
else:
|
|
||||||
return 2*pi*freq/self.mat.c0
|
|
||||||
|
|
||||||
|
|
||||||
def R(self, meas):
|
|
||||||
freq, G_AB = self.G_AB(meas)
|
|
||||||
s = self.s
|
|
||||||
k = self.k(freq)
|
|
||||||
d1 = self.d1
|
|
||||||
RpA = (G_AB - exp(-1j*k*s))/(exp(1j*k*s)-G_AB)
|
|
||||||
|
|
||||||
R = RpA*exp(2*1j*k*(s+d1))
|
|
||||||
|
|
||||||
return freq, R
|
|
||||||
|
|
||||||
def alpha(self, meas):
|
|
||||||
"""
|
|
||||||
Acoustic absorption coefficient
|
|
||||||
"""
|
|
||||||
freq, R = self.R(meas)
|
|
||||||
return freq, 1 - np.abs(R)**2
|
|
||||||
|
|
||||||
def z(self, meas):
|
|
||||||
"""
|
|
||||||
Acoustic impedance at the position of the sample, in front of the sample
|
|
||||||
"""
|
|
||||||
freq, R = self.R(meas)
|
|
||||||
return freq, self.mat.z0*(1+R)/(1-R)
|
|
||||||
|
|
||||||
def zs(self, meas):
|
|
||||||
"""
|
|
||||||
Sample impedance jump, assuming a cavity behind the sample with
|
|
||||||
thickness d2
|
|
||||||
"""
|
|
||||||
freq, R = self.R(meas)
|
|
||||||
z0 = self.mat.z0
|
|
||||||
k = 2*pi*freq/self.mat.c0
|
|
||||||
d2 = self.d2
|
|
||||||
|
|
||||||
zs = 2*z0*(1-R*exp(2*1j*k*d2))/((R-1)*(exp(2*d2*k*1j)-1))
|
|
||||||
return freq, zs
|
|
||||||
|
|
@ -1,6 +1,24 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from weakref import WeakValueDictionary
|
||||||
|
import h5py as h5
|
||||||
|
import uuid
|
||||||
|
import pathlib
|
||||||
|
import glob
|
||||||
|
import itertools
|
||||||
|
import numpy as np
|
||||||
|
from enum import Enum, unique
|
||||||
|
from .lasp_config import LASP_NUMPY_FLOAT_TYPE
|
||||||
|
from scipy.io import wavfile
|
||||||
|
import os, time, wave, logging
|
||||||
|
from .lasp_common import SIQtys, Qty, getFreq
|
||||||
|
from .lasp_version import LASP_VERSION_MAJOR, LASP_VERSION_MINOR
|
||||||
|
from .lasp_cpp import Window, DaqChannel, AvPowerSpectra
|
||||||
|
from typing import List
|
||||||
|
from functools import lru_cache
|
||||||
|
from .lasp_config import ones
|
||||||
"""!
|
"""!
|
||||||
Author: J.A. de Jong - ASCEE
|
Author: J.A. de Jong - ASCEE
|
||||||
|
|
||||||
@ -46,28 +64,11 @@ The video dataset can possibly be not present in the data.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__all__ = ["Measurement", "scaleBlockSens", "MeasurementType"]
|
|
||||||
from contextlib import contextmanager
|
|
||||||
from weakref import WeakValueDictionary
|
|
||||||
import h5py as h5
|
|
||||||
import uuid
|
|
||||||
import pathlib
|
|
||||||
import glob
|
|
||||||
import itertools
|
|
||||||
import numpy as np
|
|
||||||
from enum import Enum, unique
|
|
||||||
from .lasp_config import LASP_NUMPY_FLOAT_TYPE
|
|
||||||
from scipy.io import wavfile
|
|
||||||
import os, time, wave, logging
|
|
||||||
from .lasp_common import SIQtys, Qty, getFreq
|
|
||||||
from .lasp_version import LASP_VERSION_MAJOR, LASP_VERSION_MINOR
|
|
||||||
from .lasp_cpp import Window, DaqChannel, AvPowerSpectra
|
|
||||||
from typing import List
|
|
||||||
from functools import lru_cache
|
|
||||||
|
|
||||||
|
__all__ = ["Measurement", "scaleBlockSens", "MeasurementType"]
|
||||||
# Measurement file extension
|
# Measurement file extension
|
||||||
MEXT = 'h5'
|
MEXT = "h5"
|
||||||
DOTMEXT = f'.{MEXT}'
|
DOTMEXT = f".{MEXT}"
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
@ -223,7 +224,7 @@ class IterData(IterRawData):
|
|||||||
|
|
||||||
def __init__(self, fa, channels, sensitivity, **kwargs):
|
def __init__(self, fa, channels, sensitivity, **kwargs):
|
||||||
super().__init__(fa, channels, **kwargs)
|
super().__init__(fa, channels, **kwargs)
|
||||||
self.sens = np.asarray(sensitivity)[self.channels]
|
self.sens = np.asarray(sensitivity, dtype=LASP_NUMPY_FLOAT_TYPE)[self.channels]
|
||||||
assert self.sens.ndim == 1
|
assert self.sens.ndim == 1
|
||||||
|
|
||||||
def __next__(self):
|
def __next__(self):
|
||||||
@ -235,7 +236,8 @@ class Measurement:
|
|||||||
"""Provides access to measurement data stored in the h5 measurement file
|
"""Provides access to measurement data stored in the h5 measurement file
|
||||||
format."""
|
format."""
|
||||||
|
|
||||||
# Store a dict of open measurements, with uuid string as a key. We store them as a weak ref.
|
# Store a dict of open measurements, with uuid string as a key. We store
|
||||||
|
# them as a weak ref.
|
||||||
uuid_s = WeakValueDictionary()
|
uuid_s = WeakValueDictionary()
|
||||||
|
|
||||||
def __init__(self, fn):
|
def __init__(self, fn):
|
||||||
@ -271,36 +273,36 @@ class Measurement:
|
|||||||
self.version_major = f.attrs["LASP_VERSION_MAJOR"]
|
self.version_major = f.attrs["LASP_VERSION_MAJOR"]
|
||||||
self.version_minor = f.attrs["LASP_VERSION_MINOR"]
|
self.version_minor = f.attrs["LASP_VERSION_MINOR"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# No version information stored
|
# No version information stored
|
||||||
self.version_major = 0
|
self.version_major = 0
|
||||||
self.version_minor = 1
|
self.version_minor = 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Try to catch UUID (Universally Unique IDentifier)
|
# Try to catch UUID (Universally Unique IDentifier)
|
||||||
self._UUID = f.attrs['UUID']
|
self._UUID = f.attrs["UUID"]
|
||||||
# Flag indicating we have to add a new UUID
|
# Flag indicating we have to add a new UUID
|
||||||
create_new_uuid = False
|
create_new_uuid = False
|
||||||
except KeyError:
|
except KeyError:
|
||||||
create_new_uuid = True
|
create_new_uuid = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# UUID of the reference measurement. Should be stored as
|
# UUID of the reference measurement. Should be stored as
|
||||||
# a lists of tuples, where each tuple is a combination of (<MeasurementType.value>, <uuid_string>, <last_filename>).
|
# a lists of tuples, where each tuple is a combination of (<MeasurementType.value>, <uuid_string>, <last_filename>).
|
||||||
|
|
||||||
# The last filename is a filename that *probably* is the reference measurement with
|
# The last filename is a filename that *probably* is the reference measurement with
|
||||||
# given UUID. If it is not, we will search for it in the same directory as `this` measurement.
|
# given UUID. If it is not, we will search for it in the same directory as `this` measurement.
|
||||||
# If we cannot find it there, we will give up, and remove the field corresponding to this reference measurement type.
|
# If we cannot find it there, we will give up, and remove the field corresponding to this reference measurement type.
|
||||||
refMeas_list = f.attrs['refMeas']
|
refMeas_list = f.attrs["refMeas"]
|
||||||
|
|
||||||
# Build a tuple string from it
|
# Build a tuple string from it
|
||||||
self._refMeas = {}
|
self._refMeas = {}
|
||||||
for (key, val, name) in refMeas_list:
|
for key, val, name in refMeas_list:
|
||||||
self._refMeas[MeasurementType(int(key))] = (val, name)
|
self._refMeas[MeasurementType(int(key))] = (val, name)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self._refMeas = {}
|
self._refMeas = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._type_int = f.attrs['type_int']
|
self._type_int = f.attrs["type_int"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self._type_int = 0
|
self._type_int = 0
|
||||||
|
|
||||||
@ -329,10 +331,10 @@ class Measurement:
|
|||||||
try:
|
try:
|
||||||
sens = f.attrs["sensitivity"]
|
sens = f.attrs["sensitivity"]
|
||||||
self._sens = (
|
self._sens = (
|
||||||
sens * np.ones(self.nchannels) if isinstance(sens, float) else sens
|
sens * ones(self.nchannels) if isinstance(sens, float) else sens
|
||||||
)
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self._sens = np.ones(self.nchannels)
|
self._sens = ones(self.nchannels)
|
||||||
|
|
||||||
# The time is cached AND ALWAYS ASSUMED TO BE AN IMMUTABLE OBJECT.
|
# The time is cached AND ALWAYS ASSUMED TO BE AN IMMUTABLE OBJECT.
|
||||||
# It is also cached. Changing the measurement timestamp should not
|
# It is also cached. Changing the measurement timestamp should not
|
||||||
@ -367,7 +369,9 @@ class Measurement:
|
|||||||
self.genNewUUID()
|
self.genNewUUID()
|
||||||
else:
|
else:
|
||||||
if self.UUID in Measurement.uuid_s.keys():
|
if self.UUID in Measurement.uuid_s.keys():
|
||||||
raise RuntimeError(f"Measurement '{self.name}' is already opened. Cannot open measurement twice. Note: this error can happen when measurements are manually copied.")
|
raise RuntimeError(
|
||||||
|
f"Measurement '{self.name}' is already opened. Cannot open measurement twice. Note: this error can happen when measurements are manually copied."
|
||||||
|
)
|
||||||
|
|
||||||
# Store weak reference to 'self' in list of UUID's. They are removed when no file is open anymore
|
# Store weak reference to 'self' in list of UUID's. They are removed when no file is open anymore
|
||||||
Measurement.uuid_s[self._UUID] = self
|
Measurement.uuid_s[self._UUID] = self
|
||||||
@ -379,11 +383,11 @@ class Measurement:
|
|||||||
Args:
|
Args:
|
||||||
newname: New name, with or without extension
|
newname: New name, with or without extension
|
||||||
"""
|
"""
|
||||||
_ , ext = os.path.splitext(newname)
|
_, ext = os.path.splitext(newname)
|
||||||
# Add proper extension if new name is given without extension.
|
# Add proper extension if new name is given without extension.
|
||||||
if ext != DOTMEXT:
|
if ext != DOTMEXT:
|
||||||
newname = newname + DOTMEXT
|
newname = newname + DOTMEXT
|
||||||
|
|
||||||
# Folder, Base filename + extension
|
# Folder, Base filename + extension
|
||||||
folder, _ = os.path.split(self.fn)
|
folder, _ = os.path.split(self.fn)
|
||||||
|
|
||||||
@ -396,7 +400,7 @@ class Measurement:
|
|||||||
"""
|
"""
|
||||||
Create new UUID for measurement and store in file.
|
Create new UUID for measurement and store in file.
|
||||||
"""
|
"""
|
||||||
self.setAttribute('UUID', str(uuid.uuid1()))
|
self.setAttribute("UUID", str(uuid.uuid1()))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def UUID(self):
|
def UUID(self):
|
||||||
@ -404,59 +408,73 @@ class Measurement:
|
|||||||
Universally unique identifier
|
Universally unique identifier
|
||||||
"""
|
"""
|
||||||
return self._UUID
|
return self._UUID
|
||||||
|
|
||||||
def getRefMeas(self, mtype: MeasurementType):
|
def getRefMeas(self, mtype: MeasurementType):
|
||||||
"""
|
"""
|
||||||
Return corresponding reference measurement, if configured and can be found. If the reference
|
Return corresponding reference measurement, if configured and can be found. If the reference
|
||||||
measurement is currently not open, it tries to open it by traversing other measurement
|
measurement is currently not open, it tries to open it by traversing other measurement
|
||||||
files in the current directory. Throws a runtime error in case the reference measurement cannot be found.
|
files in the current directory. Throws a runtime error in case the reference measurement cannot be found.
|
||||||
Throws a ValueError when the reference measurement is not configured.
|
Throws a ValueError when the reference measurement is not configured.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# See if we can find the UUID for the required measurement type
|
# See if we can find the UUID for the required measurement type
|
||||||
try:
|
try:
|
||||||
required_uuid, possible_name = self._refMeas[mtype]
|
required_uuid, possible_name = self._refMeas[mtype]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError(f"No reference measurement configured for '{self.name}'")
|
raise ValueError(f"No reference measurement configured for '{self.name}'")
|
||||||
|
|
||||||
m = None
|
m = None
|
||||||
# Try to find it in the dictionary of of open measurements
|
# Try to find it in the dictionary of of open measurements
|
||||||
if required_uuid in Measurement.uuid_s.keys():
|
if required_uuid in Measurement.uuid_s.keys():
|
||||||
m = Measurement.uuid_s[required_uuid]
|
m = Measurement.uuid_s[required_uuid]
|
||||||
logging.debug(f'Returned reference measurement {m.name} from list of open measurements')
|
logging.debug(
|
||||||
|
f"Returned reference measurement {m.name} from list of open measurements"
|
||||||
|
)
|
||||||
|
|
||||||
# Not found in list of openend measurements. See if we can open it using its last stored file name we know of
|
# Not found in list of openend measurements. See if we can open it using its last stored file name we know of
|
||||||
if m is None:
|
if m is None:
|
||||||
try:
|
try:
|
||||||
m = Measurement(possible_name)
|
m = Measurement(possible_name)
|
||||||
if m.UUID == required_uuid:
|
if m.UUID == required_uuid:
|
||||||
logging.info(f'Opened reference measurement {m.name} by name')
|
logging.info(f"Opened reference measurement {m.name} by name")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f'Could not find reference measurement using file name: {possible_name}')
|
logging.error(
|
||||||
|
f"Could not find reference measurement using file name: {possible_name}"
|
||||||
|
)
|
||||||
|
|
||||||
# Last resort, see if we can find the right measurement in the same folder
|
# Last resort, see if we can find the right measurement in the same folder
|
||||||
if m is None:
|
if m is None:
|
||||||
try:
|
try:
|
||||||
folder, _ = os.path.split(self.fn)
|
folder, _ = os.path.split(self.fn)
|
||||||
m = Measurement.fromFolderWithUUID(required_uuid, folder, skip=[self.name])
|
m = Measurement.fromFolderWithUUID(
|
||||||
logging.info('Found reference measurement in folder with correct UUID. Updating name of reference measurement')
|
required_uuid, folder, skip=[self.name]
|
||||||
|
)
|
||||||
|
logging.info(
|
||||||
|
"Found reference measurement in folder with correct UUID. Updating name of reference measurement"
|
||||||
|
)
|
||||||
# Update the measurement file name in the list, such that next time it
|
# Update the measurement file name in the list, such that next time it
|
||||||
# can be opened just by its name.
|
# can be opened just by its name.
|
||||||
self.setRefMeas(m)
|
self.setRefMeas(m)
|
||||||
except:
|
except:
|
||||||
logging.error("Could not find the reference measurement. Is it deleted?")
|
logging.error(
|
||||||
|
"Could not find the reference measurement. Is it deleted?"
|
||||||
|
)
|
||||||
|
|
||||||
# Well, we found it. Now make sure the reference measurement actually has the right type (User could have marked it as a NotSpecific for example in the mean time).
|
# Well, we found it. Now make sure the reference measurement actually has the right type (User could have marked it as a NotSpecific for example in the mean time).
|
||||||
if m is not None:
|
if m is not None:
|
||||||
if m.measurementType() != mtype:
|
if m.measurementType() != mtype:
|
||||||
m.removeRefMeas(mtype)
|
m.removeRefMeas(mtype)
|
||||||
raise RuntimeError(f"Reference measurement for {self.name} is not a proper reference (anymore).")
|
raise RuntimeError(
|
||||||
|
f"Reference measurement for {self.name} is not a proper reference (anymore)."
|
||||||
|
)
|
||||||
|
|
||||||
# Whow, we passed all security checks, here we go!
|
# Whow, we passed all security checks, here we go!
|
||||||
return m
|
return m
|
||||||
else:
|
else:
|
||||||
# Nope, not there.
|
# Nope, not there.
|
||||||
raise RuntimeError(f"Could not find the reference measurement for '{self.name}'. Is it deleted?")
|
raise RuntimeError(
|
||||||
|
f"Could not find the reference measurement for '{self.name}'. Is it deleted?"
|
||||||
|
)
|
||||||
|
|
||||||
def removeRefMeas(self, mtype: MeasurementType):
|
def removeRefMeas(self, mtype: MeasurementType):
|
||||||
"""
|
"""
|
||||||
@ -477,9 +495,12 @@ class Measurement:
|
|||||||
with self.file("r+") as f:
|
with self.file("r+") as f:
|
||||||
# Update attribute in file. Stored as a flat list of string tuples:
|
# Update attribute in file. Stored as a flat list of string tuples:
|
||||||
# [(ref_value1, uuid_1, name_1), (ref_value2, uuid_2, name_2), ...]
|
# [(ref_value1, uuid_1, name_1), (ref_value2, uuid_2, name_2), ...]
|
||||||
reflist = list((str(key.value), val1, val2) for key, (val1, val2) in self._refMeas.items())
|
reflist = list(
|
||||||
|
(str(key.value), val1, val2)
|
||||||
|
for key, (val1, val2) in self._refMeas.items()
|
||||||
|
)
|
||||||
# print(reflist)
|
# print(reflist)
|
||||||
f.attrs['refMeas'] = reflist
|
f.attrs["refMeas"] = reflist
|
||||||
|
|
||||||
def setRefMeas(self, m: Measurement):
|
def setRefMeas(self, m: Measurement):
|
||||||
"""
|
"""
|
||||||
@ -489,32 +510,36 @@ class Measurement:
|
|||||||
"""
|
"""
|
||||||
mtype = m.measurementType()
|
mtype = m.measurementType()
|
||||||
if mtype == MeasurementType.NotSpecific:
|
if mtype == MeasurementType.NotSpecific:
|
||||||
raise ValueError('Measurement to be set as reference is not a reference measurement')
|
raise ValueError(
|
||||||
|
"Measurement to be set as reference is not a reference measurement"
|
||||||
|
)
|
||||||
|
|
||||||
self._refMeas[mtype] = (m.UUID, m.name)
|
self._refMeas[mtype] = (m.UUID, m.name)
|
||||||
self.__storeReafMeas()
|
self.__storeReafMeas()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fromFolderWithUUID(uuid_str: str, folder: str='', skip=[]):
|
def fromFolderWithUUID(uuid_str: str, folder: str = "", skip=[]):
|
||||||
"""
|
"""
|
||||||
Returns Measurement object from a given UUID string. It first tries to find whether there
|
Returns Measurement object from a given UUID string. It first tries to find whether there
|
||||||
is an uuid in the static list of weak references. If not, it will try to open files in
|
is an uuid in the static list of weak references. If not, it will try to open files in
|
||||||
the current file path.
|
the current file path.
|
||||||
"""
|
"""
|
||||||
for fn in glob.glob(str(pathlib.Path(folder)) + f'/*{DOTMEXT}'):
|
for fn in glob.glob(str(pathlib.Path(folder)) + f"/*{DOTMEXT}"):
|
||||||
# Do not try to open this file in case it is in the 'skip' list.
|
# Do not try to open this file in case it is in the 'skip' list.
|
||||||
if len(list(filter(lambda a: a in fn, skip))) > 0:
|
if len(list(filter(lambda a: a in fn, skip))) > 0:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
m = Measurement(fn)
|
m = Measurement(fn)
|
||||||
if m.UUID == uuid_str:
|
if m.UUID == uuid_str:
|
||||||
# Update 'last_fn' attribute in dict of stored reference measurements
|
# Update 'last_fn' attribute in dict of stored reference measurements
|
||||||
return m
|
return m
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f'Possible measurement file {fn} returned error {e} when opening.')
|
logging.error(
|
||||||
|
f"Possible measurement file {fn} returned error {e} when opening."
|
||||||
|
)
|
||||||
|
|
||||||
raise RuntimeError(f'Measurement with UUID {uuid_str} could not be found.')
|
raise RuntimeError(f"Measurement with UUID {uuid_str} could not be found.")
|
||||||
|
|
||||||
def setAttribute(self, attrname: str, value):
|
def setAttribute(self, attrname: str, value):
|
||||||
"""
|
"""
|
||||||
@ -534,17 +559,17 @@ class Measurement:
|
|||||||
"""
|
"""
|
||||||
Returns True when a measurement is flagged as being of a certaint "MeasurementType"
|
Returns True when a measurement is flagged as being of a certaint "MeasurementType"
|
||||||
"""
|
"""
|
||||||
if (type_.value & self._type_int):
|
if type_.value & self._type_int:
|
||||||
return True
|
return True
|
||||||
elif type_.value == self._type_int == 0:
|
elif type_.value == self._type_int == 0:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def setType(self, type_: MeasurementType):
|
def setType(self, type_: MeasurementType):
|
||||||
"""
|
"""
|
||||||
Set the measurement type to given type
|
Set the measurement type to given type
|
||||||
"""
|
"""
|
||||||
self.setAttribute('type_int', type_.value)
|
self.setAttribute("type_int", type_.value)
|
||||||
|
|
||||||
def measurementType(self):
|
def measurementType(self):
|
||||||
"""
|
"""
|
||||||
@ -589,7 +614,7 @@ class Measurement:
|
|||||||
@channelConfig.setter
|
@channelConfig.setter
|
||||||
def channelConfig(self, chcfg: List[DaqChannel]):
|
def channelConfig(self, chcfg: List[DaqChannel]):
|
||||||
"""
|
"""
|
||||||
Set new channel configuration from list of DaqChannel objects.
|
Set new channel configuration from list of DaqChannel objects.
|
||||||
|
|
||||||
Use cases:
|
Use cases:
|
||||||
- Update channel types, sensitivities etc.
|
- Update channel types, sensitivities etc.
|
||||||
@ -885,7 +910,7 @@ class Measurement:
|
|||||||
"""
|
"""
|
||||||
if isinstance(sens, float):
|
if isinstance(sens, float):
|
||||||
# Put all sensitivities equal
|
# Put all sensitivities equal
|
||||||
sens = sens * np.ones(self.nchannels)
|
sens = sens * ones(self.nchannels)
|
||||||
elif isinstance(sens, list):
|
elif isinstance(sens, list):
|
||||||
sens = np.asarray(sens)
|
sens = np.asarray(sens)
|
||||||
|
|
||||||
@ -998,9 +1023,9 @@ class Measurement:
|
|||||||
happen that a Measurement object is created twice for the same backing file, which we do not allow.
|
happen that a Measurement object is created twice for the same backing file, which we do not allow.
|
||||||
"""
|
"""
|
||||||
# See if the base part of the filename is referring to a file that is already open
|
# See if the base part of the filename is referring to a file that is already open
|
||||||
with h5.File(fn, 'r') as f:
|
with h5.File(fn, "r") as f:
|
||||||
try:
|
try:
|
||||||
theuuid = f.attrs['UUID']
|
theuuid = f.attrs["UUID"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# No UUID stored in measurement. This is an old measurement that did not have UUID's
|
# No UUID stored in measurement. This is an old measurement that did not have UUID's
|
||||||
# We create a new UUID here such that the file is opened from the filesystem
|
# We create a new UUID here such that the file is opened from the filesystem
|
||||||
@ -1009,7 +1034,7 @@ class Measurement:
|
|||||||
|
|
||||||
if theuuid in Measurement.uuid_s.keys():
|
if theuuid in Measurement.uuid_s.keys():
|
||||||
return Measurement.uuid_s[theuuid]
|
return Measurement.uuid_s[theuuid]
|
||||||
|
|
||||||
return Measurement(fn)
|
return Measurement(fn)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -1090,8 +1115,8 @@ class Measurement:
|
|||||||
sensitivity,
|
sensitivity,
|
||||||
mfn,
|
mfn,
|
||||||
timestamp=None,
|
timestamp=None,
|
||||||
qtys: List[SIQtys]=None,
|
qtys: List[SIQtys] = None,
|
||||||
channelNames: List[str]=None,
|
channelNames: List[str] = None,
|
||||||
force=False,
|
force=False,
|
||||||
) -> Measurement:
|
) -> Measurement:
|
||||||
"""
|
"""
|
||||||
@ -1161,7 +1186,7 @@ class Measurement:
|
|||||||
hf.attrs["nchannels"] = nchannels
|
hf.attrs["nchannels"] = nchannels
|
||||||
|
|
||||||
# Add physical quantity indices
|
# Add physical quantity indices
|
||||||
hf.attrs['qtys_enum_idx'] = [qty.toInt() for qty in qtys]
|
hf.attrs["qtys_enum_idx"] = [qty.toInt() for qty in qtys]
|
||||||
|
|
||||||
# Add channel names in case given
|
# Add channel names in case given
|
||||||
if channelNames is not None:
|
if channelNames is not None:
|
||||||
@ -1199,7 +1224,7 @@ class Measurement:
|
|||||||
nchannels = 1
|
nchannels = 1
|
||||||
nframes = len(data)
|
nframes = len(data)
|
||||||
data = data[:, np.newaxis]
|
data = data[:, np.newaxis]
|
||||||
sensitivity = np.ones(nchannels)
|
sensitivity = ones(nchannels)
|
||||||
|
|
||||||
with h5.File(newfn, "w") as hf:
|
with h5.File(newfn, "w") as hf:
|
||||||
hf.attrs["samplerate"] = samplerate
|
hf.attrs["samplerate"] = samplerate
|
||||||
@ -1217,4 +1242,3 @@ class Measurement:
|
|||||||
ad[0] = data
|
ad[0] = data
|
||||||
|
|
||||||
return Measurement(newfn)
|
return Measurement(newfn)
|
||||||
|
|
||||||
|
@ -14,17 +14,16 @@ class MeasurementSet(list):
|
|||||||
"""
|
"""
|
||||||
Group of measurements that have some correspondence to one another. Class
|
Group of measurements that have some correspondence to one another. Class
|
||||||
is used to operate on multiple measurements at once.
|
is used to operate on multiple measurements at once.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, mlist: List[Measurement] = []):
|
def __init__(self, mlist: List[Measurement] = []):
|
||||||
"""
|
"""
|
||||||
Initialize a measurement set
|
Initialize a measurement set
|
||||||
|
|
||||||
Args:
|
Arg:
|
||||||
mlist: Measurement list
|
mlist: Measurement list
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if any([not isinstance(i, Measurement) for i in mlist]):
|
if any([not isinstance(i, Measurement) for i in mlist]):
|
||||||
raise TypeError("Object in list should be of Measurement type")
|
raise TypeError("Object in list should be of Measurement type")
|
||||||
|
|
||||||
@ -33,11 +32,17 @@ class MeasurementSet(list):
|
|||||||
super().__init__(mlist)
|
super().__init__(mlist)
|
||||||
|
|
||||||
def getNewestReferenceMeasurement(self, mtype: MeasurementType):
|
def getNewestReferenceMeasurement(self, mtype: MeasurementType):
|
||||||
"""Return the newest (in time) measurement in the current list of a certain type. Returns None in case no measurement could be found.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
mtype (MeasurementType): The type required.
|
|
||||||
"""
|
"""
|
||||||
|
Get the NEWEST ref. measurement of a current type, in the current set.
|
||||||
|
|
||||||
|
Arg:
|
||||||
|
mtype (MeasurementType): The type required.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
- The newest (in time) measurement in the current list of a certain type.
|
||||||
|
- None, in case no measurement could be found.
|
||||||
|
"""
|
||||||
|
|
||||||
mnewest = None
|
mnewest = None
|
||||||
for m in self:
|
for m in self:
|
||||||
if m.measurementType() == mtype:
|
if m.measurementType() == mtype:
|
||||||
@ -49,25 +54,32 @@ class MeasurementSet(list):
|
|||||||
return mnewest
|
return mnewest
|
||||||
|
|
||||||
def getReferenceMeasurements(self, mtype: MeasurementType):
|
def getReferenceMeasurements(self, mtype: MeasurementType):
|
||||||
"""Get all available reference measurements of a certain type in the
|
"""Get ALL ref. measurements of a certain type, in the current set.
|
||||||
current set.
|
|
||||||
|
|
||||||
Args:
|
Arg:
|
||||||
mtype (MeasurementType): The type of which to list
|
mtype (MeasurementType): The type of which to list
|
||||||
|
|
||||||
Returns:
|
Return:
|
||||||
a new measurement set including all measurements of a certain type
|
A new measurement set, including all measurements of a certain type
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return [m for m in self if m.measurementType() == mtype]
|
return [m for m in self if m.measurementType() == mtype]
|
||||||
|
|
||||||
def getNewestReferenceMeasurements(self):
|
def getNewestReferenceMeasurements(self):
|
||||||
"""Returns a dictionary with newest measurement of each type that is not specific returns None in case no measurement is found."""
|
"""
|
||||||
|
Get the NEWEST ref. measurement of all types, in the current set.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
- A dictionary with the newest measurement of each type that is not specific
|
||||||
|
- None, in case no measurement is found.
|
||||||
|
"""
|
||||||
|
|
||||||
newest = {}
|
newest = {}
|
||||||
for m in self:
|
for m in self:
|
||||||
mtype = m.measurementType()
|
mtype = m.measurementType()
|
||||||
if mtype == MeasurementType.NotSpecific:
|
if mtype == MeasurementType.NotSpecific:
|
||||||
continue
|
continue
|
||||||
if not mtype in newest:
|
if mtype not in newest:
|
||||||
newest[mtype] = m
|
newest[mtype] = m
|
||||||
else:
|
else:
|
||||||
if m.time > newest[mtype].time:
|
if m.time > newest[mtype].time:
|
||||||
@ -75,8 +87,17 @@ class MeasurementSet(list):
|
|||||||
return newest
|
return newest
|
||||||
|
|
||||||
def newestReferenceOlderThan(self, secs):
|
def newestReferenceOlderThan(self, secs):
|
||||||
"""Returns a dictionary of references with the newest reference, that is still
|
"""
|
||||||
older than `secs` seconds."""
|
Get a dictionary of reference measurements which are older than a
|
||||||
|
specified threshold. Only one of each type is returned.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- secs: time threshold, in seconds
|
||||||
|
|
||||||
|
Return:
|
||||||
|
- a dictionary of references with the newest reference, that is still
|
||||||
|
older than `secs` seconds
|
||||||
|
"""
|
||||||
curtime = time.time()
|
curtime = time.time()
|
||||||
newest = self.getNewestReferenceMeasurements()
|
newest = self.getNewestReferenceMeasurements()
|
||||||
newest_older_than = {}
|
newest_older_than = {}
|
||||||
@ -87,25 +108,29 @@ class MeasurementSet(list):
|
|||||||
|
|
||||||
def measTimeSame(self):
|
def measTimeSame(self):
|
||||||
"""
|
"""
|
||||||
Returns True if all measurements have the same measurement
|
Returns True if all measurements have the same measurement length and
|
||||||
time (recorded time)
|
sample rate
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if len(self) > 0:
|
if len(self) > 0:
|
||||||
first = self[0].N
|
firstN = self[0].N # samples
|
||||||
return all([first == meas.N for meas in self])
|
firstFS = self[0].samplerate # sample rate
|
||||||
|
sameN = all([firstN == meas.N for meas in self])
|
||||||
|
sameFS = all([firstFS == meas.samplerate for meas in self])
|
||||||
|
return sameN and sameFS
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def measSimilar(self):
|
def measSimilar(self):
|
||||||
"""
|
"""
|
||||||
Similar means: channel metadata is the same, and the measurement time
|
Similar means: channel metadata is the same, and the measurement time
|
||||||
is the same. It means that the recorded data is, of course, different.
|
is the same.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if measChannelsSame() and measTimeSame() else False
|
- True if measChannelsSame() and measTimeSame()
|
||||||
|
- False otherwise
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.measTimeSame() and self.measChannelsSame()
|
return self.measTimeSame() and self.measChannelsSame()
|
||||||
|
|
||||||
def measChannelsSame(self):
|
def measChannelsSame(self):
|
||||||
@ -115,6 +140,7 @@ class MeasurementSet(list):
|
|||||||
a set of measurements, simultaneously. If the channel data is the same
|
a set of measurements, simultaneously. If the channel data is the same
|
||||||
(name, sensitivity, ...) it returns True.
|
(name, sensitivity, ...) it returns True.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if len(self) > 0:
|
if len(self) > 0:
|
||||||
first = self[0].channelConfig
|
first = self[0].channelConfig
|
||||||
return all([first == meas.channelConfig for meas in self])
|
return all([first == meas.channelConfig for meas in self])
|
||||||
|
@ -77,6 +77,7 @@ class FirFilterBank:
|
|||||||
|
|
||||||
self.fs = fs
|
self.fs = fs
|
||||||
self.xs = list(range(xmin, xmax + 1))
|
self.xs = list(range(xmin, xmax + 1))
|
||||||
|
raise RuntimeError('Not working code anymore')
|
||||||
|
|
||||||
maxdecimation = self.designer.firDecimation(self.xs[0])
|
maxdecimation = self.designer.firDecimation(self.xs[0])
|
||||||
self.decimators = []
|
self.decimators = []
|
||||||
@ -245,7 +246,7 @@ class SosFilterBank:
|
|||||||
for i, x in enumerate(self.xs):
|
for i, x in enumerate(self.xs):
|
||||||
channel = self.designer.createSOSFilter(x)
|
channel = self.designer.createSOSFilter(x)
|
||||||
if sos is None:
|
if sos is None:
|
||||||
sos = np.empty((channel.size, len(self.xs)))
|
sos = empty((channel.size, len(self.xs)))
|
||||||
sos[:, i] = channel.flatten()
|
sos[:, i] = channel.flatten()
|
||||||
|
|
||||||
self._fb = BiquadBank(sos)
|
self._fb = BiquadBank(sos)
|
||||||
|
@ -6,6 +6,7 @@ Description:
|
|||||||
Reverberation time estimation tool using least squares
|
Reverberation time estimation tool using least squares
|
||||||
"""
|
"""
|
||||||
from .lasp_common import getTime
|
from .lasp_common import getTime
|
||||||
|
from .lasp_config import ones
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
@ -56,7 +57,7 @@ class ReverbTime:
|
|||||||
x = self._t[istart:istop][:, np.newaxis]
|
x = self._t[istart:istop][:, np.newaxis]
|
||||||
|
|
||||||
# Solve the least-squares problem, by creating a matrix of
|
# Solve the least-squares problem, by creating a matrix of
|
||||||
A = np.hstack([x, np.ones(x.shape)])
|
A = np.hstack([x, ones(x.shape)])
|
||||||
|
|
||||||
# print(A.shape)
|
# print(A.shape)
|
||||||
# print(points.shape)
|
# print(points.shape)
|
||||||
|
@ -5,6 +5,7 @@ Sound level meter implementation
|
|||||||
@author: J.A. de Jong - ASCEE
|
@author: J.A. de Jong - ASCEE
|
||||||
"""
|
"""
|
||||||
from .lasp_cpp import cppSLM
|
from .lasp_cpp import cppSLM
|
||||||
|
from .lasp_config import empty
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from .lasp_common import (TimeWeighting, FreqWeighting, P_REF)
|
from .lasp_common import (TimeWeighting, FreqWeighting, P_REF)
|
||||||
from .filter import SPLFilterDesigner
|
from .filter import SPLFilterDesigner
|
||||||
@ -101,7 +102,7 @@ class SLM:
|
|||||||
assert fbdesigner.fs == fs
|
assert fbdesigner.fs == fs
|
||||||
sos_firstx = fbdesigner.createSOSFilter(self.xs[0]).flatten()
|
sos_firstx = fbdesigner.createSOSFilter(self.xs[0]).flatten()
|
||||||
self.nom_txt.append(fbdesigner.nominal_txt(self.xs[0]))
|
self.nom_txt.append(fbdesigner.nominal_txt(self.xs[0]))
|
||||||
sos = np.empty((sos_firstx.size, nfilters), dtype=float, order='C')
|
sos = empty((sos_firstx.size, nfilters), dtype=float, order='C')
|
||||||
sos[:, 0] = sos_firstx
|
sos[:, 0] = sos_firstx
|
||||||
|
|
||||||
for i, x in enumerate(self.xs[1:]):
|
for i, x in enumerate(self.xs[1:]):
|
||||||
|
@ -47,7 +47,7 @@ class WeighCal:
|
|||||||
|
|
||||||
P = 2048 # Filter length (number of taps)
|
P = 2048 # Filter length (number of taps)
|
||||||
|
|
||||||
self._firs = np.empty((P, self.nchannels))
|
self._firs = empty((P, self.nchannels))
|
||||||
self._fbs = []
|
self._fbs = []
|
||||||
for chan in range(self.nchannels):
|
for chan in range(self.nchannels):
|
||||||
fir = arbitrary_fir_design(fs, P, freq_design,
|
fir = arbitrary_fir_design(fs, P, freq_design,
|
||||||
|
@ -21,6 +21,7 @@ import copy
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from numpy import log2, pi, sin
|
from numpy import log2, pi, sin
|
||||||
from ..lasp_cpp import freqSmooth
|
from ..lasp_cpp import freqSmooth
|
||||||
|
from ..lasp_config import zeros
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
|
@ -31,7 +31,7 @@ def test_backward_fft():
|
|||||||
nfft = 2048
|
nfft = 2048
|
||||||
freq = getFreq(nfft, nfft)
|
freq = getFreq(nfft, nfft)
|
||||||
|
|
||||||
# Sig = np.zeros(nfft//2+1, dtype=complex)
|
# Sig = zeros(nfft//2+1, dtype=complex)
|
||||||
Sigr = np.random.randn(nfft//2+1)
|
Sigr = np.random.randn(nfft//2+1)
|
||||||
Sigi = np.random.randn(nfft//2+1)
|
Sigi = np.random.randn(nfft//2+1)
|
||||||
|
|
||||||
|
2
third_party/portaudio
vendored
2
third_party/portaudio
vendored
@ -1 +1 @@
|
|||||||
Subproject commit daaf637f6f9fce670031221abfd7dfde92e5cce3
|
Subproject commit 18a606e1f928852bfc29639d9539ae74d37b5dee
|
Loading…
Reference in New Issue
Block a user