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.
Files changed (49) hide show
  1. DASPy_toolbox-1.0.0.dist-info/LICENSE.txt +1 -0
  2. DASPy_toolbox-1.0.0.dist-info/METADATA +85 -0
  3. DASPy_toolbox-1.0.0.dist-info/RECORD +49 -0
  4. DASPy_toolbox-1.0.0.dist-info/WHEEL +5 -0
  5. DASPy_toolbox-1.0.0.dist-info/entry_points.txt +2 -0
  6. DASPy_toolbox-1.0.0.dist-info/top_level.txt +1 -0
  7. daspy/__init__.py +4 -0
  8. daspy/advanced_tools/__init__.py +0 -0
  9. daspy/advanced_tools/channel.py +354 -0
  10. daspy/advanced_tools/decomposition.py +165 -0
  11. daspy/advanced_tools/denoising.py +276 -0
  12. daspy/advanced_tools/fdct.py +789 -0
  13. daspy/advanced_tools/strain2vel.py +245 -0
  14. daspy/basic_tools/__init__.py +0 -0
  15. daspy/basic_tools/filter.py +257 -0
  16. daspy/basic_tools/freqattributes.py +117 -0
  17. daspy/basic_tools/preprocessing.py +238 -0
  18. daspy/basic_tools/visualization.py +186 -0
  19. daspy/core/__init__.py +4 -0
  20. daspy/core/collection.py +279 -0
  21. daspy/core/dasdatetime.py +72 -0
  22. daspy/core/example.pkl +0 -0
  23. daspy/core/make_example.py +32 -0
  24. daspy/core/read.py +544 -0
  25. daspy/core/section.py +1319 -0
  26. daspy/core/write.py +282 -0
  27. daspy/seismic_detection/__init__.py +1 -0
  28. daspy/seismic_detection/calc_travel_time.py +23 -0
  29. daspy/seismic_detection/core.py +119 -0
  30. daspy/seismic_detection/detection.py +12 -0
  31. daspy/seismic_detection/gamma/__init__.py +13 -0
  32. daspy/seismic_detection/gamma/_base.py +549 -0
  33. daspy/seismic_detection/gamma/_bayesian_mixture.py +875 -0
  34. daspy/seismic_detection/gamma/_gaussian_mixture.py +866 -0
  35. daspy/seismic_detection/gamma/app.py +192 -0
  36. daspy/seismic_detection/gamma/seismic_ops.py +478 -0
  37. daspy/seismic_detection/gamma/utils.py +512 -0
  38. daspy/seismic_detection/location.py +266 -0
  39. daspy/seismic_detection/magnitude.py +43 -0
  40. daspy/seismic_detection/phase_picking.py +67 -0
  41. daspy/structure_imaging/__init__.py +0 -0
  42. daspy/structure_imaging/ambient_noise.py +4 -0
  43. daspy/structure_imaging/dispersion.py +27 -0
  44. daspy/structure_imaging/fault_zone.py +59 -0
  45. daspy/structure_imaging/inversion.py +6 -0
  46. daspy/traffic_monitoring/JamDetection.py +6 -0
  47. daspy/traffic_monitoring/SpeedMeasurement.py +6 -0
  48. daspy/traffic_monitoring/VehicleDetection.py +6 -0
  49. 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