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
daspy/core/section.py
ADDED
|
@@ -0,0 +1,1319 @@
|
|
|
1
|
+
# Purpose: Module for handling Section objects.
|
|
2
|
+
# Author: Minzhe Hu
|
|
3
|
+
# Date: 2024.10.31
|
|
4
|
+
# Email: hmz2018@mail.ustc.edu.cn
|
|
5
|
+
import warnings
|
|
6
|
+
import os
|
|
7
|
+
import numpy as np
|
|
8
|
+
from copy import deepcopy
|
|
9
|
+
from typing import Iterable
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from daspy.core.dasdatetime import DASDateTime, utc
|
|
12
|
+
from daspy.core.write import write
|
|
13
|
+
from daspy.basic_tools.visualization import plot
|
|
14
|
+
from daspy.basic_tools.preprocessing import (phase2strain, normalization,
|
|
15
|
+
demeaning, detrending, stacking,
|
|
16
|
+
cosine_taper, downsampling,
|
|
17
|
+
padding, trimming,
|
|
18
|
+
time_integration,
|
|
19
|
+
time_differential,
|
|
20
|
+
distance_integration)
|
|
21
|
+
from daspy.basic_tools.filter import (bandpass, bandstop, lowpass,
|
|
22
|
+
lowpass_cheby_2, highpass, envelope)
|
|
23
|
+
from daspy.basic_tools.freqattributes import (spectrum, spectrogram,
|
|
24
|
+
fk_transform)
|
|
25
|
+
from daspy.advanced_tools.channel import channel_checking, turning_points
|
|
26
|
+
from daspy.advanced_tools.denoising import (curvelet_denoising,
|
|
27
|
+
common_mode_noise_removal,
|
|
28
|
+
spike_removal)
|
|
29
|
+
from daspy.advanced_tools.decomposition import fk_filter, curvelet_windowing
|
|
30
|
+
from daspy.advanced_tools.strain2vel import (slant_stacking, fk_rescaling,
|
|
31
|
+
curvelet_conversion)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Section(object):
|
|
35
|
+
def __init__(self, data, dx, fs, start_channel=0, start_distance=0,
|
|
36
|
+
start_time=0, **kwargs):
|
|
37
|
+
"""
|
|
38
|
+
:param data: numpy.ndarray. Data recorded by DAS.
|
|
39
|
+
:param dx: number. Channel interval in m.
|
|
40
|
+
:param fs: number. Sampling rate in Hz.
|
|
41
|
+
:param start_channel: int. Channel number of the first channel.
|
|
42
|
+
:param start_distance: number. Distance of the first channel, in m.
|
|
43
|
+
:param start_time: number or DASDateTime. Time of the first
|
|
44
|
+
sampling point. If number, the unit is s.
|
|
45
|
+
:param origin_time: number or DASDateTime. Ocurance time of the event.
|
|
46
|
+
:param gauge_length: number. Gauge length in m.
|
|
47
|
+
:param data_type: str. Can be 'phase shift', 'phase change rate',
|
|
48
|
+
'strain', 'strain rate', 'displacement', 'velocity', 'acceleration',
|
|
49
|
+
or normalized above parameters.
|
|
50
|
+
:param scale: number. Scale or gain of data.
|
|
51
|
+
:param geometry: numpy.ndarray. Should include latitude and longitude
|
|
52
|
+
(first two columns), and can also include depth (last column).
|
|
53
|
+
:param turning_channels: sequnce of channel numbers. Channel numbers of
|
|
54
|
+
turning points.
|
|
55
|
+
:param headers: dict. Other headers.
|
|
56
|
+
:param source: str or pathlib.PosixPath. Path to the source file.
|
|
57
|
+
:param source_type: str. Raw type it read from.
|
|
58
|
+
"""
|
|
59
|
+
if data.ndim == 1:
|
|
60
|
+
data = data[np.newaxis, :]
|
|
61
|
+
self.data = data
|
|
62
|
+
self.dx = dx
|
|
63
|
+
self.fs = fs
|
|
64
|
+
self.start_channel = start_channel
|
|
65
|
+
self.start_distance = start_distance
|
|
66
|
+
self.start_time = start_time
|
|
67
|
+
opt_attrs = ['origin_time', 'gauge_length', 'data_type', 'scale',
|
|
68
|
+
'geometry', 'turning_channels', 'headers', 'source',
|
|
69
|
+
'source_type']
|
|
70
|
+
for attr in opt_attrs:
|
|
71
|
+
if attr in kwargs:
|
|
72
|
+
setattr(self, attr, kwargs.pop(attr))
|
|
73
|
+
|
|
74
|
+
def __str__(self):
|
|
75
|
+
n = max(map(len, self.__dict__.keys()))
|
|
76
|
+
describe = '{}: shape{}\n'.format('data'.rjust(n), self.data.shape)
|
|
77
|
+
for key, value in self.__dict__.items():
|
|
78
|
+
if key == 'data':
|
|
79
|
+
continue
|
|
80
|
+
if key == 'geometry':
|
|
81
|
+
describe += '{}: shape{}\n'.format(key.rjust(n), value.shape)
|
|
82
|
+
elif key in ['dx', 'start_distance', 'gauge_length']:
|
|
83
|
+
describe += '{}: {} m\n'.format(key.rjust(n), value)
|
|
84
|
+
elif key == 'fs':
|
|
85
|
+
describe += '{}: {} Hz\n'.format(key.rjust(n), value)
|
|
86
|
+
elif key == 'start_time':
|
|
87
|
+
if isinstance(value, DASDateTime):
|
|
88
|
+
describe += '{}: {}\n'.format(key.rjust(n), value)
|
|
89
|
+
else:
|
|
90
|
+
describe += '{}: {} s\n'.format(key.rjust(n), value)
|
|
91
|
+
else:
|
|
92
|
+
describe += '{}: {}\n'.format(key.rjust(n), value)
|
|
93
|
+
return describe
|
|
94
|
+
|
|
95
|
+
__repr__ = __str__
|
|
96
|
+
|
|
97
|
+
def __add__(self, other):
|
|
98
|
+
"""
|
|
99
|
+
Join two sections in time.
|
|
100
|
+
"""
|
|
101
|
+
out = self.copy()
|
|
102
|
+
if isinstance(other, Section):
|
|
103
|
+
if other.dx != self.dx:
|
|
104
|
+
if self.dx is None:
|
|
105
|
+
out.dx = other.dx
|
|
106
|
+
elif other.dx is not None:
|
|
107
|
+
raise ValueError('These two Sections have different dx, '
|
|
108
|
+
'please check.')
|
|
109
|
+
if other.fs != self.fs:
|
|
110
|
+
if self.fs is None:
|
|
111
|
+
out.fs = other.fs
|
|
112
|
+
elif other.fs is not None:
|
|
113
|
+
raise ValueError('These two Sections have different fs, '
|
|
114
|
+
'please check.')
|
|
115
|
+
if isinstance(self.start_time, DASDateTime) and \
|
|
116
|
+
isinstance(other.start_time, DASDateTime):
|
|
117
|
+
if abs(other.start_time - self.end_time) > 0.1:
|
|
118
|
+
if abs(other.end_time - self.start_time) <= 0.1:
|
|
119
|
+
warnings.warn('According to the time information of the'
|
|
120
|
+
' two Sections, the order of addition is '
|
|
121
|
+
'reversed.')
|
|
122
|
+
return other + self
|
|
123
|
+
else:
|
|
124
|
+
warnings.warn('The start time of the second Section '
|
|
125
|
+
f'({other.start_time}) is inconsistent '
|
|
126
|
+
'with the end time of the first Section ('
|
|
127
|
+
f'{self.end_time}).')
|
|
128
|
+
data = other.data
|
|
129
|
+
elif isinstance(other, np.ndarray):
|
|
130
|
+
data = other
|
|
131
|
+
elif isinstance(other, list):
|
|
132
|
+
data = np.array(other)
|
|
133
|
+
else:
|
|
134
|
+
raise TypeError('The input should be Section or np.ndarray.')
|
|
135
|
+
|
|
136
|
+
if len(data) != self.nch:
|
|
137
|
+
if len(data[0]) == self.nch:
|
|
138
|
+
data = data.T
|
|
139
|
+
else:
|
|
140
|
+
raise ValueError('These two Sections have different number of '
|
|
141
|
+
'channels, please check.')
|
|
142
|
+
if out.data is None:
|
|
143
|
+
out.data = data
|
|
144
|
+
else:
|
|
145
|
+
out.data = np.hstack((out.data, data))
|
|
146
|
+
|
|
147
|
+
return out
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def shape(self):
|
|
151
|
+
return self.data.shape
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def dt(self):
|
|
155
|
+
return 1 / self.fs
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def nch(self):
|
|
159
|
+
return len(self.data)
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def nt(self):
|
|
163
|
+
return self.data.shape[1]
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def end_channel(self):
|
|
167
|
+
return self.start_channel + self.nch - 1
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def distance(self):
|
|
171
|
+
return self.nch * self.dx
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def end_distance(self):
|
|
175
|
+
return self.start_distance + self.nch * self.dx
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def duration(self):
|
|
179
|
+
return self.nt / self.fs
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def end_time(self):
|
|
183
|
+
return self.start_time + self.nt / self.fs
|
|
184
|
+
|
|
185
|
+
def copy(self):
|
|
186
|
+
return deepcopy(self)
|
|
187
|
+
|
|
188
|
+
@classmethod
|
|
189
|
+
def from_obspy_stream(cls, st):
|
|
190
|
+
"""
|
|
191
|
+
Construct a Section from a obspy.core.stream.Stream instance.
|
|
192
|
+
|
|
193
|
+
:param patch: obspy.core.stream.Stream. An instance of
|
|
194
|
+
obspy.core.stream.Stream for construction.
|
|
195
|
+
"""
|
|
196
|
+
stime = min([tr.stats['starttime'] for tr in st])
|
|
197
|
+
etime = max([tr.stats['endtime'] for tr in st])
|
|
198
|
+
st.trim(starttime=stime, endtime=etime, pad=True, fill_value=0)
|
|
199
|
+
matadata = [(tr.stats['sampling_rate'], tr.stats['delta'],
|
|
200
|
+
tr.stats['npts'], tr.stats['calib']) for tr in st]
|
|
201
|
+
assert len(set(matadata)) == 1, ('The metadata of all traces in the '
|
|
202
|
+
'stream should be the same.')
|
|
203
|
+
nch = len(st)
|
|
204
|
+
nt = st[0].stats.npts
|
|
205
|
+
fs = st[0].stats.sampling_rate
|
|
206
|
+
start_time = DASDateTime.from_datetime(st[0].stats.starttime.datetime).\
|
|
207
|
+
replace(tzinfo=utc)
|
|
208
|
+
scale = st[0].stats.calib
|
|
209
|
+
source = type(st)
|
|
210
|
+
data = np.zeros((nch, nt))
|
|
211
|
+
|
|
212
|
+
if str.isdigit(st[0].stats.channel):
|
|
213
|
+
channel_no = np.zeros(nch)
|
|
214
|
+
for i, tr in enumerate(st):
|
|
215
|
+
channel_no[i] = int(tr.stats.channel)
|
|
216
|
+
if sum(np.diff(np.sort(channel_no)) - 1) > 0:
|
|
217
|
+
channel_no = np.arange(nch)
|
|
218
|
+
else:
|
|
219
|
+
channel_no = np.arange(nch)
|
|
220
|
+
start_channel = min(channel_no)
|
|
221
|
+
channel_no -= start_channel
|
|
222
|
+
for i, tr in enumerate(st):
|
|
223
|
+
data[channel_no[i]] = tr.data
|
|
224
|
+
|
|
225
|
+
warnings.warn('obspy.core.stream.Stream doesn\'t include channel '
|
|
226
|
+
'interval. Please set dx manually.')
|
|
227
|
+
return cls(data.astype(float), None, fs, start_channel=start_channel,
|
|
228
|
+
start_time=start_time, scale=scale, source=source)
|
|
229
|
+
|
|
230
|
+
@classmethod
|
|
231
|
+
def from_dascore_patch(cls, patch):
|
|
232
|
+
"""
|
|
233
|
+
Construct a Section from a dascore.core.patch.Patch instance.
|
|
234
|
+
|
|
235
|
+
:param patch: dascore.core.patch.Patch. An instance of
|
|
236
|
+
dascore.core.patch.Patch for construction.
|
|
237
|
+
:return: daspy.Section.
|
|
238
|
+
"""
|
|
239
|
+
kwargs = {}
|
|
240
|
+
if patch.dims == ('time', 'distance'):
|
|
241
|
+
data = patch.data.T
|
|
242
|
+
dx = patch.coords.coord_map['distance'].step
|
|
243
|
+
kwargs['start_distance'] = patch.coords.coord_map['distance'].start
|
|
244
|
+
elif patch.dims == ('time', 'channel'):
|
|
245
|
+
data = patch.data.T
|
|
246
|
+
dx = 1
|
|
247
|
+
warnings.warn('This dascore.core.patch.Patch instance doesn\'t '
|
|
248
|
+
'include channel interval. Set dx to 1.')
|
|
249
|
+
kwargs['start_channel'] = patch.coords.coord_map['channel'].start
|
|
250
|
+
elif patch.dims == ('distance', 'time'):
|
|
251
|
+
data = patch.data
|
|
252
|
+
dx = patch.coords.coord_map['distance'].step
|
|
253
|
+
kwargs['start_distance'] = patch.coords.coord_map['distance'].start
|
|
254
|
+
elif patch.dim == ('channel', 'time'):
|
|
255
|
+
data = patch.data
|
|
256
|
+
dx = 1
|
|
257
|
+
warnings.warn('This dascore.core.patch.Patch instance doesn\'t '
|
|
258
|
+
'include channel interval. Set dx to 1.')
|
|
259
|
+
kwargs['start_channel'] = patch.coords.coord_map['channel'].start
|
|
260
|
+
|
|
261
|
+
if isinstance(patch.coords.coord_map['time'].step, np.timedelta64):
|
|
262
|
+
start_time = DASDateTime.fromtimestamp(
|
|
263
|
+
patch.coords.coord_map['time'].start.item() / 1e9, tz=utc)
|
|
264
|
+
fs = np.timedelta64(1, 's') / patch.coords.coord_map['time'].step
|
|
265
|
+
else:
|
|
266
|
+
start_time = patch.coords.coord_map['time'].start
|
|
267
|
+
fs = patch.coords.coord_map['time'].step
|
|
268
|
+
|
|
269
|
+
if hasattr(patch.attrs, 'gauge_length'):
|
|
270
|
+
kwargs['gauge_length'] = patch.attrs.gauge_length
|
|
271
|
+
if patch.attrs.data_type:
|
|
272
|
+
kwargs['data_type'] = ' '.join(patch.attrs.data_type.split('_'))
|
|
273
|
+
sec = cls(data, dx, fs, start_time=start_time,
|
|
274
|
+
headers=patch.attrs, source=type(patch), **kwargs)
|
|
275
|
+
return sec
|
|
276
|
+
|
|
277
|
+
@classmethod
|
|
278
|
+
def from_lightguide_blast(cls, blast):
|
|
279
|
+
"""
|
|
280
|
+
Construct a Section from a lightguide.blast.Blast instance.
|
|
281
|
+
|
|
282
|
+
:param blast: lightguide.blast.Blast. An instance of
|
|
283
|
+
lightguide.blast.Blast for construction.
|
|
284
|
+
:return: daspy.Section.
|
|
285
|
+
"""
|
|
286
|
+
sec = cls(blast.data, blast.channel_spacing, blast.sampling_rate,
|
|
287
|
+
start_time=DASDateTime.from_datetime(blast.start_time),
|
|
288
|
+
start_channel=blast.start_channel, data_type=blast.unit,
|
|
289
|
+
source=type(blast))
|
|
290
|
+
return sec
|
|
291
|
+
|
|
292
|
+
def to_obspy_stream(self):
|
|
293
|
+
"""
|
|
294
|
+
Construct an instance of obspy.core.stream.Stream.
|
|
295
|
+
|
|
296
|
+
:return: obspy.core.stream.Stream.
|
|
297
|
+
"""
|
|
298
|
+
from obspy import Stream, Trace, UTCDateTime
|
|
299
|
+
st = Stream()
|
|
300
|
+
header = {'sampling_rate': self.fs}
|
|
301
|
+
if not isinstance(datetime, self.start_time):
|
|
302
|
+
warnings.warn('The type of start_time is not DASDateTime. The '
|
|
303
|
+
'starttime of Trace instances may be wrong')
|
|
304
|
+
header['starttime'] = UTCDateTime(self.start_time)
|
|
305
|
+
else:
|
|
306
|
+
header['starttime'] = UTCDateTime(self.start_time.timestamp())
|
|
307
|
+
if hasattr(self, 'scale'):
|
|
308
|
+
header['calib'] = self.scale
|
|
309
|
+
for i in range(self.nch):
|
|
310
|
+
header_tr = deepcopy(header)
|
|
311
|
+
header_tr['channel'] = str(self.start_channel + i)
|
|
312
|
+
tr = Trace(self.data[i], header_tr)
|
|
313
|
+
st += tr
|
|
314
|
+
|
|
315
|
+
warnings.warn('obspy.core.stream.Stream doesn\'t include channel '
|
|
316
|
+
'interval.')
|
|
317
|
+
return st
|
|
318
|
+
|
|
319
|
+
def to_dascore_patch(self):
|
|
320
|
+
"""
|
|
321
|
+
Construct an instance of dascore.core.patch.Patch.
|
|
322
|
+
|
|
323
|
+
:return: dascore.core.patch.Patch.
|
|
324
|
+
"""
|
|
325
|
+
from pint import Quantity
|
|
326
|
+
from datetime import datetime
|
|
327
|
+
from dascore.core import Patch, CoordManager
|
|
328
|
+
from dascore.core.coords import CoordRange
|
|
329
|
+
from dascore.utils.mapping import FrozenDict
|
|
330
|
+
|
|
331
|
+
dims = ('time', 'distance')
|
|
332
|
+
if isinstance(self.start_time, datetime):
|
|
333
|
+
if self.start_time.tzinfo:
|
|
334
|
+
stime = np.datetime64(self.start_time.astimezone(utc).
|
|
335
|
+
replace(tzinfo=None))
|
|
336
|
+
etime = np.datetime64(self.end_time.astimezone(utc).
|
|
337
|
+
replace(tzinfo=None))
|
|
338
|
+
else:
|
|
339
|
+
stime = np.datetime64(self.start_time.replace(tzinfo=None))
|
|
340
|
+
etime = np.datetime64(self.end_time.replace(tzinfo=None))
|
|
341
|
+
|
|
342
|
+
time_range = CoordRange(units=(Quantity(1, 'second')),
|
|
343
|
+
step=np.timedelta64(int(self.dt * 1e9),
|
|
344
|
+
'ns'),
|
|
345
|
+
start=stime, stop=etime)
|
|
346
|
+
else:
|
|
347
|
+
stime = self.start_time
|
|
348
|
+
etime = self.end_time
|
|
349
|
+
time_range = CoordRange(units=(Quantity(1, 'second')), step=self.dt,
|
|
350
|
+
start=stime, stop=etime)
|
|
351
|
+
|
|
352
|
+
dist_range = CoordRange(units=(Quantity(1, 'meter')), step=self.dx,
|
|
353
|
+
start=self.start_distance,
|
|
354
|
+
stop=self.end_distance)
|
|
355
|
+
coord_map = FrozenDict({'time': time_range, 'distance': dist_range})
|
|
356
|
+
dim_map = FrozenDict({'time': ('time',), 'distance': ('distance', )})
|
|
357
|
+
coords = CoordManager(dims=dims, coord_map=coord_map,
|
|
358
|
+
dim_map=dim_map)
|
|
359
|
+
|
|
360
|
+
if self.source == Patch:
|
|
361
|
+
attrs = self.headers
|
|
362
|
+
else:
|
|
363
|
+
from dascore.io.prodml.core import ProdMLPatchAttrs
|
|
364
|
+
attrs = ProdMLPatchAttrs(coords=coords)
|
|
365
|
+
kwargs = {}
|
|
366
|
+
if hasattr(self, 'data_type'):
|
|
367
|
+
kwargs['data_type'] = '_'.join(self.data_type.split(' '))
|
|
368
|
+
if hasattr(self, 'gauge_length'):
|
|
369
|
+
kwargs['gauge_length'] = self.gauge_length
|
|
370
|
+
kwargs['gauge_length_units'] = Quantity(1, 'meter')
|
|
371
|
+
|
|
372
|
+
return Patch(self.data.T, coords, dims, attrs)
|
|
373
|
+
|
|
374
|
+
def to_lightguide_blast(self):
|
|
375
|
+
"""
|
|
376
|
+
Construct an instance of lightguide.blast.Blast
|
|
377
|
+
|
|
378
|
+
:return: lightguide.blast.Blast.
|
|
379
|
+
"""
|
|
380
|
+
from lightguide.blast import Blast
|
|
381
|
+
if not isinstance(self.start_time, datetime):
|
|
382
|
+
warnings.warn('The type of start_time is not DASDateTime. The '
|
|
383
|
+
'starttime of Trace instances may be wrong')
|
|
384
|
+
start_time = datetime.fromtimestamp(self.start_time)
|
|
385
|
+
elif isinstance(self.start_time, DASDateTime):
|
|
386
|
+
start_time = self.start_time.to_datetime()
|
|
387
|
+
if start_time.tzinfo is None:
|
|
388
|
+
start_time = start_time.astimezone(utc)
|
|
389
|
+
if hasattr(self, 'data_type'):
|
|
390
|
+
for key in ['strain rate', 'strain', 'displacement', 'velocity',
|
|
391
|
+
'acceleration']:
|
|
392
|
+
if key in self.data_type.lower():
|
|
393
|
+
if self.data_type.lower() == key:
|
|
394
|
+
unit = key
|
|
395
|
+
else:
|
|
396
|
+
warnings.warn(f'Data type {self.data_type} is not '
|
|
397
|
+
f'supported in lightguide. Set to {key}.')
|
|
398
|
+
break
|
|
399
|
+
else:
|
|
400
|
+
warnings.warn(f'Data type {unit} is not supported in '
|
|
401
|
+
'lightguide. Set to default (starin rate).')
|
|
402
|
+
unit = 'strain rate'
|
|
403
|
+
else:
|
|
404
|
+
print('Set unit to default (starin rate).')
|
|
405
|
+
unit = 'strain rate'
|
|
406
|
+
return Blast(self.data, start_time, self.fs,
|
|
407
|
+
start_channel=self.start_channel, channel_spacing=self.dx,
|
|
408
|
+
unit=unit)
|
|
409
|
+
|
|
410
|
+
def save(self, fname=None, ftype=None, keep_format=False):
|
|
411
|
+
"""
|
|
412
|
+
Save the instance as a pickle file or update the raw file and resave as
|
|
413
|
+
new file.
|
|
414
|
+
|
|
415
|
+
:param fname: str or pathlib.PosixPath. Path of new DAS data file to
|
|
416
|
+
save.
|
|
417
|
+
:param ftype: None or str. None for automatic detection), or 'pkl',
|
|
418
|
+
'pickle', 'tdms', 'h5', 'hdf5', 'segy', 'sgy', 'npy'.
|
|
419
|
+
:param keep_format: bool. If True, we will make a copy of the
|
|
420
|
+
self.source file and make changes to it. This will strictly preserve
|
|
421
|
+
the original format, but will cost more IO resources.
|
|
422
|
+
"""
|
|
423
|
+
if fname is None:
|
|
424
|
+
if hasattr(self, 'source'):
|
|
425
|
+
fname_list = self.source.split('.')
|
|
426
|
+
fname_list[-2] += '_new'
|
|
427
|
+
fname = '.'.join(fname_list)
|
|
428
|
+
else:
|
|
429
|
+
fname = 'section.pkl'
|
|
430
|
+
|
|
431
|
+
if ftype is None:
|
|
432
|
+
ftype = str(fname).lower().split('.')[-1]
|
|
433
|
+
|
|
434
|
+
for rtp in [('pickle', 'pkl'), ('hdf5', 'h5'), ('segy', 'sgy')]:
|
|
435
|
+
ftype = ftype.replace(*rtp)
|
|
436
|
+
|
|
437
|
+
if keep_format:
|
|
438
|
+
if not hasattr(self, 'source'):
|
|
439
|
+
raise ValueError('self.source not exit.')
|
|
440
|
+
if not os.path.isfile(self.source):
|
|
441
|
+
raise ValueError('self.source is not a file.')
|
|
442
|
+
if ftype != self.source_type:
|
|
443
|
+
raise ValueError('self.source_type is different from ftype.')
|
|
444
|
+
write(self, fname, ftype=ftype, raw_fname=self.source)
|
|
445
|
+
else:
|
|
446
|
+
write(self, fname, ftype=ftype)
|
|
447
|
+
|
|
448
|
+
return self
|
|
449
|
+
|
|
450
|
+
def channel_data(self, use_channel, replace=False):
|
|
451
|
+
"""
|
|
452
|
+
Extract data of one channel or several channels.
|
|
453
|
+
"""
|
|
454
|
+
channel = deepcopy(use_channel)
|
|
455
|
+
channel -= self.start_channel
|
|
456
|
+
data = self.data[channel]
|
|
457
|
+
if replace:
|
|
458
|
+
self.data = data
|
|
459
|
+
self.start_channel += channel[0]
|
|
460
|
+
self.start_distance += channel[0] * self.dx
|
|
461
|
+
return self
|
|
462
|
+
else:
|
|
463
|
+
return data
|
|
464
|
+
|
|
465
|
+
def plot(self, xmode='distance', tmode='origin', obj='waveform',
|
|
466
|
+
kwargs_pro={}, **kwargs):
|
|
467
|
+
"""
|
|
468
|
+
Plot several types of 2-D seismological data.
|
|
469
|
+
|
|
470
|
+
:param xmode: str. 'distance' or 'channel'.
|
|
471
|
+
:param tmode: str. 'origin', 'start', 'time' or 'sampling'. If
|
|
472
|
+
origin_time is not defined, 'origin' and 'start' is the same.
|
|
473
|
+
:param obj: str. Type of data to plot. It should be one of 'waveform',
|
|
474
|
+
'phasepick', 'spectrum', 'spectrogram', 'fk', or 'dispersion'.
|
|
475
|
+
:param kwargs_pro: dict. If obj is one of 'spectrum', 'spectrogram',
|
|
476
|
+
'fk' and data is not specified, this parameter will be used to
|
|
477
|
+
process the data to plot.
|
|
478
|
+
:param ax: Matplotlib.axes.Axes or tuple. Axes to plot. A tuple for new
|
|
479
|
+
figsize. If not specified, the function will directly display the
|
|
480
|
+
image using matplotlib.pyplot.show().
|
|
481
|
+
:param dpi: int. The resolution of the figure in dots-per-inch.
|
|
482
|
+
:param title: str. The title of this axes.
|
|
483
|
+
:param transpose: bool. Transpose the figure or not.
|
|
484
|
+
:param cmap: str or Colormap. The Colormap instance or registered
|
|
485
|
+
colormap name used to map scalar data to colors.
|
|
486
|
+
:param vmin, vmax: Define the data range that the colormap covers.
|
|
487
|
+
:param xlim, ylim: Set the x-axis and y-axis view limits.
|
|
488
|
+
:param xlog, ylog: bool. If True, set the x-axis' or y-axis' scale as
|
|
489
|
+
log.
|
|
490
|
+
:param xinv, yinv: bool. If True, invert x-axis or y-axis.
|
|
491
|
+
:param xaxis, yaxis: bool. Show ticks and labels for x-axis or y-axis.
|
|
492
|
+
:param colorbar: bool, str or Matplotlib.axes.Axes. Bool means plot
|
|
493
|
+
colorbar or not. Str means the location of colorbar. Axes means the
|
|
494
|
+
Axes into which the colorbar will be drawn.
|
|
495
|
+
:param t0, x0: The beginning of time and space. Use instance's
|
|
496
|
+
properties by default
|
|
497
|
+
:param pick: Sequence of picked phases. Required if obj=='phasepick'.
|
|
498
|
+
:param c: Phase velocity sequence. Required if obj=='dispersion'.
|
|
499
|
+
:param data: numpy.ndarray. Data to plot. Required if obj is not
|
|
500
|
+
'spectrum', 'spectrogram' and 'fk'.
|
|
501
|
+
:param f: Frequency sequence. Required if obj is one of 'spectrum',
|
|
502
|
+
'spectrogram', 'fk' and data is specified, or obj is 'dispersion'.
|
|
503
|
+
:param k: Wavenumber sequence. Required if obj=='fk' and data is
|
|
504
|
+
specified.
|
|
505
|
+
:param t: Time sequence. Required if obj=='spectrogram' and data is
|
|
506
|
+
specified.
|
|
507
|
+
:param savefig: str or bool. Figure name to save if needed. If True,
|
|
508
|
+
it will be set to parameter obj.
|
|
509
|
+
"""
|
|
510
|
+
if 'data' not in kwargs.keys():
|
|
511
|
+
if obj == 'waveform':
|
|
512
|
+
data = deepcopy(self.data)
|
|
513
|
+
elif obj == 'spectrum':
|
|
514
|
+
data, f = self.spectrum(**kwargs_pro)
|
|
515
|
+
kwargs['f'] = f
|
|
516
|
+
elif obj == 'spectrogram':
|
|
517
|
+
data, f, t = self.spectrogram(**kwargs_pro)
|
|
518
|
+
kwargs['f'] = f
|
|
519
|
+
kwargs['t'] = t
|
|
520
|
+
elif obj == 'fk':
|
|
521
|
+
data, f, k = self.fk_transform(**kwargs_pro)
|
|
522
|
+
kwargs['f'] = f
|
|
523
|
+
kwargs['k'] = k
|
|
524
|
+
if hasattr(self, 'scale'):
|
|
525
|
+
data *= self.scale
|
|
526
|
+
else:
|
|
527
|
+
data = kwargs.pop('data')
|
|
528
|
+
|
|
529
|
+
if 'ax' not in kwargs.keys() and 'title' not in kwargs.keys():
|
|
530
|
+
kwargs['title'] = obj
|
|
531
|
+
if hasattr(self, 'data_type'):
|
|
532
|
+
kwargs['title'] += f' ({self.data_type})'
|
|
533
|
+
|
|
534
|
+
if xmode == 'channel':
|
|
535
|
+
kwargs.setdefault('x0', self.start_channel)
|
|
536
|
+
elif xmode == 'distance':
|
|
537
|
+
kwargs.setdefault('x0', self.start_distance)
|
|
538
|
+
if tmode in ['origin', 'start', 'time']:
|
|
539
|
+
kwargs.setdefault('t0', self.start_time)
|
|
540
|
+
if tmode == 'origin':
|
|
541
|
+
if hasattr(self, 'origin_time'):
|
|
542
|
+
kwargs['t0'] -= self.origin_time
|
|
543
|
+
else:
|
|
544
|
+
tmode == 'start'
|
|
545
|
+
if tmode == 'start':
|
|
546
|
+
kwargs['t0'] -= self.start_time
|
|
547
|
+
tmode = 'time'
|
|
548
|
+
if hasattr(self, 'datatype'):
|
|
549
|
+
kwargs.setdefault('colorbar_label', self.datatype)
|
|
550
|
+
|
|
551
|
+
plot(data, self.dx, self.fs, obj=obj, xmode=xmode, tmode=tmode,
|
|
552
|
+
**kwargs)
|
|
553
|
+
|
|
554
|
+
def rescaling(self, scale=None):
|
|
555
|
+
"""
|
|
556
|
+
Scale data according to a scale factor.
|
|
557
|
+
|
|
558
|
+
:param scale: float. It is required if the Section instance does
|
|
559
|
+
not specify the attribute 'scale'.
|
|
560
|
+
"""
|
|
561
|
+
if scale is None:
|
|
562
|
+
try:
|
|
563
|
+
self.data *= self['scale']
|
|
564
|
+
except ValueError:
|
|
565
|
+
print('Please specify a scale factor.')
|
|
566
|
+
else:
|
|
567
|
+
if hasattr(self, 'scale') and self.scale != scale:
|
|
568
|
+
warnings.warn('The set scale is different from the previous '
|
|
569
|
+
'self.scale.')
|
|
570
|
+
self.data *= scale
|
|
571
|
+
self.scale = 1
|
|
572
|
+
return self
|
|
573
|
+
|
|
574
|
+
def phase2strain(self, lam, e, n, gl=None):
|
|
575
|
+
"""
|
|
576
|
+
Convert the optical phase shift in radians to strain, or phase change
|
|
577
|
+
rate to strain rate.
|
|
578
|
+
|
|
579
|
+
:param lam: float. Operational optical wavelength in vacuum.
|
|
580
|
+
:param e: float. photo-slastic scaling factor for logitudinal strain in
|
|
581
|
+
isotropic material.
|
|
582
|
+
:param n: float. Refractive index of the sensing fiber.
|
|
583
|
+
:paran gl: float. Gauge length. Required if self.gauge_length has not
|
|
584
|
+
been set.
|
|
585
|
+
"""
|
|
586
|
+
if gl:
|
|
587
|
+
self.gauge_length = gl
|
|
588
|
+
self.data = phase2strain(self.data, lam, e, n, self.gauge_length)
|
|
589
|
+
if hasattr(self, 'data_type'):
|
|
590
|
+
if 'phase' not in self.data_type:
|
|
591
|
+
warnings.warn('The data type is {}, not phase shift. But it' +
|
|
592
|
+
'still takes effect.'.format(self.data_type))
|
|
593
|
+
else:
|
|
594
|
+
self.data_type = self.data_type.replace(
|
|
595
|
+
'phase shift', 'strain')
|
|
596
|
+
self.data_type = self.data_type.replace('phase change rate',
|
|
597
|
+
'strain rate')
|
|
598
|
+
return self
|
|
599
|
+
|
|
600
|
+
def normalization(self, method='z-score'):
|
|
601
|
+
"""
|
|
602
|
+
Normalize for each individual channel using Z-score method.
|
|
603
|
+
|
|
604
|
+
:param method: str. Method for normalization, should be one of 'max' or
|
|
605
|
+
'z-score'.
|
|
606
|
+
"""
|
|
607
|
+
self.data = normalization(self.data, method=method)
|
|
608
|
+
if hasattr(self, 'data_type'):
|
|
609
|
+
self.data_type = 'normed ' + self.data_type
|
|
610
|
+
return self
|
|
611
|
+
|
|
612
|
+
def demeaning(self):
|
|
613
|
+
"""
|
|
614
|
+
Demean signal by subtracted mean of each channel.
|
|
615
|
+
"""
|
|
616
|
+
self.data = demeaning(self.data)
|
|
617
|
+
return self
|
|
618
|
+
|
|
619
|
+
def detrending(self):
|
|
620
|
+
"""
|
|
621
|
+
Detrend signal by subtracted a linear least-squares fit to data.
|
|
622
|
+
"""
|
|
623
|
+
self.data = detrending(self.data)
|
|
624
|
+
return self
|
|
625
|
+
|
|
626
|
+
def stacking(self, N, step=None):
|
|
627
|
+
"""
|
|
628
|
+
Stack several channels to increase the signal-noise ratio(SNR).
|
|
629
|
+
|
|
630
|
+
:param N: int. N adjacent channels stacked into 1.
|
|
631
|
+
:param step: int. Interval of data stacking.
|
|
632
|
+
"""
|
|
633
|
+
if step is None:
|
|
634
|
+
step = N
|
|
635
|
+
self.data = stacking(self.data, N, step=step)
|
|
636
|
+
self.dx *= step
|
|
637
|
+
if hasattr(self, 'gauge_length'):
|
|
638
|
+
self.gauge_length += self.dx * (N - 1)
|
|
639
|
+
return self
|
|
640
|
+
|
|
641
|
+
def cosine_taper(self, p=0.1):
|
|
642
|
+
"""
|
|
643
|
+
Taper using Tukey window.
|
|
644
|
+
|
|
645
|
+
:param p: float or sequence of floats. Each float means decimal
|
|
646
|
+
percentage of Tukey taper for corresponding dimension (ranging from
|
|
647
|
+
0 to 1). Default is 0.1 which tapers 5% from the beginning and 5%
|
|
648
|
+
from the end. If only one float is given, it only do for time
|
|
649
|
+
dimension.
|
|
650
|
+
"""
|
|
651
|
+
self.data = cosine_taper(self.data, p=p)
|
|
652
|
+
return self
|
|
653
|
+
|
|
654
|
+
def downsampling(self, xint=None, tint=None, stack=True,
|
|
655
|
+
lowpass_filter=True):
|
|
656
|
+
"""
|
|
657
|
+
Downsample DAS data.
|
|
658
|
+
|
|
659
|
+
:param xint: int. Spatial downsampling factor.
|
|
660
|
+
:param tint: int. Time downsampling factor.
|
|
661
|
+
:param stack: bool. If True, stacking will replace decimation.
|
|
662
|
+
:param lowpass_filter: bool. Lowpass cheby2 filter before time
|
|
663
|
+
downsampling or not.
|
|
664
|
+
:return: Downsampled data.
|
|
665
|
+
"""
|
|
666
|
+
self.data = downsampling(self.data, xint=xint, tint=tint, stack=stack,
|
|
667
|
+
lowpass_filter=lowpass_filter)
|
|
668
|
+
if xint and xint > 1:
|
|
669
|
+
self.dx *= xint
|
|
670
|
+
if hasattr(self, 'gauge_length'):
|
|
671
|
+
self.gauge_length += self.dx * (xint - 1)
|
|
672
|
+
if tint and tint > 1:
|
|
673
|
+
self.fs /= tint
|
|
674
|
+
return self
|
|
675
|
+
|
|
676
|
+
def trimming(self, mode=1, xmin=None, xmax=None, tmin=None, tmax=None):
|
|
677
|
+
"""
|
|
678
|
+
Cut data to given start and end distance/channel or time/sampling
|
|
679
|
+
points.
|
|
680
|
+
|
|
681
|
+
:param mode: int. 0 means the unit of boundary is channel number and
|
|
682
|
+
sampling points; 1 means the unit of boundary is meters and seconds.
|
|
683
|
+
:param xmin, xmax: int or float. Boundary of channel number (mode=0)
|
|
684
|
+
or boundary of distance (mode=1).
|
|
685
|
+
in meters.
|
|
686
|
+
:param tmin, tmax: int, float or DASDateTime. Boundary of sampling
|
|
687
|
+
points (mode=0) or boundary of time (mode=1). When mode=1,
|
|
688
|
+
start_time is of type DASDateTime and tmin/tmax are floats,
|
|
689
|
+
tmin/tmax means the time relative to start_time.
|
|
690
|
+
"""
|
|
691
|
+
if mode == 1:
|
|
692
|
+
if tmin is not None:
|
|
693
|
+
try:
|
|
694
|
+
tmin = round((tmin - self.start_time) * self.fs)
|
|
695
|
+
except TypeError:
|
|
696
|
+
tmin = round(tmin * self.fs)
|
|
697
|
+
if tmin < 0:
|
|
698
|
+
warnings.warn('tmin is earlier than start_time. Set tmin '
|
|
699
|
+
'to start_time.')
|
|
700
|
+
tmin = 0
|
|
701
|
+
elif tmin >= self.nt:
|
|
702
|
+
raise ValueError('tmin is later than end_time.')
|
|
703
|
+
else:
|
|
704
|
+
tmin = 0
|
|
705
|
+
|
|
706
|
+
if tmax is not None:
|
|
707
|
+
try:
|
|
708
|
+
tmax = round((tmax - self.start_time) * self.fs)
|
|
709
|
+
except TypeError:
|
|
710
|
+
tmax = round(tmax * self.fs)
|
|
711
|
+
if tmax <= 0:
|
|
712
|
+
raise ValueError('tmax is earlier than start_time.')
|
|
713
|
+
if tmax > self.nt:
|
|
714
|
+
warnings.warn('tmax is later than end_time. Set tmax to the'
|
|
715
|
+
' end_time.')
|
|
716
|
+
tmax = self.nt
|
|
717
|
+
|
|
718
|
+
if xmin is not None:
|
|
719
|
+
xmin = round((xmin - self.start_distance) / self.dx)
|
|
720
|
+
if xmin < 0:
|
|
721
|
+
warnings.warn('xmin is smaller than start_distance. Set '
|
|
722
|
+
'xmin to 0.')
|
|
723
|
+
xmin = 0
|
|
724
|
+
elif xmin >= self.nch:
|
|
725
|
+
raise ValueError('xmin is later than end_distance.')
|
|
726
|
+
else:
|
|
727
|
+
xmin = 0
|
|
728
|
+
|
|
729
|
+
if xmax is not None:
|
|
730
|
+
xmax = round((xmax - self.start_distance) / self.dx)
|
|
731
|
+
if xmax <= 0:
|
|
732
|
+
raise ValueError('xmax is smaller than start_distance.')
|
|
733
|
+
if xmax > self.nch:
|
|
734
|
+
warnings.warn('xmax is later than end_distance. Set xmax '
|
|
735
|
+
'to the array length.')
|
|
736
|
+
xmax = self.nch
|
|
737
|
+
|
|
738
|
+
elif mode == 0:
|
|
739
|
+
if tmin is None:
|
|
740
|
+
tmin = 0
|
|
741
|
+
else:
|
|
742
|
+
tmin = int(tmin)
|
|
743
|
+
if xmin is None:
|
|
744
|
+
xmin = 0
|
|
745
|
+
else:
|
|
746
|
+
xmin = int(xmin - self.start_channel)
|
|
747
|
+
if xmax is not None:
|
|
748
|
+
xmax = int(xmax - self.start_channel)
|
|
749
|
+
|
|
750
|
+
self.data = trimming(self.data, dx=self.dx, fs=self.fs, xmin=xmin,
|
|
751
|
+
xmax=xmax, tmin=tmin, tmax=tmax)
|
|
752
|
+
|
|
753
|
+
self.start_time += tmin / self.fs
|
|
754
|
+
self.start_distance += xmin * self.dx
|
|
755
|
+
self.start_channel += xmin
|
|
756
|
+
|
|
757
|
+
return self
|
|
758
|
+
|
|
759
|
+
def padding(self, dn, reverse=False):
|
|
760
|
+
"""
|
|
761
|
+
Pad DAS data with 0.
|
|
762
|
+
|
|
763
|
+
:param dn: int or sequence of ints. Number of points to pad for both
|
|
764
|
+
dimensions.
|
|
765
|
+
:param reverse: bool. Set True to reverse the operation.
|
|
766
|
+
"""
|
|
767
|
+
self.data = padding(self.data, dn, reverse=reverse)
|
|
768
|
+
return self
|
|
769
|
+
|
|
770
|
+
def _time_int_dif_attr(self, mode=0):
|
|
771
|
+
for type_group in [['phase change rate', 'phase shift'],
|
|
772
|
+
['strain rate', 'strain'],
|
|
773
|
+
['acceleration', 'velocity', 'displacement']]:
|
|
774
|
+
for (i, tp) in enumerate(type_group):
|
|
775
|
+
if tp in self.data_type:
|
|
776
|
+
try:
|
|
777
|
+
self.data_type = self.data_type.replace(
|
|
778
|
+
tp, type_group[i + mode])
|
|
779
|
+
except BaseException:
|
|
780
|
+
operate = ('differentiate', 'integrate')[mode > 0]
|
|
781
|
+
print(f'Data type conversion error. Can not {operate} '
|
|
782
|
+
f'{self.data_type} data.')
|
|
783
|
+
return self
|
|
784
|
+
warnings.warn('Unable to convert data type.')
|
|
785
|
+
|
|
786
|
+
def time_integration(self, c=0):
|
|
787
|
+
"""
|
|
788
|
+
Integrate DAS data in time.
|
|
789
|
+
|
|
790
|
+
:param c: float. A constant added to the result.
|
|
791
|
+
"""
|
|
792
|
+
self.data = time_integration(self.data, self.fs, c=c)
|
|
793
|
+
if hasattr(self, 'data_type'):
|
|
794
|
+
self._time_int_dif_attr(mode=1)
|
|
795
|
+
return self
|
|
796
|
+
|
|
797
|
+
def time_differential(self, prepend=0):
|
|
798
|
+
"""
|
|
799
|
+
Differentiate DAS data in time.
|
|
800
|
+
|
|
801
|
+
:param prepend: 'mean' or values to prepend to `data` along axis prior to
|
|
802
|
+
performing the difference.
|
|
803
|
+
"""
|
|
804
|
+
self.data = time_differential(self.data, self.fs, prepend=prepend)
|
|
805
|
+
if hasattr(self, 'data_type'):
|
|
806
|
+
self._time_int_dif_attr(mode=-1)
|
|
807
|
+
return self
|
|
808
|
+
|
|
809
|
+
def distance_integration(self, c=0):
|
|
810
|
+
"""
|
|
811
|
+
Differentiate DAS data in distance.
|
|
812
|
+
|
|
813
|
+
:param c: float. A constant added to the result.
|
|
814
|
+
"""
|
|
815
|
+
self.data = distance_integration(self.data, self.dx, c=c)
|
|
816
|
+
self._strain2vel_attr()
|
|
817
|
+
return self
|
|
818
|
+
|
|
819
|
+
def bandpass(self, freqmin, freqmax, zi=None, **kwargs):
|
|
820
|
+
"""
|
|
821
|
+
Filter data from 'freqmin' to 'freqmax' using Butterworth bandpass
|
|
822
|
+
filter of 'corners' corners.
|
|
823
|
+
|
|
824
|
+
:param freqmin: Pass band low corner frequency.
|
|
825
|
+
:param freqmax: Pass band high corner frequency.
|
|
826
|
+
:param zi : None, 0, or array_like. Initial conditions for the cascaded
|
|
827
|
+
filter delays. It is a vector of shape (n_sections, nch, 2). Set to
|
|
828
|
+
0 to trigger a output of the final filter delay values.
|
|
829
|
+
:param corners: Filter corners / order.
|
|
830
|
+
:param zerophase: If True, apply filter once forwards and once
|
|
831
|
+
backwards. This results in twice the number of corners but zero
|
|
832
|
+
phase shift in the resulting filtered data. Only valid when zi is
|
|
833
|
+
None.
|
|
834
|
+
"""
|
|
835
|
+
if zi is None:
|
|
836
|
+
self.data = bandpass(self.data, self.fs, freqmin, freqmax, **kwargs)
|
|
837
|
+
return self
|
|
838
|
+
else:
|
|
839
|
+
self.data, zf = bandpass(self.data, self.fs, freqmin, freqmax,
|
|
840
|
+
zi=zi, **kwargs)
|
|
841
|
+
return zf
|
|
842
|
+
|
|
843
|
+
def bandstop(self, freqmin, freqmax, zi=None, **kwargs):
|
|
844
|
+
"""
|
|
845
|
+
Filter data removing data between frequencies 'freqmin' and 'freqmax'
|
|
846
|
+
using Butterworth bandstop filter of 'corners' corners.
|
|
847
|
+
|
|
848
|
+
:param freqmin: Stop band low corner frequency.
|
|
849
|
+
:param freqmax: Stop band high corner frequency.
|
|
850
|
+
:param zi : None, 0, or array_like. Initial conditions for the cascaded
|
|
851
|
+
filter delays. It is a vector of shape (n_sections, nch, 2). Set to
|
|
852
|
+
0 to trigger a output of the final filter delay values.
|
|
853
|
+
:param corners: Filter corners / order.
|
|
854
|
+
:param zerophase: If True, apply filter once forwards and once
|
|
855
|
+
backwards. This results in twice the number of corners but zero
|
|
856
|
+
phase shift in the resulting filtered data. Only valid when zi is
|
|
857
|
+
None.
|
|
858
|
+
"""
|
|
859
|
+
|
|
860
|
+
if zi is None:
|
|
861
|
+
self.data = bandstop(self.data, self.fs, freqmin, freqmax, **kwargs)
|
|
862
|
+
return self
|
|
863
|
+
else:
|
|
864
|
+
self.data, zf = bandstop(self.data, self.fs, freqmin, freqmax,
|
|
865
|
+
zi=zi, **kwargs)
|
|
866
|
+
return zf
|
|
867
|
+
|
|
868
|
+
def lowpass(self, freq, zi=None, **kwargs):
|
|
869
|
+
"""
|
|
870
|
+
Filter data removing data over certain frequency 'freq' using
|
|
871
|
+
Butterworth lowpass filter of 'corners' corners.
|
|
872
|
+
|
|
873
|
+
:param freq: Filter corner frequency.
|
|
874
|
+
:param zi : None, 0, or array_like. Initial conditions for the cascaded
|
|
875
|
+
filter delays. It is a vector of shape (n_sections, nch, 2). Set to
|
|
876
|
+
0 to trigger a output of the final filter delay values.
|
|
877
|
+
:param corners: Filter corners / order.
|
|
878
|
+
:param zerophase: If True, apply filter once forwards and once
|
|
879
|
+
backwards. This results in twice the number of corners but zero
|
|
880
|
+
phase shift in the resulting filtered data. Only valid when zi is
|
|
881
|
+
None.
|
|
882
|
+
"""
|
|
883
|
+
if zi is None:
|
|
884
|
+
self.data = lowpass(self.data, self.fs, freq, **kwargs)
|
|
885
|
+
return self
|
|
886
|
+
else:
|
|
887
|
+
self.data, zf = lowpass(self.data, self.fs, freq, zi=zi, **kwargs)
|
|
888
|
+
return zf
|
|
889
|
+
|
|
890
|
+
def lowpass_cheby_2(self, freq, **kwargs):
|
|
891
|
+
"""
|
|
892
|
+
Filter data by passing data only below a certain frequency. The main
|
|
893
|
+
purpose of this cheby2 filter is downsampling. This method will
|
|
894
|
+
iteratively design a filter, whose pass band frequency is determined
|
|
895
|
+
dynamically, such that the values above the stop band frequency are
|
|
896
|
+
lower than -96dB.
|
|
897
|
+
|
|
898
|
+
:param freq: The frequency above which signals are attenuated with 95
|
|
899
|
+
dB.
|
|
900
|
+
:param maxorder: Maximal order of the designed cheby2 filter.
|
|
901
|
+
:param zi : None, 0, or array_like. Initial conditions for the cascaded
|
|
902
|
+
filter delays. It is a vector of shape (n_sections, nch, 2). Set to 0 to
|
|
903
|
+
trigger a output of the final filter delay values.
|
|
904
|
+
:param ba: If True return only the filter coefficients (b, a) instead of
|
|
905
|
+
filtering.
|
|
906
|
+
:param freq_passband: If True return additionally to the filtered data,
|
|
907
|
+
the iteratively determined pass band frequency.
|
|
908
|
+
"""
|
|
909
|
+
output = lowpass_cheby_2(self.data, self.fs, freq, **kwargs)
|
|
910
|
+
if isinstance(output, tuple):
|
|
911
|
+
self.data = output[0]
|
|
912
|
+
if len(output) == 2:
|
|
913
|
+
return output[1]
|
|
914
|
+
else:
|
|
915
|
+
return output[1:]
|
|
916
|
+
else:
|
|
917
|
+
if kwargs.pop('ba', False):
|
|
918
|
+
self.data = output
|
|
919
|
+
return self
|
|
920
|
+
else:
|
|
921
|
+
return output
|
|
922
|
+
|
|
923
|
+
def highpass(self, freq, zi=None, **kwargs):
|
|
924
|
+
"""
|
|
925
|
+
Filter data removing data below certain frequency 'freq' using
|
|
926
|
+
Butterworth highpass filter of 'corners' corners.
|
|
927
|
+
|
|
928
|
+
:param freq: Filter corner frequency.
|
|
929
|
+
:param zi : None, 0, or array_like. Initial conditions for the cascaded
|
|
930
|
+
filter delays. It is a vector of shape (n_sections, nch, 2). Set to
|
|
931
|
+
0 to trigger a output of the final filter delay values.
|
|
932
|
+
:param corners: Filter corners / order.
|
|
933
|
+
:param zerophase: If True, apply filter once forwards and once
|
|
934
|
+
backwards. This results in twice the number of corners but zero
|
|
935
|
+
phase shift in the resulting filtered data. Only valid when zi is
|
|
936
|
+
None.
|
|
937
|
+
"""
|
|
938
|
+
if zi is None:
|
|
939
|
+
self.data = highpass(self.data, self.fs, freq, **kwargs)
|
|
940
|
+
return self
|
|
941
|
+
else:
|
|
942
|
+
self.data, zf = highpass(self.data, self.fs, freq, zi=zi, **kwargs)
|
|
943
|
+
return zf
|
|
944
|
+
|
|
945
|
+
def envelope(self):
|
|
946
|
+
"""
|
|
947
|
+
Computes the envelope of the given data. The envelope is determined by
|
|
948
|
+
adding the squared amplitudes of the data and it's Hilbert-Transform and
|
|
949
|
+
then taking the square-root. The envelope at the start/end should not be
|
|
950
|
+
taken too seriously.
|
|
951
|
+
"""
|
|
952
|
+
self.data = envelope(self.data)
|
|
953
|
+
if hasattr(self, 'data_type'):
|
|
954
|
+
self.data_type += ' envelope'
|
|
955
|
+
return self
|
|
956
|
+
|
|
957
|
+
def spectrum(self, taper=0.05, nfft='default'):
|
|
958
|
+
"""
|
|
959
|
+
Computes the spectrum of the given data.
|
|
960
|
+
|
|
961
|
+
:param taper: Decimal percentage of Tukey taper.
|
|
962
|
+
:param nfft: Number of points for FFT. None = sampling points, 'default'
|
|
963
|
+
= next power of 2 of sampling points.
|
|
964
|
+
:return: Spectrum and frequency sequence.
|
|
965
|
+
"""
|
|
966
|
+
return spectrum(self.data, self.fs, taper=taper, nfft=nfft)
|
|
967
|
+
|
|
968
|
+
def spectrogram(self, **kwargs):
|
|
969
|
+
"""
|
|
970
|
+
Computes the spectrogram of the given data.
|
|
971
|
+
|
|
972
|
+
:param xmin, xmax: int. Start channel and end channel for calculating
|
|
973
|
+
the average spectrogram.
|
|
974
|
+
:param nperseg: int. Length of each segment.
|
|
975
|
+
:param noverlap: int. Number of points to overlap between segments. If
|
|
976
|
+
None, noverlap = nperseg // 2.
|
|
977
|
+
:param nfft: int. Length of the FFT used. None = nperseg.
|
|
978
|
+
:param detrend : str or bool. Specifies whether and how to detrend each
|
|
979
|
+
segment. 'linear' or 'detrend' or True = detrend, 'constant' or
|
|
980
|
+
'demean' = demean.
|
|
981
|
+
:param boundary: str or None. Specifies whether the input signal is
|
|
982
|
+
extended at both ends, and how to generate the new values, in order
|
|
983
|
+
to center the first windowed segment on the first input point. This
|
|
984
|
+
has the benefit of enabling reconstruction of the first input point
|
|
985
|
+
when the employed window function starts at zero. Valid options are
|
|
986
|
+
['even', 'odd', 'constant', 'zeros', None].
|
|
987
|
+
:return: Spectrogram, frequency sequence and time sequence.
|
|
988
|
+
"""
|
|
989
|
+
if 'xmin' in kwargs.keys():
|
|
990
|
+
xmin = int(kwargs.pop('xmin') - self.start_channel)
|
|
991
|
+
else:
|
|
992
|
+
xmin = 0
|
|
993
|
+
if 'xmax' in kwargs.keys():
|
|
994
|
+
xmax = int(kwargs.pop('xmax') - self.start_channel)
|
|
995
|
+
else:
|
|
996
|
+
xmax = len(self.data)
|
|
997
|
+
|
|
998
|
+
return spectrogram(self.data[xmin:xmax], self.fs, **kwargs)
|
|
999
|
+
|
|
1000
|
+
def fk_transform(self, **kwargs):
|
|
1001
|
+
"""
|
|
1002
|
+
Transform the data to the fk domain using 2-D Fourier transform method
|
|
1003
|
+
|
|
1004
|
+
:param taper: float or sequence of floats. Each float means decimal
|
|
1005
|
+
percentage of Tukey taper for corresponding dimension (ranging from
|
|
1006
|
+
0 to 1). Default is 0.1 which tapers 5% from the beginning and 5%
|
|
1007
|
+
from the end.
|
|
1008
|
+
:param nfft: Number of points for FFT. None means sampling points;
|
|
1009
|
+
'default' means next power of 2 of sampling points, which makes
|
|
1010
|
+
result smoother.
|
|
1011
|
+
"""
|
|
1012
|
+
return fk_transform(self.data, self.dx, self.fs, **kwargs)
|
|
1013
|
+
|
|
1014
|
+
def channel_checking(self, use=False, **kwargs):
|
|
1015
|
+
"""
|
|
1016
|
+
Use the energy of each channel to determine which channels are bad.
|
|
1017
|
+
|
|
1018
|
+
:param use: bool. If True, only keep the data of good channels in
|
|
1019
|
+
self.data and return self.
|
|
1020
|
+
:param deg: int. Degree of the fitting polynomial.
|
|
1021
|
+
:param thresh: int or float. The MAD multiple of bad channel energy
|
|
1022
|
+
lower than good channels.
|
|
1023
|
+
:param continuity: bool. Perform continuity checks on bad channels and
|
|
1024
|
+
good channels.
|
|
1025
|
+
:param adjacent: int. The number of nearby channels for continuity
|
|
1026
|
+
checks.
|
|
1027
|
+
:param toleration: int. The number of discontinuous channel allowed in
|
|
1028
|
+
each channel (including itself) in the continuity check.
|
|
1029
|
+
:param plot: bool or str. False means no plotting. Str or True means
|
|
1030
|
+
plotting while str gives a non-default filename.
|
|
1031
|
+
:return: self or Good channels and bad channels.
|
|
1032
|
+
"""
|
|
1033
|
+
good_chn, bad_chn = channel_checking(self.data, **kwargs)
|
|
1034
|
+
if use:
|
|
1035
|
+
self.channel_data(good_chn, replace=True)
|
|
1036
|
+
return self
|
|
1037
|
+
else:
|
|
1038
|
+
return good_chn, bad_chn
|
|
1039
|
+
|
|
1040
|
+
def turning_points(self, data_type='default', **kwargs):
|
|
1041
|
+
"""
|
|
1042
|
+
Seek turning points in the DAS channel.
|
|
1043
|
+
|
|
1044
|
+
:param data_type: str. If data_type is 'coordinate', data should include
|
|
1045
|
+
latitude and longitude (first two columns), and can also include
|
|
1046
|
+
depth (last column). If data_type is 'waveform', data should be
|
|
1047
|
+
continuous waveform, preferably containing signal with strong
|
|
1048
|
+
coherence (earthquake, traffic signal, etc.).
|
|
1049
|
+
:param thresh: For coordinate data, when the angle of the optical cables
|
|
1050
|
+
on both sides centered on a certain point exceeds thresh, it is
|
|
1051
|
+
considered an turning point. For waveform, thresh means the MAD
|
|
1052
|
+
multiple of adjacent channel cross-correlation values lower than
|
|
1053
|
+
their median.
|
|
1054
|
+
:param depth_info: bool. Optional if data_type is 'coordinate'. Whether
|
|
1055
|
+
depth (in meters) is included in the coordinate data and need to be
|
|
1056
|
+
used.
|
|
1057
|
+
:param channel_gap: int. Optional if data_type is 'coordinate'. The
|
|
1058
|
+
smaller the value is, the finer the segmentation will be. It is
|
|
1059
|
+
set to half the ratio of gauge length and channel interval by
|
|
1060
|
+
default.
|
|
1061
|
+
:return: list. Channel index of turning points.
|
|
1062
|
+
"""
|
|
1063
|
+
if data_type == 'default':
|
|
1064
|
+
data_type = 'coordinate' if hasattr(self, 'geometry') else \
|
|
1065
|
+
'waveform'
|
|
1066
|
+
if data_type == 'coordinate':
|
|
1067
|
+
if hasattr(self, 'gauge_length'):
|
|
1068
|
+
kwargs.setdefault(
|
|
1069
|
+
'channel_gap', self.gauge_length / self.dx / 2)
|
|
1070
|
+
if 'data' in kwargs.keys():
|
|
1071
|
+
output = turning_points(data_type=data_type, **kwargs)
|
|
1072
|
+
elif hasattr(self, 'geometry'):
|
|
1073
|
+
output = turning_points(self.geometry, data_type=data_type,
|
|
1074
|
+
**kwargs)
|
|
1075
|
+
else:
|
|
1076
|
+
raise ValueError('Geometry needs to be defined in DASdata, or '
|
|
1077
|
+
'coordinate data should be given.')
|
|
1078
|
+
else:
|
|
1079
|
+
output = turning_points(self.data, data_type=data_type, **kwargs)
|
|
1080
|
+
|
|
1081
|
+
if isinstance(output, tuple):
|
|
1082
|
+
output = np.array(list(set(output[0]) | set(output[1])))
|
|
1083
|
+
else:
|
|
1084
|
+
output = np.array(output)
|
|
1085
|
+
output += self.start_channel
|
|
1086
|
+
self.turning_channels = output
|
|
1087
|
+
return output
|
|
1088
|
+
|
|
1089
|
+
def spike_removal(self, nch=50, nsp=5, thresh=10):
|
|
1090
|
+
"""
|
|
1091
|
+
Use a median filter to remove high-strain spikes in the data.
|
|
1092
|
+
|
|
1093
|
+
:param nch: int. Number of channels over which to compute the median.
|
|
1094
|
+
:param nsp: int. Number of sampling points over which to compute the
|
|
1095
|
+
median.
|
|
1096
|
+
:param thresh: Ratio threshold over the median over which a number is
|
|
1097
|
+
considered to be an outlier.
|
|
1098
|
+
"""
|
|
1099
|
+
self.data = spike_removal(self.data, nch=nch, nsp=nsp, thresh=thresh)
|
|
1100
|
+
return self
|
|
1101
|
+
|
|
1102
|
+
def common_mode_noise_removal(self):
|
|
1103
|
+
"""
|
|
1104
|
+
Remove common mode noise (sometimes called horizontal noise) from data.
|
|
1105
|
+
"""
|
|
1106
|
+
self.data = common_mode_noise_removal(self.data)
|
|
1107
|
+
return self
|
|
1108
|
+
|
|
1109
|
+
def curvelet_denoising(self, **kwargs):
|
|
1110
|
+
"""
|
|
1111
|
+
Use curevelet transform to filter stochastic or/and cooherent noise.
|
|
1112
|
+
|
|
1113
|
+
:param choice: int. 0 for Gaussian denoising using soft thresholding, 1
|
|
1114
|
+
for velocity filtering using the standard FK methodology and 2 for
|
|
1115
|
+
both.
|
|
1116
|
+
:param pad: float or sequence of floats. Each float means padding
|
|
1117
|
+
percentage before FFT for corresponding dimension. If set to 0.1
|
|
1118
|
+
will pad 5% before the beginning and after the end.
|
|
1119
|
+
:param noise: numpy.ndarray. Noise record as reference.
|
|
1120
|
+
:param soft_thresh: bool. True for soft thresholding and False for hard
|
|
1121
|
+
thresholding.
|
|
1122
|
+
:param vmin, vmax: float. Velocity range in m/s.
|
|
1123
|
+
:param flag: -1 choose only negative apparent velocities, 0 choose both
|
|
1124
|
+
postive and negative apparent velocities, 1 choose only positive
|
|
1125
|
+
apparent velocities.
|
|
1126
|
+
:param mode: str. 'remove' for denoising and 'retain' for decomposition.
|
|
1127
|
+
:param scale_begin: int. The beginning scale to do coherent denoising.
|
|
1128
|
+
:param nbscales: int. Number of scales including the coarsest wavelet
|
|
1129
|
+
level. Default set to ceil(log2(min(M,N)) - 3).
|
|
1130
|
+
:param nbangles: int. Number of angles at the 2nd coarsest level,
|
|
1131
|
+
minimum 8, must be a multiple of 4.
|
|
1132
|
+
"""
|
|
1133
|
+
self.data = curvelet_denoising(self.data, dx=self.dx, fs=self.fs,
|
|
1134
|
+
**kwargs)
|
|
1135
|
+
return self
|
|
1136
|
+
|
|
1137
|
+
def fk_filter(self, mode='retain', verbose=False, **kwargs):
|
|
1138
|
+
"""
|
|
1139
|
+
Transform the data to the f-k domain using 2-D Fourier transform method,
|
|
1140
|
+
and transform back to the x-t domain after filtering.
|
|
1141
|
+
|
|
1142
|
+
:param mode: str. 'remove' for denoising, 'retain' for extraction, and
|
|
1143
|
+
'decompose' for decomposition and not update self.data.
|
|
1144
|
+
:param verbose: If True, return filtered data, f-k spectrum,
|
|
1145
|
+
frequency sequence, wavenumber sequence and f-k mask.
|
|
1146
|
+
:param taper: float or sequence of floats. Each float means decimal
|
|
1147
|
+
percentage of Tukey taper for corresponding dimension (ranging from
|
|
1148
|
+
0 to 1). Default is 0.1 which tapers 5% from the beginning and 5%
|
|
1149
|
+
from the end.
|
|
1150
|
+
:param pad: Pad the data or not. It can be float or sequence of floats.
|
|
1151
|
+
Each float means padding percentage before FFT for corresponding
|
|
1152
|
+
dimension. If set to 0.1 will pad 5% before the beginning and after
|
|
1153
|
+
the end. 'default' means pad both dimensions to next power of 2.
|
|
1154
|
+
None or False means don't pad data before or during Fast Fourier
|
|
1155
|
+
Transform.
|
|
1156
|
+
:param fmin, fmax, kmin, kmax, vmin, vmax: float or or sequence of 2
|
|
1157
|
+
floats. Sequence of 2 floats represents the start and end of taper.
|
|
1158
|
+
:param edge: float. The width of fan mask taper edge.
|
|
1159
|
+
:param flag: -1 keep only negative apparent velocities, 0 keep both
|
|
1160
|
+
postive and negative apparent velocities, 1 keep only positive
|
|
1161
|
+
apparent velocities.
|
|
1162
|
+
"""
|
|
1163
|
+
output = fk_filter(self.data, self.dx, self.fs, mode=mode,
|
|
1164
|
+
verbose=verbose, **kwargs)
|
|
1165
|
+
if mode == 'decompose':
|
|
1166
|
+
sec1 = self.copy()
|
|
1167
|
+
sec2 = self.copy()
|
|
1168
|
+
sec1.data, sec2.data = output[:2]
|
|
1169
|
+
if verbose:
|
|
1170
|
+
return sec1, sec2, *output[2:]
|
|
1171
|
+
else:
|
|
1172
|
+
return sec1, sec2
|
|
1173
|
+
elif verbose:
|
|
1174
|
+
self.data = output[0]
|
|
1175
|
+
return output
|
|
1176
|
+
else:
|
|
1177
|
+
self.data = output
|
|
1178
|
+
return self
|
|
1179
|
+
|
|
1180
|
+
def curvelet_windowing(self, mode='retain', **kwargs):
|
|
1181
|
+
"""
|
|
1182
|
+
Use curevelet transform to keep cooherent signal with certain velocity
|
|
1183
|
+
range.
|
|
1184
|
+
|
|
1185
|
+
:param mode: str. 'remove' for denoising, 'retain' for extraction, and
|
|
1186
|
+
'decompose' for decomposition and not update self.data.
|
|
1187
|
+
:param vmin, vmax: float. Velocity range in m/s.
|
|
1188
|
+
:param flag: -1 keep only negative apparent velocities, 0 keep both
|
|
1189
|
+
postive and negative apparent velocities, 1 keep only positive
|
|
1190
|
+
apparent velocities.
|
|
1191
|
+
:param pad: float or sequence of floats. Each float means padding
|
|
1192
|
+
percentage before FFT for corresponding dimension. If set to 0.1
|
|
1193
|
+
will pad 5% before the beginning and after the end.
|
|
1194
|
+
:param scale_begin: int. The beginning scale to do coherent denoising.
|
|
1195
|
+
:param nbscales: int. Number of scales including the coarsest wavelet
|
|
1196
|
+
level. Default set to ceil(log2(min(M,N)) - 3).
|
|
1197
|
+
:param nbangles: int. Number of angles at the 2nd coarsest level,
|
|
1198
|
+
minimum 8, must be a multiple of 4.
|
|
1199
|
+
"""
|
|
1200
|
+
output = curvelet_windowing(self.data, self.dx, self.fs, mode=mode,
|
|
1201
|
+
**kwargs)
|
|
1202
|
+
if mode == 'decompose':
|
|
1203
|
+
sec1 = self.copy()
|
|
1204
|
+
sec2 = self.copy()
|
|
1205
|
+
sec1.data, sec2.data = output
|
|
1206
|
+
return sec1, sec2
|
|
1207
|
+
else:
|
|
1208
|
+
self.data = output
|
|
1209
|
+
return self
|
|
1210
|
+
|
|
1211
|
+
def _strain2vel_attr(self):
|
|
1212
|
+
if hasattr(self, 'data_type'):
|
|
1213
|
+
if 'strain rate' in self.data_type:
|
|
1214
|
+
self.data_type = 'acceleration'
|
|
1215
|
+
elif 'strain' in self.data_type:
|
|
1216
|
+
self.data_type = 'velocity'
|
|
1217
|
+
else:
|
|
1218
|
+
warnings.warn(f'The data type is {self.data_type}, neither '
|
|
1219
|
+
'strain nor strain rate. But it still takes '
|
|
1220
|
+
'effect.')
|
|
1221
|
+
else:
|
|
1222
|
+
self.data_type = 'velocity or acceleration'
|
|
1223
|
+
return self
|
|
1224
|
+
|
|
1225
|
+
def fk_rescaling(self, turning=None, verbose=False, **kwargs):
|
|
1226
|
+
"""
|
|
1227
|
+
Convert strain / strain rate to velocity / acceleration by fk rescaling.
|
|
1228
|
+
|
|
1229
|
+
:param turning: Sequence of int. Channel number of turning points. If
|
|
1230
|
+
self.turning exists, it will be used by default unless the parameter
|
|
1231
|
+
turning is set to False.
|
|
1232
|
+
:param verbose: If True and turning is not set, return f-k spectrum,
|
|
1233
|
+
frequency sequence, wavenumber sequence and f-k mask.
|
|
1234
|
+
:param taper: float or sequence of floats. Each float means decimal
|
|
1235
|
+
percentage of Tukey taper for corresponding dimension (ranging from
|
|
1236
|
+
0 to 1). Default is 0.1 which tapers 5% from the beginning and 5%
|
|
1237
|
+
from the end. If the turning parameter is set, this parameter will
|
|
1238
|
+
be invalid.
|
|
1239
|
+
:param pad: Pad the data or not. It can be float or sequence of floats.
|
|
1240
|
+
Each float means padding percentage before FFT for corresponding
|
|
1241
|
+
dimension. If set to 0.1 will pad 5% before the beginning and after
|
|
1242
|
+
the end. 'default' means pad both dimensions to next power of 2.
|
|
1243
|
+
None or False means don't pad data before or during Fast Fourier
|
|
1244
|
+
Transform.
|
|
1245
|
+
:param fmax, kmin, vmax: float or or sequence of 2 floats. Sequence of 2
|
|
1246
|
+
floats represents the start and end of taper. Setting these
|
|
1247
|
+
parameters can reduce artifacts.
|
|
1248
|
+
:param edge: float. The width of fan mask taper edge.
|
|
1249
|
+
"""
|
|
1250
|
+
if hasattr(self, 'turning_channels') and turning is None:
|
|
1251
|
+
turning = np.array(self.turning_channels) - self.start_channel
|
|
1252
|
+
|
|
1253
|
+
self._strain2vel_attr()
|
|
1254
|
+
if verbose and not turning:
|
|
1255
|
+
data_res, fk, f, k, mask = fk_rescaling(self.data, self.dx, self.fs,
|
|
1256
|
+
verbose=True, **kwargs)
|
|
1257
|
+
self.data = data_res
|
|
1258
|
+
return fk, f, k, mask
|
|
1259
|
+
else:
|
|
1260
|
+
self.data = fk_rescaling(self.data, self.dx, self.fs, **kwargs)
|
|
1261
|
+
return self
|
|
1262
|
+
|
|
1263
|
+
def curvelet_conversion(self, turning=None, **kwargs):
|
|
1264
|
+
"""
|
|
1265
|
+
Use curevelet transform to convert strain/strain rate to
|
|
1266
|
+
velocity/acceleration.
|
|
1267
|
+
|
|
1268
|
+
:param turning: Sequence of int. Channel number of turning points. If
|
|
1269
|
+
self.turning exists, it will be used by default unless the parameter
|
|
1270
|
+
turning is set to False.
|
|
1271
|
+
:param pad: float or sequence of floats. Each float means padding
|
|
1272
|
+
percentage before FFT for corresponding dimension. If set to 0.1
|
|
1273
|
+
will pad 5% before the beginning and after the end.
|
|
1274
|
+
:param scale_begin: int. The beginning scale to do conversion.
|
|
1275
|
+
:param nbscales: int. Number of scales including the coarsest wavelet
|
|
1276
|
+
level. Default set to ceil(log2(min(M,N)) - 3).
|
|
1277
|
+
:param nbangles: int. Number of angles at the 2nd coarsest level,
|
|
1278
|
+
minimum 8, must be a multiple of 4.
|
|
1279
|
+
"""
|
|
1280
|
+
if hasattr(self, 'turning_channels') and turning is None:
|
|
1281
|
+
turning = np.array(self.turning_channels) - self.start_channel
|
|
1282
|
+
|
|
1283
|
+
self.data = curvelet_conversion(self.data, self.dx, self.fs,
|
|
1284
|
+
turning=turning, **kwargs)
|
|
1285
|
+
self._strain2vel_attr()
|
|
1286
|
+
return self
|
|
1287
|
+
|
|
1288
|
+
def slant_stacking(self, channel='all', turning=None, **kwargs):
|
|
1289
|
+
"""
|
|
1290
|
+
Convert strain to velocity based on slant-stack.
|
|
1291
|
+
|
|
1292
|
+
:param channel: int or list or 'all'. convert a certain channel number /
|
|
1293
|
+
certain channel range / all channels.
|
|
1294
|
+
:param turning: Sequence of int. Channel number of turning points. If
|
|
1295
|
+
self.turning exists, it will be used by default unless the parameter
|
|
1296
|
+
turning is set to False.
|
|
1297
|
+
:param L: int. the number of adjacent channels over which slowness is
|
|
1298
|
+
estimated.
|
|
1299
|
+
:param slm: float. Slowness x max
|
|
1300
|
+
:param sls: float. slowness step
|
|
1301
|
+
:param freqmin: Pass band low corner frequency.
|
|
1302
|
+
:param freqmax: Pass band high corner frequency.
|
|
1303
|
+
"""
|
|
1304
|
+
if hasattr(self, 'turning_channels') and turning is None:
|
|
1305
|
+
turning = np.array(self.turning_channels) - self.start_channel
|
|
1306
|
+
|
|
1307
|
+
if isinstance(channel, int):
|
|
1308
|
+
channel = [channel - self.start_channel]
|
|
1309
|
+
elif isinstance(channel, Iterable):
|
|
1310
|
+
channel = np.array(channel) - self.start_channel
|
|
1311
|
+
elif isinstance(channel, str) and channel == 'all':
|
|
1312
|
+
channel = list(range(self.nch))
|
|
1313
|
+
|
|
1314
|
+
self.start_channel += channel[0]
|
|
1315
|
+
self.start_distance += channel[0] * self.dx
|
|
1316
|
+
self.data = slant_stacking(self.data, self.dx, self.fs, channel=channel,
|
|
1317
|
+
turning=turning, **kwargs)
|
|
1318
|
+
self._strain2vel_attr()
|
|
1319
|
+
return self
|