lasp/lasp/lasp_slm.py

178 lines
4.9 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Sound level meter implementation
@author: J.A. de Jong - ASCEE
"""
from .wrappers import SPLowpass
from .lasp_computewidget import ComputeWidget
import numpy as np
from .lasp_config import zeros
from .lasp_common import (FreqWeighting, sens, calfile,
TimeWeighting, getTime, P_REF)
from .lasp_weighcal import WeighCal
from .lasp_gui_tools import wait_cursor
from .lasp_figure import FigureDialog, PlotOptions
from .ui_slmwidget import Ui_SlmWidget
__all__ = ['SLM', 'SlmWidget']
class Dummy:
"""
Emulate filtering, but does not filter anything at all.
"""
def filter_(self, data):
return data[:, np.newaxis]
class SLM:
"""
Sound Level Meter, implements the single pole lowpass filter
"""
def __init__(self, fs, weighcal,
tw=TimeWeighting.default,
):
"""
Initialize a sound level meter object. Number of channels comes from
the weighcal object
Args:
fs: Sampling frequency [Hz]
weighcal: WeighCal instance used for calibration and frequency
weighting.
nchannels: Number of channels to allocate filters for
"""
nchannels = weighcal.nchannels
self.nchannels = nchannels
self._weighcal = weighcal
if tw[0] is not TimeWeighting.none[0]:
self._lps = [SPLowpass(fs, tw[0]) for i in range(nchannels)]
else:
self._lps = [Dummy() for i in range(nchannels)]
self._Lmax = zeros(nchannels)
@property
def Lmax(self):
"""
Returns the currently maximum recorded level
"""
return self._Lmax
def addData(self, data):
"""
Add new fresh timedata to the Sound Level Meter
Args:
data:
"""
if data.ndim == 1:
data = data[:, np.newaxis]
data_weighted = self._weighcal.filter_(data)
# Squared
sq = data_weighted**2
if sq.shape[0] == 0:
return np.array([])
tw = []
# Time-weight the signal
for chan, lp in enumerate(self._lps):
tw.append(lp.filter_(sq[:, chan])[:, 0])
tw = np.asarray(tw).transpose()
Level = 10*np.log10(tw/P_REF**2)
curmax = np.max(Level)
if curmax > self._Lmax:
self._Lmax = curmax
return Level
class SlmWidget(ComputeWidget, Ui_SlmWidget):
def __init__(self):
"""
Initialize the SlmWidget.
"""
super().__init__()
self.setupUi(self)
FreqWeighting.fillComboBox(self.freqweighting)
self.setMeas(None)
def setMeas(self, meas):
"""
Set the current measurement for this widget.
Args:
meas: if None, the Widget is disabled
"""
self.meas = meas
if meas is None:
self.setEnabled(False)
else:
self.setEnabled(True)
rt = meas.recTime
self.starttime.setRange(0, rt, 0)
self.stoptime.setRange(0, rt, rt)
self.channel.clear()
for i in range(meas.nchannels):
self.channel.addItem(str(i))
self.channel.setCurrentIndex(0)
def compute(self):
"""
Compute Sound Level using settings. This method is
called whenever the Compute button is pushed in the SLM tab
"""
meas = self.meas
fs = meas.samplerate
channel = self.channel.currentIndex()
tw = TimeWeighting.getCurrent(self.timeweighting)
fw = FreqWeighting.getCurrent(self.freqweighting)
# Downsampling factor of result
dsf = self.downsampling.value()
# gb = self.slmFre
with wait_cursor():
# This one exctracts the calfile and sensitivity from global
# variables defined at the top. # TODO: Change this to a more
# robust variant.
weighcal = WeighCal(fw, nchannels=1,
fs=fs, calfile=calfile,
sens=sens)
slm = SLM(fs, weighcal, tw)
praw = meas.praw()[:, [channel]]
# Filter, downsample data
filtered = slm.addData(praw)[::dsf, :]
N = filtered.shape[0]
time = getTime(float(fs)/dsf, N)
Lmax = slm.Lmax
pto = PlotOptions()
pto.ylabel = f'L{fw[0]} [dB({fw[0]})]'
pto.xlim = (time[0], time[-1])
fig, new = self.getFigure(pto)
fig.fig.plot(time, filtered)
fig.show()
stats = f"""Statistical results:
=============================
Applied frequency weighting: {fw[1]}
Applied time weighting: {tw[1]}
Applied Downsampling factor: {dsf}
Maximum level (L{fw[0]} max): {Lmax:4.4} [dB({fw[0]})]
"""
self.results.setPlainText(stats)