added fractional octave smoothing to 'lasp/tools/tools.py'
This commit is contained in:
parent
fa6fbbe12d
commit
2a1e9e2163
152
lasp/tools/tools.py
Normal file
152
lasp/tools/tools.py
Normal file
@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on Thu May 6 14:49:03 2021
|
||||
|
||||
@author: Casper
|
||||
|
||||
Smooth data in the frequency domain
|
||||
"""
|
||||
|
||||
# TO DO: check if everything is correct
|
||||
# TO DO: add possibility to insert data that is not lin spaced in frequency
|
||||
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from scipy.signal.windows import gaussian
|
||||
|
||||
|
||||
# %% Smoothing function
|
||||
def oct_smooth(f, M, Noct, dB=False):
|
||||
"""
|
||||
Apply fractional octave smoothing to magnitude data in frequency domain.
|
||||
Smoothing is performed to power, using a sliding Gaussian window with
|
||||
variable length. The window is truncated after 2x std at either side.
|
||||
|
||||
The implementation is not exact, because f is linearly spaced and
|
||||
fractional octave smoothing is related to log spaced data. In this
|
||||
implementation, the window extends with a fixed frequency step to either
|
||||
side. The deviation is largest when Noct is small (e.g. coarse smoothing).
|
||||
Casper Jansen, 07-05-2021
|
||||
|
||||
Parameters
|
||||
----------
|
||||
f : float
|
||||
frequencies of data points [Hz] - equally spaced
|
||||
M : float
|
||||
magnitude of data points [- or dB, specify in paramater 'dB']
|
||||
Noct : int
|
||||
smoothing strength: Noct=12 means 1/12 octave smoothing
|
||||
dB : Bool
|
||||
True if [M]=dB, False if [M]=absolute
|
||||
|
||||
Returns
|
||||
-------
|
||||
f : float
|
||||
frequencies of data points [Hz]
|
||||
Msm : float
|
||||
smoothed magnitude of data points
|
||||
|
||||
"""
|
||||
|
||||
# Settings
|
||||
tr = 2 # truncate window after 2x std
|
||||
|
||||
# Safety
|
||||
assert Noct > 0, '\'Noct\' must be absolute positive'
|
||||
if Noct < 1: raise Warning('Check if \'Noct\' is entered correctly')
|
||||
assert len(f)==len(M), 'f and M should have equal length'
|
||||
if not dB: assert np.min(M) >= 0, 'absolute magnitude M cannot be negative'
|
||||
|
||||
# Initialize
|
||||
L = len(M) # number of data points
|
||||
P = 10**(M/10) if dB else M**2 # convert magnitude --> power
|
||||
Psm = np.zeros(L) # smoothed power - to be calculated
|
||||
x0 = 1 if f[0]==0 else 0 # skip first data point if zero frequency
|
||||
df = f[1] - f[0] # frequency step
|
||||
|
||||
# Loop through data points
|
||||
for x in range(x0, L):
|
||||
# Find indices of data points to calculate current (smoothed) magnitude
|
||||
fc = f[x] # center freq. of smoothing window
|
||||
Df = tr * fc / Noct # freq. range of smoothing window
|
||||
xl = int(np.ceil(x - 0.5*Df/df)) # desired lower index of frequency array to be used during smoothing
|
||||
xu = int(np.floor(x + 0.5*Df/df)) + 1 # upper index + 1 (because half-open interval)
|
||||
|
||||
# Create window
|
||||
Np = xu - xl # number of points
|
||||
std = Np / (2 * tr)
|
||||
wind = gaussian(Np, std) # Gaussian window
|
||||
|
||||
# Clip indices to valid range
|
||||
#
|
||||
# Indices beyond [0, L] point to non-existing data. This occurs when
|
||||
# the smoothing windows nears the beginning or end of the series.
|
||||
# Optional: if one end of the window is clipped, the other end
|
||||
# could be clipped as well, to prevent an error on magnitude data with
|
||||
# a slope. It however results in unsmoothed looking data at the ends.
|
||||
if xl < 0:
|
||||
rl = 0 - xl # remove this number of points at the lower end
|
||||
xl = xl + rl # .. from f
|
||||
wind = wind[rl:] # .. and from window
|
||||
|
||||
# rl = 0 - xl # remove this number of points at the lower end
|
||||
# xl = xl + rl # .. from f
|
||||
# xu = xu - rl
|
||||
# wind = wind[rl:-rl] # .. and from window
|
||||
|
||||
if xu > L:
|
||||
ru = xu - L # remove this number of points at the upper end
|
||||
xu = xu - ru
|
||||
wind = wind[:-ru]
|
||||
|
||||
# ru = xu - L # remove this number of points at the upper end
|
||||
# xl = xl + ru
|
||||
# xu = xu - ru
|
||||
# wind = wind[ru:-ru]
|
||||
|
||||
# Apply smoothing
|
||||
wind_int = np.sum(wind) # integral
|
||||
Psm[x] = np.dot(wind, P[xl:xu]) / wind_int # apply window
|
||||
|
||||
Msm = 10*np.log10(Psm) if dB else Psm**0.5 # convert power --> magnitude
|
||||
|
||||
return Msm
|
||||
|
||||
|
||||
# %% Test
|
||||
if __name__ == "__main__":
|
||||
# Initialize
|
||||
Noct = 6 # 1/6 oct. smoothing
|
||||
|
||||
# Create dummy data
|
||||
fmin = 3e3 # [Hz] min freq
|
||||
fmax = 24e3 # [Hz] max freq
|
||||
Ndata = 200 # number of data points
|
||||
|
||||
f = np.linspace(fmin, fmax, Ndata) # frequency points
|
||||
M = abs(1+0.4*np.random.normal(size=(Ndata,)))+0.01 #
|
||||
dB = False
|
||||
M = 20*np.log10(M)
|
||||
dB = True
|
||||
|
||||
|
||||
# M = f+1 # magnitude
|
||||
# dB = False # True if M is given in dB
|
||||
|
||||
|
||||
# Apply function
|
||||
Msm = oct_smooth(f, M, Noct, dB)
|
||||
fsm = f
|
||||
|
||||
# Plot
|
||||
|
||||
plt.figure()
|
||||
# plt.semilogx(f, M, '.b')
|
||||
# plt.semilogx(fsm, Msm, 'r')
|
||||
plt.plot(f, M, '.b')
|
||||
plt.plot(fsm, Msm, 'r')
|
||||
plt.xlabel('f (Hz)')
|
||||
plt.ylabel('magnitude')
|
||||
plt.xlim([100, fmax])
|
Loading…
Reference in New Issue
Block a user