Skip to content

Commit 417e772

Browse files
author
Martin Glesser
authored
Merge pull request #53 from wantysal/freq_correction
Frequency domain corrections for nth octave spectrum and screening for tones functions
2 parents c76d970 + ff32c70 commit 417e772

22 files changed

+265
-263
lines changed
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
from mosqito.sound_level_meter.noct_spectrum.noct_spectrum import (
2-
noct_spectrum,
3-
)
1+
from mosqito.sound_level_meter.noct_spectrum.noct_spectrum import noct_spectrum
2+
from mosqito.sound_level_meter.noct_spectrum.noct_synthesis import noct_synthesis
3+

mosqito/sound_level_meter/noct_spectrum/_filter_bandwidth.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ def _filter_bandwidth(fc, n=3, N=3):
3030
alpha : numpy.ndarray
3131
Ratio of the upper and lower band-edge frequencies to the mid-band
3232
frequency
33+
f1 : float
34+
Lower frequency of the band in Hz
35+
f2 : float
36+
Upper frequency of the band in Hz
37+
3338
"""
3439

3540
"""
@@ -54,7 +59,7 @@ def _filter_bandwidth(fc, n=3, N=3):
5459
# Ratio of the upper and lower band-edge frequencies to the mid-band frequency
5560
alpha = (1 + np.sqrt(1 + 4 * qd ** 2)) / 2 / qd
5661

57-
return alpha
62+
return alpha, f1, f2
5863

5964

6065
if __name__ == "__main__":
Lines changed: 14 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
# -*- coding: utf-8 -*-
2-
"""
3-
Created on Thu Mar 10 10:00:03 2022
42

5-
@author: wantysal
6-
"""
3+
74
# Standard library imports
85
import numpy as np
9-
from scipy.signal import butter, freqz
10-
6+
from scipy.signal import butter, sosfreqz
117

12-
def _n_oct_freq_filter(spectrum, fs, fc, alpha, n=3):
8+
def _n_oct_freq_filter(spectrum, fs, fc, alpha, n=3):
139
""" n-th octave filtering in frequency domain
1410
1511
Designs a digital 1/3-octave filter with center frequency fc for
@@ -26,7 +22,7 @@ def _n_oct_freq_filter(spectrum, fs, fc, alpha, n=3):
2622
alpha : float
2723
Ratio of the upper and lower band-edge frequencies to the mid-band
2824
frequency
29-
N : int, optional
25+
n : int, optional
3026
Filter order. Default to 3
3127
3228
Outputs
@@ -35,40 +31,17 @@ def _n_oct_freq_filter(spectrum, fs, fc, alpha, n=3):
3531
Rms level of sig in the third octave band centered on fc
3632
"""
3733

38-
# Check for Nyquist-Shannon criteria
39-
if fc > 0.88 * (fs / 2):
40-
raise ValueError(
41-
"""ERROR: Design not possible. Filter center frequency shall
42-
verify: fc <= 0.88 * (fs / 2)"""
43-
)
44-
4534
# Normalized cutoff frequencies
46-
w1 = fc / (fs / 2) / alpha
47-
w2 = fc / (fs / 2) * alpha
48-
49-
# define filter coefficient
50-
b, a = butter(n, [w1, w2], "bandpass", analog=False)
51-
35+
w1 = fc / (fs / 2) / alpha
36+
w2 = fc / (fs / 2) * alpha
37+
38+
# Define filter coefficient
39+
sos = butter(n, [w1, w2], "bandpass", analog=False, output ='sos')
40+
# Get FRF and apply it
41+
w, h = sosfreqz(sos, worN=len(spectrum))
42+
spec_filt = np.multiply(h, spectrum.T).T
5243

53-
# filter signal
54-
if len(spectrum.shape)>1:
55-
level = []
56-
# go into frequency domain
57-
w, h = freqz(b, a, worN=spectrum.shape[1])
58-
for i in range(spectrum.shape[0]):
59-
spec_filt = spectrum[i,:] * h
60-
# Compute overall rms level
61-
level.append(np.sqrt(np.sum(np.abs(spec_filt) ** 2)))
62-
level = np.array(level)
63-
else:
64-
# go into frequency domain
65-
w, h = freqz(b, a, worN=len(spectrum))
66-
spec_filt = spectrum * h
67-
# Compute overall rms level
68-
level = np.sqrt(np.sum(np.abs(spec_filt) ** 2))
44+
# Compute overall rms level
45+
level = np.sqrt(np.sum(np.abs(spec_filt) ** 2, axis=0))
6946

7047
return level
71-
72-
73-
74-

