DASPy-toolbox 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- DASPy_toolbox-1.0.0.dist-info/LICENSE.txt +1 -0
- DASPy_toolbox-1.0.0.dist-info/METADATA +85 -0
- DASPy_toolbox-1.0.0.dist-info/RECORD +49 -0
- DASPy_toolbox-1.0.0.dist-info/WHEEL +5 -0
- DASPy_toolbox-1.0.0.dist-info/entry_points.txt +2 -0
- DASPy_toolbox-1.0.0.dist-info/top_level.txt +1 -0
- daspy/__init__.py +4 -0
- daspy/advanced_tools/__init__.py +0 -0
- daspy/advanced_tools/channel.py +354 -0
- daspy/advanced_tools/decomposition.py +165 -0
- daspy/advanced_tools/denoising.py +276 -0
- daspy/advanced_tools/fdct.py +789 -0
- daspy/advanced_tools/strain2vel.py +245 -0
- daspy/basic_tools/__init__.py +0 -0
- daspy/basic_tools/filter.py +257 -0
- daspy/basic_tools/freqattributes.py +117 -0
- daspy/basic_tools/preprocessing.py +238 -0
- daspy/basic_tools/visualization.py +186 -0
- daspy/core/__init__.py +4 -0
- daspy/core/collection.py +279 -0
- daspy/core/dasdatetime.py +72 -0
- daspy/core/example.pkl +0 -0
- daspy/core/make_example.py +32 -0
- daspy/core/read.py +544 -0
- daspy/core/section.py +1319 -0
- daspy/core/write.py +282 -0
- daspy/seismic_detection/__init__.py +1 -0
- daspy/seismic_detection/calc_travel_time.py +23 -0
- daspy/seismic_detection/core.py +119 -0
- daspy/seismic_detection/detection.py +12 -0
- daspy/seismic_detection/gamma/__init__.py +13 -0
- daspy/seismic_detection/gamma/_base.py +549 -0
- daspy/seismic_detection/gamma/_bayesian_mixture.py +875 -0
- daspy/seismic_detection/gamma/_gaussian_mixture.py +866 -0
- daspy/seismic_detection/gamma/app.py +192 -0
- daspy/seismic_detection/gamma/seismic_ops.py +478 -0
- daspy/seismic_detection/gamma/utils.py +512 -0
- daspy/seismic_detection/location.py +266 -0
- daspy/seismic_detection/magnitude.py +43 -0
- daspy/seismic_detection/phase_picking.py +67 -0
- daspy/structure_imaging/__init__.py +0 -0
- daspy/structure_imaging/ambient_noise.py +4 -0
- daspy/structure_imaging/dispersion.py +27 -0
- daspy/structure_imaging/fault_zone.py +59 -0
- daspy/structure_imaging/inversion.py +6 -0
- daspy/traffic_monitoring/JamDetection.py +6 -0
- daspy/traffic_monitoring/SpeedMeasurement.py +6 -0
- daspy/traffic_monitoring/VehicleDetection.py +6 -0
- daspy/traffic_monitoring/__init__.py +0 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# Purpose: Convert strain rate data to velocity
|
|
2
|
+
# Author: Minzhe Hu
|
|
3
|
+
# Date: 2024.6.8
|
|
4
|
+
# Email: hmz2018@mail.ustc.edu.cn
|
|
5
|
+
import numpy as np
|
|
6
|
+
from numpy.fft import irfft2, ifftshift
|
|
7
|
+
from scipy.signal import hilbert
|
|
8
|
+
from daspy.basic_tools.freqattributes import next_pow_2, fk_transform
|
|
9
|
+
from daspy.basic_tools.preprocessing import padding, cosine_taper
|
|
10
|
+
from daspy.basic_tools.filter import bandpass
|
|
11
|
+
from daspy.advanced_tools.fdct import fdct_wrapping, ifdct_wrapping
|
|
12
|
+
from daspy.advanced_tools.denoising import _velocity_bin
|
|
13
|
+
from daspy.advanced_tools.decomposition import fk_fan_mask
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def fk_rescaling(data, dx, fs, taper=(0.02, 0.05), pad='default', fmax=None,
|
|
17
|
+
kmin=(1 / 2000, 1 / 3000), vmax=(15000, 30000), edge=0.2,
|
|
18
|
+
turning=None, verbose=False):
|
|
19
|
+
"""
|
|
20
|
+
Convert strain/strain rate to velocity/acceleration by fk rescaling.
|
|
21
|
+
|
|
22
|
+
:param data: numpy.ndarray. Data to do fk rescaling.
|
|
23
|
+
:param dx: Channel interval in m.
|
|
24
|
+
:param fs: Sampling rate in Hz.
|
|
25
|
+
:param taper: float or sequence of floats. Each float means decimal
|
|
26
|
+
percentage of Tukey taper for corresponding dimension (ranging from 0 to
|
|
27
|
+
1). Default is 0.1 which tapers 5% from the beginning and 5% from the
|
|
28
|
+
end.
|
|
29
|
+
:param pad: Pad the data or not. It can be float or sequence of floats. Each
|
|
30
|
+
float means padding percentage before FFT for corresponding dimension.
|
|
31
|
+
If set to 0.1 will pad 5% before the beginning and after the end.
|
|
32
|
+
'default' means pad both dimensions to next power of 2. None or False
|
|
33
|
+
means don't pad data before or during Fast Fourier Transform.
|
|
34
|
+
:param fmax, kmin, vmax: float or or sequence of 2 floats. Sequence of 2
|
|
35
|
+
floats represents the start and end of taper. Setting these parameters
|
|
36
|
+
can reduce artifacts.
|
|
37
|
+
:param edge: float. The width of fan mask taper edge.
|
|
38
|
+
:param turning: Sequence of int. Channel number of turning points.
|
|
39
|
+
:param verbose: If True, return converted data, f-k spectrum, frequency
|
|
40
|
+
sequence, wavenumber sequence and f-k mask.
|
|
41
|
+
:return: Converted data and some variables in the process if verbose==True.
|
|
42
|
+
"""
|
|
43
|
+
if turning is not None:
|
|
44
|
+
data_vel = np.zeros_like(data)
|
|
45
|
+
start_ch = [0, *turning]
|
|
46
|
+
end_ch = [*turning, len(data)]
|
|
47
|
+
for (s, e) in zip(start_ch, end_ch):
|
|
48
|
+
data_vel[s:e] = fk_rescaling(data[s:e], dx, fs, taper=taper,
|
|
49
|
+
pad=pad, fmax=fmax, kmin=kmin,
|
|
50
|
+
vmax=vmax, edge=edge, verbose=False)
|
|
51
|
+
else:
|
|
52
|
+
data_tp = cosine_taper(data, taper)
|
|
53
|
+
|
|
54
|
+
if pad == 'default':
|
|
55
|
+
nch, nt = data.shape
|
|
56
|
+
dn = (next_pow_2(nch) - nch, next_pow_2(nt) - nt)
|
|
57
|
+
nfft = None
|
|
58
|
+
elif pad is None or pad is False:
|
|
59
|
+
dn = 0
|
|
60
|
+
nfft = None
|
|
61
|
+
else:
|
|
62
|
+
dn = np.round(np.array(pad) * data.shape).astype(int)
|
|
63
|
+
nfft = 'default'
|
|
64
|
+
|
|
65
|
+
data_pd = padding(data_tp, dn)
|
|
66
|
+
nch, nt = data_pd.shape
|
|
67
|
+
|
|
68
|
+
fk, f, k = fk_transform(data_pd, dx, fs, taper=taper, nfft=nfft)
|
|
69
|
+
|
|
70
|
+
ff = np.tile(f, (len(k), 1))
|
|
71
|
+
kk = np.tile(k, (len(f), 1)).T
|
|
72
|
+
vv = - np.divide(ff, kk, out=np.ones_like(ff) * 1e10, where=kk != 0)
|
|
73
|
+
|
|
74
|
+
mask = fk_fan_mask(f, k, fmax=fmax, kmin=kmin, vmax=vmax, edge=edge) * vv
|
|
75
|
+
mask[kk == 0] = 0
|
|
76
|
+
|
|
77
|
+
data_vel = irfft2(ifftshift(fk * mask, axes=0)).real[:nch, :nt]
|
|
78
|
+
data_vel = padding(data_vel, dn, reverse=True)
|
|
79
|
+
|
|
80
|
+
if verbose:
|
|
81
|
+
return data_vel, fk, f, k, mask
|
|
82
|
+
return data_vel
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def curvelet_conversion(data, dx, fs, pad=0.3, scale_begin=2, nbscales=None,
|
|
86
|
+
nbangles=16, turning=None):
|
|
87
|
+
"""
|
|
88
|
+
Use curevelet transform to convert strain/strain rate to
|
|
89
|
+
velocity/acceleration. {Yang et al. , 2023, Geophys. Res. Lett.}
|
|
90
|
+
|
|
91
|
+
:param data: numpy.ndarray. Data to convert.
|
|
92
|
+
:param dx: Channel interval in m.
|
|
93
|
+
:param fs: Sampling rate in Hz.
|
|
94
|
+
:param pad: float or sequence of floats. Each float means padding percentage
|
|
95
|
+
before FFT for corresponding dimension. If set to 0.1 will pad 5% before
|
|
96
|
+
the beginning and after the end.
|
|
97
|
+
:param scale_begin: int. The beginning scale to do conversion.
|
|
98
|
+
:param nbscales: int. Number of scales including the coarsest wavelet level.
|
|
99
|
+
Default set to ceil(log2(min(M,N)) - 3).
|
|
100
|
+
:param nbangles: int. Number of angles at the 2nd coarsest level,
|
|
101
|
+
minimum 8, must be a multiple of 4.
|
|
102
|
+
:param turning: Sequence of int. Channel number of turning points.
|
|
103
|
+
:return: numpy.ndarray. Converted data.
|
|
104
|
+
"""
|
|
105
|
+
if turning is not None:
|
|
106
|
+
print(1)
|
|
107
|
+
data_vel = np.zeros_like(data)
|
|
108
|
+
start_ch = [0, *turning]
|
|
109
|
+
end_ch = [*turning, len(data)]
|
|
110
|
+
for (s, e) in zip(start_ch, end_ch):
|
|
111
|
+
data_vel[s:e] = curvelet_conversion(data[s:e], dx, fs, pad=pad,
|
|
112
|
+
scale_begin=scale_begin,
|
|
113
|
+
nbscales=nbscales,
|
|
114
|
+
nbangles=nbangles, turning=None)
|
|
115
|
+
else:
|
|
116
|
+
if pad is None or pad is False:
|
|
117
|
+
pad = 0
|
|
118
|
+
dn = np.round(np.array(pad) * data.shape).astype(int)
|
|
119
|
+
data_pd = padding(data, dn)
|
|
120
|
+
|
|
121
|
+
C = fdct_wrapping(data_pd, is_real=True, finest=1, nbscales=nbscales,
|
|
122
|
+
nbangles_coarse=nbangles)
|
|
123
|
+
|
|
124
|
+
# rescale with velocity
|
|
125
|
+
np.seterr(divide='ignore')
|
|
126
|
+
for s in range(0, scale_begin - 1):
|
|
127
|
+
for w in range(len(C[s])):
|
|
128
|
+
C[s][w] *= 0
|
|
129
|
+
|
|
130
|
+
for s in range(scale_begin - 1, len(C)):
|
|
131
|
+
nbangles = len(C[s])
|
|
132
|
+
velocity = _velocity_bin(nbangles, fs, dx)
|
|
133
|
+
factors = np.mean(velocity, axis=1)
|
|
134
|
+
for w in range(nbangles):
|
|
135
|
+
if abs(factors[w]) == np.inf:
|
|
136
|
+
factors[w] = abs(velocity[w]).min() * \
|
|
137
|
+
np.sign(velocity[w, 0]) * 2
|
|
138
|
+
C[s][w] *= factors[w]
|
|
139
|
+
|
|
140
|
+
data_vel = ifdct_wrapping(C, is_real=True, size=data_pd.shape)
|
|
141
|
+
data_vel = padding(data_vel, dn, reverse=True)
|
|
142
|
+
|
|
143
|
+
return data_vel
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def slowness(g, dx, fs, slm, sls, swin=2):
|
|
147
|
+
"""
|
|
148
|
+
Estimate the slowness time series by calculate semblance.
|
|
149
|
+
{Lior et al., 2021, Solid Earth}
|
|
150
|
+
|
|
151
|
+
:param g: 2-dimensional array. time series of adjacent channels used for
|
|
152
|
+
estimating slowness
|
|
153
|
+
:param dx: float. Spatical sampling rate (in m)
|
|
154
|
+
:param fs: float. Sampling rate of records
|
|
155
|
+
:param slm: float. Slowness x max
|
|
156
|
+
:param sls: float. Slowness step
|
|
157
|
+
:param swin: int. Slowness smooth window
|
|
158
|
+
:return: Sequences of slowness and sembalence.
|
|
159
|
+
"""
|
|
160
|
+
L = (len(g) - 1) // 2
|
|
161
|
+
nt = len(g[0])
|
|
162
|
+
h = np.imag(hilbert(g))
|
|
163
|
+
grdpnt = round(slm / sls)
|
|
164
|
+
sem = np.zeros((2 * grdpnt + 1, nt))
|
|
165
|
+
gap = round(slm * dx * L * fs)
|
|
166
|
+
|
|
167
|
+
h_ex = np.zeros((len(g), nt + 2 * gap))
|
|
168
|
+
h_ex[:, gap:-gap] = h
|
|
169
|
+
g_ex = np.zeros((len(g), nt + 2 * gap))
|
|
170
|
+
g_ex[:, gap:-gap] = g
|
|
171
|
+
|
|
172
|
+
for i in range(2 * grdpnt + 1):
|
|
173
|
+
px = (i - grdpnt) * sls
|
|
174
|
+
if abs(px) < 1e-5:
|
|
175
|
+
continue
|
|
176
|
+
gt = np.zeros(g.shape)
|
|
177
|
+
ht = np.zeros(h.shape)
|
|
178
|
+
for j in range(-L, L):
|
|
179
|
+
shift = round(px * j * dx * fs)
|
|
180
|
+
gt[j + L] = g_ex[j + L, gap + shift:gap + shift + nt]
|
|
181
|
+
ht[j + L] = h_ex[j + L, gap + shift:gap + shift + nt]
|
|
182
|
+
sem[i] = (np.sum(gt, axis=0)**2 + np.sum(ht, axis=0)**2) / \
|
|
183
|
+
np.sum(gt**2 + ht**2, axis=0) / (2 * L + 1)
|
|
184
|
+
p = (np.argmax(sem, axis=0) - grdpnt) * sls
|
|
185
|
+
# smooth P
|
|
186
|
+
for i in range(swin, nt - swin):
|
|
187
|
+
win = p[i - swin:i + swin + 1]
|
|
188
|
+
sign = np.sign(sum(np.sign(win)))
|
|
189
|
+
win = [px for px in win if np.sign(px) == sign]
|
|
190
|
+
p[i] = np.mean(win)
|
|
191
|
+
|
|
192
|
+
return p, sem
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def slant_stacking(data, dx, fs, L=None, slm=0.01,
|
|
196
|
+
sls=0.000125, frqlow=0.1, frqhigh=15, turning=None,
|
|
197
|
+
channel='all'):
|
|
198
|
+
"""
|
|
199
|
+
Convert strain to velocity based on slant-stack.
|
|
200
|
+
|
|
201
|
+
:param data: 2-dimensional array. Axis 0 is channel number and axis 1 is
|
|
202
|
+
time series
|
|
203
|
+
:param dx: float. Spatical sampling rate (in m)
|
|
204
|
+
:param L: int. the number of adjacent channels over which slowness is
|
|
205
|
+
estimated
|
|
206
|
+
:param slm: float. Slowness x max
|
|
207
|
+
:param sls: float. slowness step
|
|
208
|
+
:param freqmin: Pass band low corner frequency.
|
|
209
|
+
:param freqmax: Pass band high corner frequency.
|
|
210
|
+
:param turning: Sequence of int. Channel number of turning points.
|
|
211
|
+
:param channel: int or list or 'all'. convert a certain channel number /
|
|
212
|
+
certain channel range / all channels.
|
|
213
|
+
:return: Converted velocity data
|
|
214
|
+
"""
|
|
215
|
+
if L is None:
|
|
216
|
+
L = round(50 / dx)
|
|
217
|
+
|
|
218
|
+
nch, nt = data.shape
|
|
219
|
+
if isinstance(channel, str) and channel == 'all':
|
|
220
|
+
channel = list(range(nch))
|
|
221
|
+
elif isinstance(channel, int):
|
|
222
|
+
channel = [channel]
|
|
223
|
+
|
|
224
|
+
if turning is not None:
|
|
225
|
+
data_vel = np.zeros((0, len(data[0])))
|
|
226
|
+
start_ch = [0, *turning]
|
|
227
|
+
end_ch = [*turning, len(data)]
|
|
228
|
+
for (s, e) in zip(start_ch, end_ch):
|
|
229
|
+
channel_seg = [ch-s for ch in range(s,e) if ch in channel]
|
|
230
|
+
if len(channel_seg):
|
|
231
|
+
d_vel = slant_stacking(data[s:e], dx, fs, L=L, slm=slm, sls=sls,
|
|
232
|
+
frqlow=frqlow, frqhigh=frqhigh,
|
|
233
|
+
turning=None, channel=channel_seg)
|
|
234
|
+
data_vel = np.vstack((data_vel, d_vel))
|
|
235
|
+
else:
|
|
236
|
+
data_ex = padding(data, (2 * L, 0))
|
|
237
|
+
swin = int(max((1 / frqhigh * fs) // 2, 1))
|
|
238
|
+
data_vel = np.zeros((len(channel), nt))
|
|
239
|
+
for i, ch in enumerate(channel):
|
|
240
|
+
p, _ = slowness(data_ex[ch:ch + 2 * L + 1], dx, fs, slm, sls,
|
|
241
|
+
swin=swin)
|
|
242
|
+
data_vel[i] = bandpass(data[ch] / p, fs=fs, freqmin=frqlow,
|
|
243
|
+
freqmax=frqhigh)
|
|
244
|
+
|
|
245
|
+
return data_vel
|
|
File without changes
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# Purpose: Filter the waveform
|
|
2
|
+
# Author: Minzhe Hu
|
|
3
|
+
# Date: 2024.10.16
|
|
4
|
+
# Email: hmz2018@mail.ustc.edu.cn
|
|
5
|
+
# Modified from https://docs.obspy.org/_modules/obspy/signal/filter.html
|
|
6
|
+
import warnings
|
|
7
|
+
import numpy as np
|
|
8
|
+
from scipy.signal import cheb2ord, cheby2, hilbert, iirfilter, zpk2sos, sosfilt
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def bandpass(data, fs, freqmin, freqmax, corners=4, zi=None, zerophase=True):
|
|
12
|
+
"""
|
|
13
|
+
Filter data from 'freqmin' to 'freqmax' using Butterworth bandpass filter of
|
|
14
|
+
'corners' corners.
|
|
15
|
+
|
|
16
|
+
:param data: numpy.ndarray. Data to filter.
|
|
17
|
+
:param fs: Sampling rate in Hz.
|
|
18
|
+
:param freqmin: Pass band low corner frequency.
|
|
19
|
+
:param freqmax: Pass band high corner frequency.
|
|
20
|
+
:param corners: Filter corners / order.
|
|
21
|
+
:param zi : None, 0, or array_like. Initial conditions for the cascaded
|
|
22
|
+
filter delays. It is a vector of shape (n_sections, nch, 2). Set to 0 to
|
|
23
|
+
trigger a output of the final filter delay values.
|
|
24
|
+
:param zerophase: If True, apply filter once forwards and once backwards.
|
|
25
|
+
This results in twice the number of corners but zero phase shift in
|
|
26
|
+
the resulting filtered data. Only valid when zi is None.
|
|
27
|
+
:return: Filtered data and the final filter delay values (if zi is not
|
|
28
|
+
None).
|
|
29
|
+
"""
|
|
30
|
+
if len(data.shape) == 1:
|
|
31
|
+
data = data[np.newaxis, :]
|
|
32
|
+
fe = 0.5 * fs
|
|
33
|
+
low = freqmin / fe
|
|
34
|
+
high = freqmax / fe
|
|
35
|
+
# raise for some bad scenarios
|
|
36
|
+
if high - 1.0 > -1e-6:
|
|
37
|
+
msg = ('Selected high corner frequency ({}) of bandpass is at or ' +
|
|
38
|
+
'above Nyquist ({}). Applying a high-pass instead.').format(
|
|
39
|
+
freqmax, fe)
|
|
40
|
+
warnings.warn(msg)
|
|
41
|
+
return highpass(data, freq=freqmin, fs=fs, corners=corners,
|
|
42
|
+
zerophase=zerophase)
|
|
43
|
+
if low > 1:
|
|
44
|
+
msg = 'Selected low corner frequency is above Nyquist.'
|
|
45
|
+
raise ValueError(msg)
|
|
46
|
+
z, p, k = iirfilter(corners, [low, high], btype='band', ftype='butter',
|
|
47
|
+
output='zpk')
|
|
48
|
+
sos = zpk2sos(z, p, k)
|
|
49
|
+
if zi is None:
|
|
50
|
+
data_flt = sosfilt(sos, data)
|
|
51
|
+
if zerophase:
|
|
52
|
+
data_flt = sosfilt(sos, data_flt[:, ::-1])[:, ::-1]
|
|
53
|
+
return data_flt
|
|
54
|
+
elif isinstance(zi, (int, float)):
|
|
55
|
+
zi = np.ones((sos.shape[0], len(data), 2)) * zi
|
|
56
|
+
|
|
57
|
+
data_flt, zf = sosfilt(sos, data, zi=zi)
|
|
58
|
+
return data_flt, zf
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def bandstop(data, fs, freqmin, freqmax, corners=4, zi=None, zerophase=False):
|
|
62
|
+
"""
|
|
63
|
+
Filter data removing data between frequencies 'freqmin' and 'freqmax' using
|
|
64
|
+
Butterworth bandstop filter of 'corners' corners.
|
|
65
|
+
|
|
66
|
+
:param data: numpy.ndarray. Data to filter.
|
|
67
|
+
:param fs: Sampling rate in Hz.
|
|
68
|
+
:param freqmin: Stop band low corner frequency.
|
|
69
|
+
:param freqmax: Stop band high corner frequency.
|
|
70
|
+
:param corners: Filter corners / order.
|
|
71
|
+
:param zi : None, 0, or array_like. Initial conditions for the cascaded
|
|
72
|
+
filter delays. It is a vector of shape (n_sections, nch, 2). Set to 0 to
|
|
73
|
+
trigger a output of the final filter delay values.
|
|
74
|
+
:param zerophase: If True, apply filter once forwards and once backwards.
|
|
75
|
+
This results in twice the number of corners but zero phase shift in
|
|
76
|
+
the resulting filtered data. Only valid when zi is None.
|
|
77
|
+
:return: Filtered data and the final filter delay values (if zi is not
|
|
78
|
+
None).
|
|
79
|
+
"""
|
|
80
|
+
if len(data.shape) == 1:
|
|
81
|
+
data = data[np.newaxis, :]
|
|
82
|
+
fe = 0.5 * fs
|
|
83
|
+
low = freqmin / fe
|
|
84
|
+
high = freqmax / fe
|
|
85
|
+
# raise for some bad scenarios
|
|
86
|
+
if high > 1:
|
|
87
|
+
high = 1.0
|
|
88
|
+
msg = 'Selected high corner frequency is above Nyquist. Setting ' + \
|
|
89
|
+
'Nyquist as high corner.'
|
|
90
|
+
warnings.warn(msg)
|
|
91
|
+
if low > 1:
|
|
92
|
+
msg = 'Selected low corner frequency is above Nyquist.'
|
|
93
|
+
raise ValueError(msg)
|
|
94
|
+
z, p, k = iirfilter(corners, [low, high],
|
|
95
|
+
btype='bandstop', ftype='butter', output='zpk')
|
|
96
|
+
sos = zpk2sos(z, p, k)
|
|
97
|
+
if zi is None:
|
|
98
|
+
data_flt = sosfilt(sos, data)
|
|
99
|
+
if zerophase:
|
|
100
|
+
data_flt = sosfilt(sos, data_flt[:, ::-1])[:, ::-1]
|
|
101
|
+
return data_flt
|
|
102
|
+
elif isinstance(zi, (int, float)):
|
|
103
|
+
zi = np.ones((sos.shape[0], len(data), 2)) * zi
|
|
104
|
+
|
|
105
|
+
data_flt, zf = sosfilt(sos, data, zi=zi)
|
|
106
|
+
return data_flt, zf
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def lowpass(data, fs, freq, corners=4, zi=None, zerophase=False):
|
|
110
|
+
"""
|
|
111
|
+
Filter data removing data over certain frequency 'freq' using Butterworth
|
|
112
|
+
lowpass filter of 'corners' corners.
|
|
113
|
+
|
|
114
|
+
:param data: numpy.ndarray. Data to filter.
|
|
115
|
+
:param fs: Sampling rate in Hz.
|
|
116
|
+
:param freq: Filter corner frequency.
|
|
117
|
+
:param corners: Filter corners / order.
|
|
118
|
+
:param zi : None, 0, or array_like. Initial conditions for the cascaded
|
|
119
|
+
filter delays. It is a vector of shape (n_sections, nch, 2). Set to 0 to
|
|
120
|
+
trigger a output of the final filter delay values.
|
|
121
|
+
:param zerophase: If True, apply filter once forwards and once backwards.
|
|
122
|
+
This results in twice the number of corners but zero phase shift in
|
|
123
|
+
the resulting filtered data. Only valid when zi is None.
|
|
124
|
+
:return: Filtered data and the final filter delay values (if zi is not
|
|
125
|
+
None).
|
|
126
|
+
"""
|
|
127
|
+
if len(data.shape) == 1:
|
|
128
|
+
data = data[np.newaxis, :]
|
|
129
|
+
fe = 0.5 * fs
|
|
130
|
+
f = freq / fe
|
|
131
|
+
# raise for some bad scenarios
|
|
132
|
+
if f > 1:
|
|
133
|
+
f = 1.0
|
|
134
|
+
msg = 'Selected corner frequency is above Nyquist. Setting Nyquist ' + \
|
|
135
|
+
'as high corner.'
|
|
136
|
+
warnings.warn(msg)
|
|
137
|
+
z, p, k = iirfilter(corners, f, btype='lowpass', ftype='butter',
|
|
138
|
+
output='zpk')
|
|
139
|
+
sos = zpk2sos(z, p, k)
|
|
140
|
+
if zi is None:
|
|
141
|
+
data_flt = sosfilt(sos, data)
|
|
142
|
+
if zerophase:
|
|
143
|
+
data_flt = sosfilt(sos, data_flt[:, ::-1])[:, ::-1]
|
|
144
|
+
return data_flt
|
|
145
|
+
elif isinstance(zi, (int, float)):
|
|
146
|
+
zi = np.ones((sos.shape[0], len(data), 2)) * zi
|
|
147
|
+
|
|
148
|
+
data_flt, zf = sosfilt(sos, data, zi=zi)
|
|
149
|
+
return data_flt, zf
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def lowpass_cheby_2(data, fs, freq, maxorder=12, zi=None, ba=False,
|
|
153
|
+
freq_passband=False):
|
|
154
|
+
"""
|
|
155
|
+
Filter data by passing data only below a certain frequency. The main purpose
|
|
156
|
+
of this cheby2 filter is downsampling. This method will iteratively design a
|
|
157
|
+
filter, whose pass band frequency is determined dynamically, such that the
|
|
158
|
+
values above the stop band frequency are lower than -96dB.
|
|
159
|
+
|
|
160
|
+
:param data: numpy.ndarray. Data to filter.
|
|
161
|
+
:param fs: Sampling rate in Hz.
|
|
162
|
+
:param freq: The frequency above which signals are attenuated with 95 dB.
|
|
163
|
+
:param maxorder: Maximal order of the designed cheby2 filter.
|
|
164
|
+
:param zi : None, 0, or array_like. Initial conditions for the cascaded
|
|
165
|
+
filter delays. It is a vector of shape (n_sections, nch, 2). Set to 0 to
|
|
166
|
+
trigger a output of the final filter delay values.
|
|
167
|
+
:param ba: If True return only the filter coefficients (b, a) instead of
|
|
168
|
+
filtering.
|
|
169
|
+
:param freq_passband: If True return additionally to the filtered data, the
|
|
170
|
+
iteratively determined pass band frequency.
|
|
171
|
+
:return: Filtered data, the final filter delay values (if zi is not None)
|
|
172
|
+
and the determined pass band frequency (if freq_passband is True).
|
|
173
|
+
"""
|
|
174
|
+
if data.ndim == 1:
|
|
175
|
+
data = data[np.newaxis, :]
|
|
176
|
+
|
|
177
|
+
nyquist = fs * 0.5
|
|
178
|
+
# rp - maximum ripple of passband, rs - attenuation of stopband
|
|
179
|
+
rp, rs, order = 1, 96, 1e99
|
|
180
|
+
ws = freq / nyquist # stop band frequency
|
|
181
|
+
wp = ws # pass band frequency
|
|
182
|
+
# raise for some bad scenarios
|
|
183
|
+
if ws > 1:
|
|
184
|
+
ws = 1.0
|
|
185
|
+
warnings.warn('Selected corner frequency is above Nyquist. Setting '
|
|
186
|
+
'Nyquist as high corner.')
|
|
187
|
+
while True:
|
|
188
|
+
if order <= maxorder:
|
|
189
|
+
break
|
|
190
|
+
wp = wp * 0.99
|
|
191
|
+
order, wn = cheb2ord(wp, ws, rp, rs, analog=0)
|
|
192
|
+
if ba:
|
|
193
|
+
return cheby2(order, rs, wn, btype='low', analog=0, output='ba')
|
|
194
|
+
z, p, k = cheby2(order, rs, wn, btype='low', analog=0, output='zpk')
|
|
195
|
+
sos = zpk2sos(z, p, k)
|
|
196
|
+
if zi is None:
|
|
197
|
+
data_flt = sosfilt(sos, data)
|
|
198
|
+
if freq_passband:
|
|
199
|
+
return data_flt, wp * nyquist
|
|
200
|
+
return data_flt
|
|
201
|
+
elif isinstance(zi, (int, float)):
|
|
202
|
+
zi = np.ones((sos.shape[0], len(data), 2)) * zi
|
|
203
|
+
|
|
204
|
+
data_flt, zf = sosfilt(sos, data, zi=zi)
|
|
205
|
+
if freq_passband:
|
|
206
|
+
return data_flt, zf, wp * nyquist
|
|
207
|
+
return data_flt, zf
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def highpass(data, fs, freq, corners=4, zi=None, zerophase=False):
|
|
211
|
+
"""
|
|
212
|
+
Filter data removing data below certain frequency 'freq' using Butterworth
|
|
213
|
+
highpass filter of 'corners' corners.
|
|
214
|
+
|
|
215
|
+
:param data: numpy.ndarray. Data to filter.
|
|
216
|
+
:param fs: Sampling rate in Hz.
|
|
217
|
+
:param freq: Filter corner frequency.
|
|
218
|
+
:param corners: Filter corners / order.
|
|
219
|
+
:param zerophase: If True, apply filter once forwards and once backwards.
|
|
220
|
+
This results in twice the number of corners but zero phase shift in
|
|
221
|
+
the resulting filtered data. Only valid when zi is None.
|
|
222
|
+
:return: Filtered data and the final filter delay values (if zi is not
|
|
223
|
+
None).
|
|
224
|
+
"""
|
|
225
|
+
if len(data.shape) == 1:
|
|
226
|
+
data = data[np.newaxis, :]
|
|
227
|
+
fe = 0.5 * fs
|
|
228
|
+
f = freq / fe
|
|
229
|
+
# raise for some bad scenarios
|
|
230
|
+
if f > 1:
|
|
231
|
+
msg = 'Selected corner frequency is above Nyquist.'
|
|
232
|
+
raise ValueError(msg)
|
|
233
|
+
z, p, k = iirfilter(corners, f, btype='highpass', ftype='butter',
|
|
234
|
+
output='zpk')
|
|
235
|
+
sos = zpk2sos(z, p, k)
|
|
236
|
+
if zi is None:
|
|
237
|
+
data_flt = sosfilt(sos, data)
|
|
238
|
+
if zerophase:
|
|
239
|
+
data_flt = sosfilt(sos, data_flt[:, ::-1])[:, ::-1]
|
|
240
|
+
return data_flt
|
|
241
|
+
elif isinstance(zi, (int, float)):
|
|
242
|
+
zi = np.ones((sos.shape[0], len(data), 2)) * zi
|
|
243
|
+
|
|
244
|
+
data_flt, zf = sosfilt(sos, data, zi=zi)
|
|
245
|
+
return data_flt, zf
|
|
246
|
+
|
|
247
|
+
def envelope(data):
|
|
248
|
+
"""
|
|
249
|
+
Computes the envelope of the given data. The envelope is determined by
|
|
250
|
+
adding the squared amplitudes of the data and it's Hilbert-Transform and
|
|
251
|
+
then taking the square-root. The envelope at the start/end should not be
|
|
252
|
+
taken too seriously.
|
|
253
|
+
|
|
254
|
+
:param data: numpy.ndarray. Data to make envelope of.
|
|
255
|
+
:return: Envelope of input data.
|
|
256
|
+
"""
|
|
257
|
+
return abs(hilbert(data, axis=-1))
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Purpose: Analyze frequency attribute and transform in frequency domain
|
|
2
|
+
# Author: Minzhe Hu
|
|
3
|
+
# Date: 2024.6.8
|
|
4
|
+
# Email: hmz2018@mail.ustc.edu.cn
|
|
5
|
+
import numpy as np
|
|
6
|
+
from numpy.fft import rfft, rfft2, fftshift, fftfreq, rfftfreq
|
|
7
|
+
from scipy.signal import stft
|
|
8
|
+
from daspy.basic_tools.preprocessing import demeaning, detrending, cosine_taper
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def next_pow_2(i):
|
|
12
|
+
"""
|
|
13
|
+
Find the next power of two.
|
|
14
|
+
|
|
15
|
+
:param i: float or int.
|
|
16
|
+
:return: int. The next power of two for i.
|
|
17
|
+
"""
|
|
18
|
+
buf = np.ceil(np.log2(i))
|
|
19
|
+
return np.power(2, buf).astype(int)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def spectrum(data, fs, taper=0.05, nfft='default'):
|
|
23
|
+
"""
|
|
24
|
+
Computes the spectrum of the given data.
|
|
25
|
+
|
|
26
|
+
:param data: numpy.ndarray. Data to make spectrum of.
|
|
27
|
+
:param fs: Sampling rate in Hz.
|
|
28
|
+
:param taper: Decimal percentage of Tukey taper.
|
|
29
|
+
:param nfft: Number of points for FFT. None = sampling points, 'default' =
|
|
30
|
+
next power of 2 of sampling points.
|
|
31
|
+
:return: Spectrum and frequency sequence.
|
|
32
|
+
"""
|
|
33
|
+
if len(data.shape) == 1:
|
|
34
|
+
data = data.reshape(1, len(data))
|
|
35
|
+
elif len(data.shape) != 2:
|
|
36
|
+
raise ValueError("Data should be 1-D or 2-D array")
|
|
37
|
+
data = cosine_taper(data, (0, taper))
|
|
38
|
+
|
|
39
|
+
if nfft == 'default':
|
|
40
|
+
nfft = next_pow_2(len(data[0]))
|
|
41
|
+
elif nfft is None:
|
|
42
|
+
nfft = len(data[0])
|
|
43
|
+
|
|
44
|
+
spec = rfft(data, n=nfft, axis=1)
|
|
45
|
+
f = rfftfreq(nfft, d=1 / fs)
|
|
46
|
+
|
|
47
|
+
return spec, f
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def spectrogram(data, fs, nperseg=256, noverlap=None, nfft=None, detrend=False,
|
|
51
|
+
boundary='zeros'):
|
|
52
|
+
"""
|
|
53
|
+
Computes the spectrogram of the given data.
|
|
54
|
+
|
|
55
|
+
:param data: 1-D or 2-D numpy.ndarray. Data to make spectrogram of.
|
|
56
|
+
:param fs: Sampling rate in Hz.
|
|
57
|
+
:param nperseg: int. Length of each segment.
|
|
58
|
+
:param noverlap: int. Number of points to overlap between segments. If None,
|
|
59
|
+
noverlap = nperseg // 2.
|
|
60
|
+
:param nfft: int. Length of the FFT used. None = nperseg.
|
|
61
|
+
:param detrend : str or bool. Specifies whether and how to detrend each
|
|
62
|
+
segment. 'linear' or 'detrend' or True = detrend, 'constant' or
|
|
63
|
+
'demean' = demean.
|
|
64
|
+
:param boundary: str or None. Specifies whether the input signal is extended
|
|
65
|
+
at both ends, and how to generate the new values, in order to center the
|
|
66
|
+
first windowed segment on the first input point. This has the benefit of
|
|
67
|
+
enabling reconstruction of the first input point when the employed
|
|
68
|
+
window function starts at zero. Valid options are ['even', 'odd',
|
|
69
|
+
'constant', 'zeros', None].
|
|
70
|
+
:return: Spectrogram, frequency sequence and time sequence.
|
|
71
|
+
"""
|
|
72
|
+
if detrend in [True, 'linear', 'detrend']:
|
|
73
|
+
detrend = detrending
|
|
74
|
+
elif detrend in ['constant', 'demean']:
|
|
75
|
+
detrend = demeaning
|
|
76
|
+
if data.ndim == 1:
|
|
77
|
+
f, t, Zxx = stft(data, fs=fs, nperseg=nperseg, noverlap=noverlap,
|
|
78
|
+
nfft=nfft, detrend=detrend, boundary=boundary)
|
|
79
|
+
elif len(data) == 1:
|
|
80
|
+
f, t, Zxx = stft(data[0], fs=fs, nperseg=nperseg, noverlap=noverlap,
|
|
81
|
+
nfft=nfft, detrend=detrend, boundary=boundary)
|
|
82
|
+
else:
|
|
83
|
+
Zxx = []
|
|
84
|
+
for d in data:
|
|
85
|
+
f, t, Zxxi = stft(d, fs=fs, nperseg=nperseg, noverlap=noverlap,
|
|
86
|
+
nfft=nfft, detrend=detrend, boundary=boundary)
|
|
87
|
+
Zxx.append(abs(Zxxi))
|
|
88
|
+
Zxx = np.mean(np.array(Zxx), axis=0)
|
|
89
|
+
|
|
90
|
+
return Zxx, f, t
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def fk_transform(data, dx, fs, taper=(0, 0.05), nfft='default'):
|
|
94
|
+
"""
|
|
95
|
+
Transform the data to the fk domain using 2-D Fourier transform method.
|
|
96
|
+
|
|
97
|
+
:param data: numpy.ndarray. Data to do fk transform.
|
|
98
|
+
:param dx: Channel interval in m.
|
|
99
|
+
:param fs: Sampling rate in Hz.
|
|
100
|
+
:param taper: float or sequence of floats. Each float means decimal
|
|
101
|
+
percentage of Tukey taper for corresponding dimension (ranging from 0 to
|
|
102
|
+
1). Default is 0.1 which tapers 5% from the beginning and 5% from the
|
|
103
|
+
end.
|
|
104
|
+
:param nfft: Number of points for FFT. None means sampling points; 'default'
|
|
105
|
+
means next power of 2 of sampling points, which makes result smoother.
|
|
106
|
+
"""
|
|
107
|
+
nch, nt = data.shape
|
|
108
|
+
data = cosine_taper(data, taper)
|
|
109
|
+
if nfft == 'default':
|
|
110
|
+
nfft = (next_pow_2(nch), next_pow_2(nt))
|
|
111
|
+
elif not nfft:
|
|
112
|
+
nfft = (nch, nt)
|
|
113
|
+
|
|
114
|
+
fk = fftshift(rfft2(data, s=nfft), axes=0)
|
|
115
|
+
f = rfftfreq(nfft[1], d=1. / fs)
|
|
116
|
+
k = fftshift(fftfreq(nfft[0], d=dx))
|
|
117
|
+
return fk, f, k
|