Skip to content

Commit

Permalink
Merge pull request #3294 from obspy/filter
Browse files Browse the repository at this point in the history
Allow to specify filter freqs as args, Add different filter types
  • Loading branch information
megies committed May 16, 2024
2 parents 635af4e + 2ee848e commit 4110e43
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 22 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Changes:
(see #3251)
* inventory: additional formatting tweaks to expanded channel information
(see #3261)
* allow to specify Trace and Stream filter frequencies as arguments
(see #3294)
- obspy.clients.filesystem:
* update syntax for SQLAlchemy 2.0 compatibility (see #3269)
- obspy.clients.fdsn
Expand Down Expand Up @@ -41,6 +43,8 @@ Changes:
* all butterworth filters: correct zero-phase filtering of 2-d arrays and
filtering along non-default axis of 2-d arrays (see #3291)
* fix naming of input args in function "rotate_rt_ne()" (see #3383)
* add support for Chebyshev I/II, elliptic and Bessel filters alongside
the default Butterworth filters (see #3294)

1.4.1 (doi: 10.5281/zenodo.11093256)
====================================
Expand Down
9 changes: 6 additions & 3 deletions obspy/core/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -2181,15 +2181,18 @@ def simulate(self, paz_remove=None, paz_simulate=None,
return self

@raise_if_masked
def filter(self, type, **options):
def filter(self, type, *args, **options):
"""
Filter the data of all traces in the Stream.
:type type: str
:param type: String that specifies which filter is applied (e.g.
``"bandpass"``). See the `Supported Filter`_ section below for
further details.
:param options: Necessary keyword arguments for the respective filter
:param args: Only filter frequency/frequencies can be specified
as argument(s). Alternatively filter frequencies can be specified
as keyword arguments.
:param options: Keyword arguments for the respective filter
that will be passed on. (e.g. ``freqmin=1.0``, ``freqmax=20.0`` for
``"bandpass"``)
Expand Down Expand Up @@ -2242,7 +2245,7 @@ def filter(self, type, **options):
st.plot()
"""
for tr in self:
tr.filter(type, **options)
tr.filter(type, *args, **options)
return self

def trigger(self, type, **options):
Expand Down
22 changes: 22 additions & 0 deletions obspy/core/tests/test_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -2840,3 +2840,25 @@ def test_stream_trim_slice_same_length(self):
n1 = len(st.slice(None, utc)[0])
n2 = len(st.trim(None, utc)[0])
assert n1 == n2

def test_filter_ftype(self):
st = read()
ftypes = ['butter', 'cheby1', 'cheby2', 'ellip', 'bessel']
streams = [st.copy().filter('bandpass', 5, 10,
ftype=ftype, rp=10, rs=100)
for ftype in ftypes]
for st in streams[1:]:
assert not streams_almost_equal(st, streams[0])

def test_filter_freq_args(self):
st = read()
for filtert, freq in [('lowpass', 5,),
('highpass', 5)]:
stf1 = st.copy().filter(filtert, freq=freq)
stf2 = st.copy().filter(filtert, freq)
assert streams_almost_equal(stf2, stf1)
for filtert, freqmin, freqmax in [('bandpass', 1, 5),
('bandstop', 1, 5)]:
stf1 = st.copy().filter(filtert, freqmin=freqmin, freqmax=freqmax)
stf2 = st.copy().filter(filtert, freqmin, freqmax)
assert streams_almost_equal(stf2, stf1)
13 changes: 10 additions & 3 deletions obspy/core/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -1481,15 +1481,18 @@ def simulate(self, paz_remove=None, paz_simulate=None,

@_add_processing_info
@raise_if_masked
def filter(self, type, **options):
def filter(self, type, *args, **options):
"""
Filter the data of the current trace.
:type type: str
:param type: String that specifies which filter is applied (e.g.
``"bandpass"``). See the `Supported Filter`_ section below for
further details.
:param options: Necessary keyword arguments for the respective filter
:param args: Only filter frequency/frequencies can be specified
as argument(s). Alternatively filter frequencies can be specified
as keyword arguments.
:param options: Keyword arguments for the respective filter
that will be passed on. (e.g. ``freqmin=1.0``, ``freqmax=20.0`` for
``"bandpass"``)
Expand Down Expand Up @@ -1533,6 +1536,9 @@ def filter(self, type, **options):
>>> tr = st[0]
>>> tr.filter("highpass", freq=1.0) # doctest: +ELLIPSIS
<...Trace object at 0x...>
>>> tr2 = st[1]
>>> tr2.filter("lowpass", 1.0) # doctest: +ELLIPSIS
<...Trace object at 0x...>
>>> tr.plot() # doctest: +SKIP
.. plot::
Expand All @@ -1549,7 +1555,8 @@ def filter(self, type, **options):
# filtering
# the options dictionary is passed as kwargs to the function that is
# mapped according to the filter_functions dictionary
self.data = func(self.data, df=self.stats.sampling_rate, **options)
self.data = func(self.data, *args,
df=self.stats.sampling_rate, **options)
return self

@_add_processing_info
Expand Down
88 changes: 72 additions & 16 deletions obspy/signal/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@
remez, sosfilt)


def _filter(data, freqs, df, btype='band', ftype='butter',
corners=4, zerophase=False, axis=-1):
def _filter(data, freqs, df, rp=None, rs=None, btype='band', ftype='butter',
corners=4, zerophase=False, axis=-1, **kwargs):
fe = 0.5 * df
normalized_freqs = [f/fe for f in freqs]
sos = iirfilter(corners, normalized_freqs, btype=btype,
sos = iirfilter(corners, normalized_freqs, rp=rp, rs=rs, btype=btype,
ftype=ftype, output='sos')
if zerophase:
firstpass = np.flip(sosfilt(sos, data, axis=axis), axis=axis)
Expand All @@ -38,9 +38,9 @@ def _filter(data, freqs, df, btype='band', ftype='butter',


def bandpass(data, freqmin, freqmax, df, corners=4, zerophase=False,
axis=-1):
rp=None, rs=None, ftype='butter', axis=-1):
"""
Butterworth-Bandpass Filter.
Bandpass Filter.
Filter data from ``freqmin`` to ``freqmax`` using ``corners``
corners.
Expand All @@ -56,6 +56,19 @@ def bandpass(data, freqmin, freqmax, df, corners=4, zerophase=False,
:param zerophase: If True, apply filter once forwards and once backwards.
This results in twice the filter order but zero phase shift in
the resulting filtered trace.
:param rp:
For Chebyshev and elliptic filters, provides the maximum ripple
in the passband. (dB)
:param rs:
For Chebyshev and elliptic filters, provides the minimum attenuation
in the stop band. (dB)
:param ftype:
The type of filter
- Butterworth : 'butter' (default)
- Chebyshev I : 'cheby1'
- Chebyshev II : 'cheby2'
- Cauer/elliptic: 'ellip'
- Bessel/Thomson: 'bessel'
:param axis: The axis of the input data array along which to apply the
linear filter. The filter is applied to each subarray along this axis.
Default is -1.
Expand All @@ -71,18 +84,19 @@ def bandpass(data, freqmin, freqmax, df, corners=4, zerophase=False,
freqmax, fe)
warnings.warn(msg)
return highpass(data, freq=freqmin, df=df, corners=corners,
zerophase=zerophase)
ftype=ftype, zerophase=zerophase)
if low > 1:
msg = "Selected low corner frequency is above Nyquist."
raise ValueError(msg)
return _filter(data, (freqmin, freqmax), df, btype='band',
return _filter(data, (freqmin, freqmax), df, rp=rp, rs=rs,
btype='band', ftype=ftype,
corners=corners, zerophase=zerophase, axis=axis)


def bandstop(data, freqmin, freqmax, df, corners=4, zerophase=False,
axis=-1):
rp=None, rs=None, ftype='butter', axis=-1):
"""
Butterworth-Bandstop Filter.
Bandstop Filter.
Filter data removing data between frequencies ``freqmin`` and ``freqmax``
using ``corners`` corners.
Expand All @@ -98,6 +112,19 @@ def bandstop(data, freqmin, freqmax, df, corners=4, zerophase=False,
:param zerophase: If True, apply filter once forwards and once backwards.
This results in twice the number of corners but zero phase shift in
the resulting filtered trace.
:param rp:
For Chebyshev and elliptic filters, provides the maximum ripple
in the passband. (dB)
:param rs:
For Chebyshev and elliptic filters, provides the minimum attenuation
in the stop band. (dB)
:param ftype:
The type of filter
- Butterworth : 'butter' (default)
- Chebyshev I : 'cheby1'
- Chebyshev II : 'cheby2'
- Cauer/elliptic: 'ellip'
- Bessel/Thomson: 'bessel'
:param axis: The axis of the input data array along which to apply the
linear filter. The filter is applied to each subarray along this axis.
Default is -1.
Expand All @@ -115,14 +142,15 @@ def bandstop(data, freqmin, freqmax, df, corners=4, zerophase=False,
if low > 1:
msg = "Selected low corner frequency is above Nyquist."
raise ValueError(msg)
return _filter(data, (freqmin, freqmax), df, btype='bandstop',
return _filter(data, (freqmin, freqmax), df, rp=rp, rs=rs,
btype='bandstop', ftype=ftype,
corners=corners, zerophase=zerophase, axis=axis)


def lowpass(data, freq, df, corners=4, zerophase=False,
axis=-1):
rp=None, rs=None, ftype='butter', axis=-1):
"""
Butterworth-Lowpass Filter.
Lowpass Filter.
Filter data removing data over certain frequency ``freq`` using ``corners``
corners.
Expand All @@ -137,6 +165,19 @@ def lowpass(data, freq, df, corners=4, zerophase=False,
:param zerophase: If True, apply filter once forwards and once backwards.
This results in twice the number of corners but zero phase shift in
the resulting filtered trace.
:param rp:
For Chebyshev and elliptic filters, provides the maximum ripple
in the passband. (dB)
:param rs:
For Chebyshev and elliptic filters, provides the minimum attenuation
in the stop band. (dB)
:param ftype:
The type of filter
- Butterworth : 'butter' (default)
- Chebyshev I : 'cheby1'
- Chebyshev II : 'cheby2'
- Cauer/elliptic: 'ellip'
- Bessel/Thomson: 'bessel'
:param axis: The axis of the input data array along which to apply the
linear filter. The filter is applied to each subarray along this axis.
Default is -1.
Expand All @@ -150,14 +191,15 @@ def lowpass(data, freq, df, corners=4, zerophase=False,
msg = "Selected corner frequency is above Nyquist. " + \
"Setting Nyquist as high corner."
warnings.warn(msg)
return _filter(data, (freq,), df, btype='lowpass',
return _filter(data, (freq,), df, rp=rp, rs=rs,
btype='lowpass', ftype=ftype,
corners=corners, zerophase=zerophase, axis=axis)


def highpass(data, freq, df, corners=4, zerophase=False,
axis=-1):
rp=None, rs=None, ftype='butter', axis=-1):
"""
Butterworth-Highpass Filter.
Highpass Filter.
Filter data removing data below certain frequency ``freq`` using
``corners`` corners.
Expand All @@ -172,6 +214,19 @@ def highpass(data, freq, df, corners=4, zerophase=False,
:param zerophase: If True, apply filter once forwards and once backwards.
This results in twice the number of corners but zero phase shift in
the resulting filtered trace.
:param rp:
For Chebyshev and elliptic filters, provides the maximum ripple
in the passband. (dB)
:param rs:
For Chebyshev and elliptic filters, provides the minimum attenuation
in the stop band. (dB)
:param ftype:
The type of filter
- Butterworth : 'butter' (default)
- Chebyshev I : 'cheby1'
- Chebyshev II : 'cheby2'
- Cauer/elliptic: 'ellip'
- Bessel/Thomson: 'bessel'
:param axis: The axis of the input data array along which to apply the
linear filter. The filter is applied to each subarray along this axis.
Default is -1.
Expand All @@ -183,7 +238,8 @@ def highpass(data, freq, df, corners=4, zerophase=False,
if f > 1:
msg = "Selected corner frequency is above Nyquist."
raise ValueError(msg)
return _filter(data, (freq,), df, btype='highpass',
return _filter(data, (freq,), df, rp=rp, rs=rs,
btype='highpass', ftype=ftype,
corners=corners, zerophase=zerophase, axis=axis)


Expand Down

0 comments on commit 4110e43

Please sign in to comment.