mosqito/sound_level_meter/noct_spectrum/_n_oct_time_filter.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# Standard library imports
44
import numpy as np
5-
from scipy.signal import decimate, butter, lfilter
5+
from scipy.signal import decimate, butter, sosfilt
66

77

88
def _n_oct_time_filter(sig, fs, fc, alpha, N=3):
@@ -29,7 +29,7 @@ def _n_oct_time_filter(sig, fs, fc, alpha, N=3):
2929
alpha : float
3030
Ratio of the upper and lower band-edge frequencies to the mid-band
3131
frequency
32-
N : int, optional
32+
n : int, optional
3333
Filter order. Default to 3
3434
3535
Outputs
@@ -59,10 +59,10 @@ def _n_oct_time_filter(sig, fs, fc, alpha, N=3):
5959
w2 = fc / (fs / 2) * alpha
6060

6161
# define filter coefficient
62-
b, a = butter(N, [w1, w2], "bandpass", analog=False)
62+
sos = butter(int(N), (w1, w2), "bandpass", analog=False, output='sos')
6363

6464
# filter signal
65-
sig_filt = lfilter(b, a, sig, axis=0)
65+
sig_filt = sosfilt(sos, sig, axis=0)
6666

6767
# Compute overall rms level
6868
level = np.sqrt(np.mean(sig_filt ** 2, axis=0))

mosqito/sound_level_meter/noct_spectrum/noct_spectrum.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def noct_spectrum(sig, fs, fmin, fmax, n=3, G=10, fr=1000):
5353
fc_vec, fpref = _center_freq(fmin=fmin, fmax=fmax, n=n, G=G, fr=fr)
5454

5555
# Compute the filters bandwidth
56-
alpha_vec = _filter_bandwidth(fc_vec, n=n)
56+
alpha_vec, _, _ = _filter_bandwidth(fc_vec, n=n)
5757

5858
# Calculation of the rms level of the signal in each band
5959
spec = []

mosqito/sound_level_meter/noct_spectrum/noct_synthesis.py

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55

66
# Local application imports
77
from mosqito.sound_level_meter.noct_spectrum._center_freq import _center_freq
8+
from mosqito.sound_level_meter.noct_spectrum._filter_bandwidth import _filter_bandwidth
9+
from mosqito.sound_level_meter.noct_spectrum._n_oct_freq_filter import _n_oct_freq_filter
810

911

1012
def noct_synthesis(spectrum, freqs, fmin, fmax, n=3, G=10, fr=1000):
1113
"""Adapt input spectrum to nth-octave band spectrum
12-
1314
Convert the input spectrum to third-octave band spectrum
1415
between "fc_min" and "fc_max".
15-
1616
Parameters
1717
----------
1818
spectrum : numpy.ndarray
@@ -32,48 +32,47 @@ def noct_synthesis(spectrum, freqs, fmin, fmax, n=3, G=10, fr=1000):
3232
Reference frequency. Shall be set to 1 kHz for audible frequency
3333
range, to 1 Hz for infrasonic range (f < 20 Hz) and to 1 MHz for
3434
ultrasonic range (f > 31.5 kHz).
35-
3635
Outputs
3736
-------
3837
spec : numpy.ndarray
3938
Third octave band spectrum of signal sig [dB re.2e-5 Pa], size (nbands, nseg).
4039
fpref : numpy.ndarray
4140
Corresponding preferred third octave band center frequencies, size (nbands).
4241
"""
42+
43+
# Deduce sampling frequency
44+
fs = np.mean(freqs[1:] - freqs[:-1]) * 2 * len(spectrum)
4345

4446
# Get filters center frequencies
4547
fc_vec, fpref = _center_freq(fmin=fmin, fmax=fmax, n=n, G=G, fr=fr)
48+
49+
# Compute the filters bandwidth
50+
alpha_vec, f_low, f_high = _filter_bandwidth(fc_vec, n=n)
51+
52+
# Delete ends frequencies to prevent aliasing
53+
idx = np.asarray(np.where(f_high > fs / 2))
54+
if any(idx[0]):
55+
fc_vec = np.delete(fc_vec, idx)
56+
f_low = np.delete(f_low, idx)
57+
f_high = np.delete(f_high, idx)
4658

47-
nband = len(fpref)
48-
59+
# Number of nth bands
60+
nband = len(fc_vec)
61+
62+
# Results array initialization
4963
if len(spectrum.shape) > 1:
5064
nseg = spectrum.shape[1]
5165
spec = np.zeros((nband, nseg))
66+
# If only one axis is given, it is used for all the spectra
5267
if len(freqs.shape) == 1:
5368
freqs = np.tile(freqs, (nseg, 1)).T
54-
5569
else:
5670
nseg = 1
5771
spec = np.zeros((nband))
5872

59-
# Frequency resolution
60-
# df = freqs[1:] - freqs[:-1]
61-
# df = np.concatenate((df, [df[-1]]))
62-
63-
# Get upper and lower frequencies
64-
fu = fc_vec * 2**(1/(2*n))
65-
fl = fc_vec / 2**(1/(2*n))
66-
67-
for s in range(nseg):
68-
for i in range(nband):
69-
if len(spectrum.shape) > 1:
70-
# index of the frequencies within the band
71-
idx = np.where((freqs[:, s] >= fl[i]) & (freqs[:, s] < fu[i]))
72-
spec[i, s] = np.sqrt(
73-
np.sum(np.power(np.abs(spectrum[idx,s]), 2)))
74-
else:
75-
# index of the frequencies within the band
76-
idx = np.where((freqs >= fl[i]) & (freqs < fu[i]))
77-
spec[i] = np.sqrt(np.sum(np.abs(spectrum[idx])**2))
73+
# Calculation of the rms level of the signal in each band
74+
spec = []
75+
for fc, alpha in zip(fc_vec, alpha_vec):
76+
spec.append(_n_oct_freq_filter(spectrum, fs, fc, alpha))
7877

79-
return spec, fpref
78+
return np.array(spec), fpref

mosqito/sq_metrics/tonality/prominence_ratio_ecma/_pr_main_calc.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,7 @@ def _pr_main_calc(spectrum_db, freq_axis):
205205

206206
# suppression from the list of tones of all the candidates belonging to the
207207
# same critical band
208-
sup = np.where((peaks >= low_limit_idx) & (peaks <= high_limit_idx))[
209-
0
210-
]
208+
sup = np.where((peaks >= low_limit_idx) & (peaks <= high_limit_idx))[0]
211209
peaks = np.delete(peaks, sup)
212210
nb_tones -= len(sup)
213211

mosqito/sq_metrics/tonality/tone_to_noise_ecma/_screening_for_tones.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@
88
import numpy as np
99

1010
# Mosqito functions import
11-
from mosqito.sq_metrics.tonality.tone_to_noise_ecma._spectrum_smoothing import (
12-
_spectrum_smoothing,
13-
)
11+
from mosqito.sq_metrics.tonality.tone_to_noise_ecma._spectrum_smoothing import _spectrum_smoothing
1412
from mosqito.sq_metrics.tonality.tone_to_noise_ecma._LTH import _LTH
1513
from mosqito.sq_metrics.tonality.tone_to_noise_ecma._critical_band import _critical_band
1614

@@ -54,7 +52,7 @@ def _screening_for_tones(freqs, spec_db, method, low_freq, high_freq):
5452
# Detection of the tonal candidates according to their level
5553

5654
# Creation of the smoothed spectrum
57-
smooth_spec = _spectrum_smoothing(freqs, spec_db, 24, low_freq, high_freq, freqs)
55+
smooth_spec = _spectrum_smoothing(freqs, spec_db.T, 24, low_freq, high_freq, freqs).T
5856

5957

6058
n = spec_db.shape[0]
@@ -149,7 +147,7 @@ def _screening_for_tones(freqs, spec_db, method, low_freq, high_freq):
149147
# Screen the left points of the peak
150148
temp = peak_index - 1
151149
# As long as the level decreases,
152-
while (spec_db[temp] > smooth_spec[temp] + 6) and (temp + 1 < (block+1)*m):
150+
while (spec_db[temp] > smooth_spec[temp] + 6) and (temp +1 > (block)*m):
153151
# if a highest spectral line is found, it becomes the candidate
154152
if spec_db[temp] > spec_db[peak_index]:
155153
peak_index = temp

mosqito/sq_metrics/tonality/tone_to_noise_ecma/_spectrum_smoothing.py

Lines changed: 23 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ def _spectrum_smoothing(freqs_in, spec, noct, low_freq, high_freq, freqs_out):
1818
Parameters
1919
----------
2020
freqs : numpy.array
21-
frequency axis (frequency axis)
21+
frequency axis dim (nperseg)
2222
spec : numpy.array
23-
spectrum in dB (n block x spectrum)
23+
spectrum in dB (nperseg, nseg)
2424
noct : integer
2525
n-th octave-band according to which smooth the spectrum
2626
low_freq : float
@@ -36,14 +36,13 @@ def _spectrum_smoothing(freqs_in, spec, noct, low_freq, high_freq, freqs_out):
3636
smoothed spectrum along the given frequency axis
3737
3838
"""
39-
n = spec.shape[0]
39+
nperseg = spec.shape[0]
4040
if len(spec.shape) > 1:
41-
m = spec.shape[1]
41+
nseg = spec.shape[1]
4242
else:
43-
m = spec.shape[0]
44-
n = 1
43+
nseg = 1
4544
spec = spec.ravel()
46-
stop = np.arange(1, n + 1) * m
45+
stop = np.arange(1, nseg + 1) * nperseg
4746
freqs_in = freqs_in.ravel()
4847

4948
# n-th octave bands filter
@@ -53,14 +52,12 @@ def _spectrum_smoothing(freqs_in, spec, noct, low_freq, high_freq, freqs_out):
5352

5453
# Smoothed spectrum creation
5554
nb_bands = filter_freqs.shape[0]
56-
smoothed_spectrum = np.zeros((n, nb_bands))
55+
smoothed_spectrum = np.zeros((nb_bands, nseg))
5756
i = 0
5857
# Each band is considered individually until all of them have been treated
5958
while nb_bands > 0:
6059
# Find the index of the spectral components within the frequency bin
61-
bin_index = np.where(
62-
(freqs_in >= filter_freqs[i, 0]) & (freqs_in <= filter_freqs[i, 2])
63-
)[0]
60+
bin_index = np.where((freqs_in >= filter_freqs[i, 0]) & (freqs_in <= filter_freqs[i, 2]))[0]
6461

6562
# If the frequency bin is empty, it is deleted from the list
6663
if len(bin_index) == 0:
@@ -70,45 +67,32 @@ def _spectrum_smoothing(freqs_in, spec, noct, low_freq, high_freq, freqs_out):
7067

7168
else:
7269
# The spectral components within the frequency bin are averaged on an energy basis
73-
spec_sum = np.zeros((n))
74-
for j in range(n):
75-
if (
76-
len(bin_index[(bin_index < stop[j]) & (bin_index > (stop[j] - m))])
77-
!= 0
78-
):
79-
spec_sum[j] = np.mean(
80-
10
81-
** (
82-
spec[
83-
bin_index[
84-
(bin_index < stop[j]) & (bin_index > (stop[j] - m))
85-
]
86-
]
87-
/ 10
88-
)
89-
)
70+
spec_sum = np.zeros((nseg))
71+
for j in range(nseg):
72+
if len(bin_index[(bin_index < stop[j]) & (bin_index > (stop[j] - nperseg))])!= 0:
73+
spec_sum[j] = np.mean(10** (spec[bin_index[(bin_index < stop[j]) & (bin_index > (stop[j] - nperseg))]]/ 10))
9074
else:
9175
spec_sum[j] = 1e-12
92-
smoothed_spectrum[:, i] = 10 * np.log10(
93-
spec_sum
94-
# / len(bin_index[(bin_index < stop[j]) & (bin_index > (stop[j] - m))])
95-
)
76+
smoothed_spectrum[i, :] = 10 * np.log10(spec_sum)# / len(bin_index[(bin_index < stop[j]) & (bin_index > (stop[j] - m))]))
9677
nb_bands -= 1
9778
i += 1
9879
# Pose of the smoothed spectrum on the frequency-axis
99-
low = np.zeros((n, filter_freqs.shape[0]))
100-
high = np.zeros((n, filter_freqs.shape[0]))
80+
low = np.zeros((filter_freqs.shape[0], nseg))
81+
high = np.zeros((filter_freqs.shape[0], nseg))
10182

10283
# Index of the lower and higher limit of each frequency bin into the original spectrum
10384
for i in range(len(filter_freqs)):
104-
low[:, i] = np.argmin(np.abs(freqs_out - filter_freqs[i, 0]))
105-
high[:, i] = np.argmin(np.abs(freqs_out - filter_freqs[i, 2]))
85+
low[i,:] = np.argmin(np.abs(freqs_out - filter_freqs[i, 0]))
86+
high[i,:] = np.argmin(np.abs(freqs_out - filter_freqs[i, 2]))
10687
low = low.astype(int)
10788
high = high.astype(int)
10889

109-
smooth_spec = np.zeros((n, m))
110-
for i in range(n):
90+
smooth_spec = np.zeros((nperseg, nseg))
91+
for i in range(nseg):
11192
for j in range(filter_freqs.shape[0]):
112-
smooth_spec[i, low[i, j] : high[i, j]] = smoothed_spectrum[i, j]
93+
smooth_spec[low[j,i] : high[j,i], i] = smoothed_spectrum[j,i]
94+
95+
if smooth_spec.shape[1] == 1:
96+
smooth_spec = np.squeeze(smooth_spec)
11397

11498
return smooth_spec

0 commit comments

Comments
 (0)