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/read.py
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
# Purpose: Module for reading DAS data.
|
|
2
|
+
# Author: Minzhe Hu
|
|
3
|
+
# Date: 2024.11.1
|
|
4
|
+
# Email: hmz2018@mail.ustc.edu.cn
|
|
5
|
+
# Partially modified from
|
|
6
|
+
# https://github.com/RobbinLuo/das-toolkit/blob/main/DasTools/DasPrep.py
|
|
7
|
+
import warnings
|
|
8
|
+
import json
|
|
9
|
+
import pickle
|
|
10
|
+
import numpy as np
|
|
11
|
+
import h5py
|
|
12
|
+
import segyio
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from nptdms import TdmsFile
|
|
15
|
+
from daspy.core.section import Section
|
|
16
|
+
from daspy.core.dasdatetime import DASDateTime, utc
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def read(fname=None, output_type='section', ftype=None, headonly=False,
|
|
20
|
+
**kwargs):
|
|
21
|
+
"""
|
|
22
|
+
Read a .pkl/.pickle, .tdms, .h5/.hdf5, .segy/.sgy file.
|
|
23
|
+
|
|
24
|
+
:param fname: str or pathlib.PosixPath. Path of DAS data file.
|
|
25
|
+
:param output_type: str. 'Section' means return an instance of
|
|
26
|
+
daspy.Section, 'array' means return numpy.array for data and a
|
|
27
|
+
dictionary for metadata.
|
|
28
|
+
:param ftype: None, str or function. None for automatic detection, or str to
|
|
29
|
+
specify a type of 'pkl', 'pickle', 'tdms', 'h5', 'hdf5', 'segy', 'sgy',
|
|
30
|
+
or 'npy', or a function for read data and metadata.
|
|
31
|
+
:param headonly. bool. If True, only metadata will be read, the returned
|
|
32
|
+
data will be an array of all zeros of the same size as the original
|
|
33
|
+
data.
|
|
34
|
+
:param ch1: int. The first channel required.
|
|
35
|
+
:param ch2: int. The last channel required (not included).
|
|
36
|
+
:param dch: int. Channel step.
|
|
37
|
+
:return: An instance of daspy.Section, or numpy.array for data and a
|
|
38
|
+
dictionary for metadata.
|
|
39
|
+
"""
|
|
40
|
+
fun_map = {'pkl': _read_pkl, 'tdms': _read_tdms, 'h5': _read_h5,
|
|
41
|
+
'sgy': _read_segy, 'npy': _read_npy}
|
|
42
|
+
if fname is None:
|
|
43
|
+
fname = Path(__file__).parent / 'example.pkl'
|
|
44
|
+
ftype = 'pkl'
|
|
45
|
+
if ftype is None:
|
|
46
|
+
ftype = str(fname).split('.')[-1].lower()
|
|
47
|
+
|
|
48
|
+
if callable(ftype):
|
|
49
|
+
try:
|
|
50
|
+
data, metadata = ftype(fname, headonly=headonly, **kwargs)
|
|
51
|
+
except TypeError:
|
|
52
|
+
data, metadata = ftype(fname)
|
|
53
|
+
else:
|
|
54
|
+
for rtp in [('pickle', 'pkl'), ('hdf5', 'h5'), ('segy', 'sgy')]:
|
|
55
|
+
ftype = ftype.replace(*rtp)
|
|
56
|
+
data, metadata = fun_map[ftype](fname, headonly=headonly, **kwargs)
|
|
57
|
+
|
|
58
|
+
if output_type.lower() == 'section':
|
|
59
|
+
metadata['source'] = Path(fname)
|
|
60
|
+
metadata['source_type'] = ftype
|
|
61
|
+
return Section(data.astype(float), **metadata)
|
|
62
|
+
elif output_type.lower() == 'array':
|
|
63
|
+
return data, metadata
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _read_pkl(fname, headonly=False, **kwargs):
|
|
67
|
+
dch = kwargs.pop('dch', 1)
|
|
68
|
+
with open(fname, 'rb') as f:
|
|
69
|
+
pkl_data = pickle.load(f)
|
|
70
|
+
if isinstance(pkl_data, np.ndarray):
|
|
71
|
+
warnings.warn('This data format doesn\'t include channel interval'
|
|
72
|
+
'and sampling rate. Please set manually')
|
|
73
|
+
if headonly:
|
|
74
|
+
return np.zeros_like(pkl_data), {'dx': None, 'fs': None}
|
|
75
|
+
else:
|
|
76
|
+
ch1 = kwargs.pop('ch1', 0)
|
|
77
|
+
ch2 = kwargs.pop('ch2', len(pkl_data))
|
|
78
|
+
return pkl_data[ch1:ch2:dch], {'dx': None, 'fs': None}
|
|
79
|
+
elif isinstance(pkl_data, dict):
|
|
80
|
+
data = pkl_data.pop('data')
|
|
81
|
+
if headonly:
|
|
82
|
+
data = np.zeros_like(data)
|
|
83
|
+
else:
|
|
84
|
+
if 'ch1' in kwargs.keys() or 'ch2' in kwargs.keys():
|
|
85
|
+
if 'start_channel' in pkl_data.keys():
|
|
86
|
+
s_chn = pkl_data['start_channel']
|
|
87
|
+
print(f'Data is start with channel {s_chn}.')
|
|
88
|
+
else:
|
|
89
|
+
s_chn = 0
|
|
90
|
+
ch1 = kwargs.pop('ch1', s_chn)
|
|
91
|
+
ch2 = kwargs.pop('ch2', s_chn + len(data))
|
|
92
|
+
data = data[ch1 - s_chn:ch2 - s_chn, :]
|
|
93
|
+
pkl_data['start_channel'] = ch1
|
|
94
|
+
return data, pkl_data
|
|
95
|
+
else:
|
|
96
|
+
raise TypeError('Unknown data type.')
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _read_h5_headers(group):
|
|
100
|
+
headers = {}
|
|
101
|
+
if len(group.attrs) != 0:
|
|
102
|
+
headers['attrs'] = dict(group.attrs)
|
|
103
|
+
for k in group.keys():
|
|
104
|
+
gp = group[k]
|
|
105
|
+
if isinstance(gp, h5py._hl.dataset.Dataset):
|
|
106
|
+
continue
|
|
107
|
+
elif isinstance(gp, h5py._hl.group.Group):
|
|
108
|
+
gp_headers = _read_h5_headers(group[k])
|
|
109
|
+
if len(gp_headers):
|
|
110
|
+
headers[k] = gp_headers
|
|
111
|
+
else:
|
|
112
|
+
headers[k] = gp
|
|
113
|
+
|
|
114
|
+
return headers
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _read_h5_starttime(h5_file):
|
|
118
|
+
try:
|
|
119
|
+
stime = h5_file['Acquisition/Raw[0]/RawData'].\
|
|
120
|
+
attrs['PartStartTime'].decode('ascii')
|
|
121
|
+
except KeyError:
|
|
122
|
+
try:
|
|
123
|
+
stime = h5_file['Acquisition'].\
|
|
124
|
+
attrs['MeasurementStartTime'].decode('ascii')
|
|
125
|
+
except KeyError:
|
|
126
|
+
try:
|
|
127
|
+
stime = h5_file['Acquisition/Raw[0]/RawDataTime/'][0]
|
|
128
|
+
except KeyError:
|
|
129
|
+
return 0
|
|
130
|
+
|
|
131
|
+
if isinstance(stime, str):
|
|
132
|
+
if len(stime) > 26:
|
|
133
|
+
stime = DASDateTime.strptime(stime, '%Y-%m-%dT%H:%M:%S.%f%z')
|
|
134
|
+
else:
|
|
135
|
+
stime = DASDateTime.strptime(stime, '%Y-%m-%dT%H:%M:%S.%f').\
|
|
136
|
+
astimezone(utc)
|
|
137
|
+
else:
|
|
138
|
+
stime = DASDateTime.fromtimestamp(stime / 1e6).astimezone(utc)
|
|
139
|
+
|
|
140
|
+
return stime
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _read_h5(fname, headonly=False, **kwargs):
|
|
144
|
+
with h5py.File(fname, 'r') as h5_file:
|
|
145
|
+
dch = kwargs.pop('dch', 1)
|
|
146
|
+
group = list(h5_file.keys())[0]
|
|
147
|
+
if len(h5_file.keys()) >= 10: # ASN/OptoDAS https://github.com/ASN-Norway/simpleDAS
|
|
148
|
+
ch1 = kwargs.pop('ch1', 0)
|
|
149
|
+
if h5_file['header/dimensionNames'][0] == b'time':
|
|
150
|
+
nch = h5_file['data'].shape[1]
|
|
151
|
+
if headonly:
|
|
152
|
+
data = np.zeros_like(h5_file['data']).T
|
|
153
|
+
else:
|
|
154
|
+
ch2 = kwargs.pop('ch2', nch)
|
|
155
|
+
data = h5_file['data'][:, ch1:ch2:dch].T
|
|
156
|
+
elif h5_file['header/dimensionNames'][0] == b'distance':
|
|
157
|
+
nch = h5_file['data'].shape[1]
|
|
158
|
+
if headonly:
|
|
159
|
+
data = np.zeros_like(h5_file['data'])
|
|
160
|
+
else:
|
|
161
|
+
ch2 = kwargs.pop('ch2', nch)
|
|
162
|
+
data = h5_file['data'][ch1:ch2:dch, :]
|
|
163
|
+
dx = h5_file['header/dx'][()]
|
|
164
|
+
start_time = DASDateTime.fromtimestamp(
|
|
165
|
+
h5_file['header/time'][()]).utc()
|
|
166
|
+
metadata = {'dx': dx * dch, 'fs': 1 / h5_file['header/dt'][()],
|
|
167
|
+
'start_time': start_time, 'start_channel': ch1,
|
|
168
|
+
'start_distance': ch1 * dx,
|
|
169
|
+
'scale': h5_file['header/dataScale'][()]}
|
|
170
|
+
if h5_file['header/gaugeLength'][()] != np.nan:
|
|
171
|
+
metadata['guage_length'] = h5_file['header/gaugeLength'][()]
|
|
172
|
+
elif len(h5_file.keys()) == 5: # AP Sensing
|
|
173
|
+
# read data
|
|
174
|
+
nch = h5_file['strain'].shape[1]
|
|
175
|
+
ch1 = kwargs.pop('ch1', 0)
|
|
176
|
+
ch2 = kwargs.pop('ch2', nch)
|
|
177
|
+
if headonly:
|
|
178
|
+
data = np.zeros_like(h5_file['strain']).T
|
|
179
|
+
else:
|
|
180
|
+
data = h5_file['strain'][:, ch1:ch2:dch].T
|
|
181
|
+
|
|
182
|
+
# read metadata
|
|
183
|
+
dx = h5_file['spatialsampling'][()]
|
|
184
|
+
metadata = {'fs': h5_file['RepetitionFrequency'][()],
|
|
185
|
+
'dx': dx * dch, 'start_channel': ch1,
|
|
186
|
+
'start_distance': ch1 * dx,
|
|
187
|
+
'gauge_length': h5_file.get('GaugeLength')[()]}
|
|
188
|
+
elif set(h5_file.keys()) == {'Mapping', 'Acquisition'}: # Silixa/iDAS
|
|
189
|
+
nch = h5_file['Acquisition/Raw[0]'].attrs['NumberOfLoci']
|
|
190
|
+
ch1 = kwargs.pop('ch1', 0)
|
|
191
|
+
ch2 = kwargs.pop('ch2', nch)
|
|
192
|
+
if h5_file['Acquisition/Raw[0]/RawData/'].shape[0] == nch:
|
|
193
|
+
if headonly:
|
|
194
|
+
data = np.zeros_like(h5_file['Acquisition/Raw[0]/RawData/'])
|
|
195
|
+
else:
|
|
196
|
+
data = h5_file['Acquisition/Raw[0]/RawData/']\
|
|
197
|
+
[ch1:ch2:dch, :]
|
|
198
|
+
else:
|
|
199
|
+
if headonly:
|
|
200
|
+
data = np.zeros_like(
|
|
201
|
+
h5_file['Acquisition/Raw[0]/RawData/']).T
|
|
202
|
+
else:
|
|
203
|
+
data = h5_file['Acquisition/Raw[0]/RawData/']\
|
|
204
|
+
[:, ch1:ch2:dch].T
|
|
205
|
+
|
|
206
|
+
dx = np.mean(h5_file['Mapping/MeasuredSpatialResolution'])
|
|
207
|
+
start_distance = h5_file['Acquisition/Custom/UserSettings'].\
|
|
208
|
+
attrs['StartDistance'] + ch1 * dx
|
|
209
|
+
h5_file['Acquisition/Raw[0]/RawData'].attrs['PartStartTime']
|
|
210
|
+
fs = h5_file['Acquisition/Raw[0]'].attrs['OutputDataRate']
|
|
211
|
+
gauge_length = h5_file['Acquisition'].attrs['GaugeLength']
|
|
212
|
+
scale = h5_file['Acquisition/Raw[0]'].attrs['AmpScaling']
|
|
213
|
+
geometry = np.vstack((h5_file['Mapping/Lon'],
|
|
214
|
+
h5_file['Mapping/Lat'])).T
|
|
215
|
+
metadata = {'dx': dx * dch, 'fs': fs, 'start_channel': ch1,
|
|
216
|
+
'start_distance': ch1 * dx,
|
|
217
|
+
'gauge_length': gauge_length, 'geometry': geometry,
|
|
218
|
+
'scale': scale}
|
|
219
|
+
metadata['start_time'] = _read_h5_starttime(h5_file)
|
|
220
|
+
elif group == 'Acquisition': # OptaSens/ODH, Silixa/iDAS, Smart Sensing/ZD DAS
|
|
221
|
+
# read data
|
|
222
|
+
try:
|
|
223
|
+
nch = h5_file['Acquisition'].attrs['NumberOfLoci']
|
|
224
|
+
except KeyError:
|
|
225
|
+
nch = len(h5_file['Acquisition/Raw[0]/RawData/'])
|
|
226
|
+
ch1 = kwargs.pop('ch1', 0)
|
|
227
|
+
ch2 = kwargs.pop('ch2', nch)
|
|
228
|
+
if h5_file['Acquisition/Raw[0]/RawData/'].shape[0] == nch:
|
|
229
|
+
if headonly:
|
|
230
|
+
data = np.zeros_like(h5_file['Acquisition/Raw[0]/RawData/'])
|
|
231
|
+
else:
|
|
232
|
+
data = h5_file['Acquisition/Raw[0]/RawData/']\
|
|
233
|
+
[ch1:ch2:dch, :]
|
|
234
|
+
else:
|
|
235
|
+
if headonly:
|
|
236
|
+
data = np.zeros_like(
|
|
237
|
+
h5_file['Acquisition/Raw[0]/RawData/']).T
|
|
238
|
+
else:
|
|
239
|
+
data = h5_file['Acquisition/Raw[0]/RawData/']\
|
|
240
|
+
[:, ch1:ch2:dch].T
|
|
241
|
+
|
|
242
|
+
# read metadata
|
|
243
|
+
try:
|
|
244
|
+
fs = h5_file['Acquisition/Raw[0]'].attrs['OutputDataRate']
|
|
245
|
+
except KeyError:
|
|
246
|
+
time_arr = h5_file['Acquisition/Raw[0]/RawDataTime/']
|
|
247
|
+
fs = 1 / (np.diff(time_arr).mean() / 1e6)
|
|
248
|
+
|
|
249
|
+
dx = h5_file['Acquisition'].attrs['SpatialSamplingInterval']
|
|
250
|
+
gauge_length = h5_file['Acquisition'].attrs['GaugeLength']
|
|
251
|
+
metadata = {'dx': dx * dch, 'fs': fs, 'start_channel': ch1,
|
|
252
|
+
'start_distance': ch1 * dx,
|
|
253
|
+
'gauge_length': gauge_length}
|
|
254
|
+
|
|
255
|
+
metadata['start_time'] = _read_h5_starttime(h5_file)
|
|
256
|
+
elif group == 'raw':
|
|
257
|
+
nch = len(h5_file['raw'])
|
|
258
|
+
ch1 = kwargs.pop('ch1', 0)
|
|
259
|
+
ch2 = kwargs.pop('ch2', nch)
|
|
260
|
+
if headonly:
|
|
261
|
+
data = np.zeros_like(h5_file['raw'])
|
|
262
|
+
else:
|
|
263
|
+
data = h5_file['raw'][ch1:ch2:dch, :]
|
|
264
|
+
fs = round(1 / np.diff(h5_file['timestamp']).mean())
|
|
265
|
+
start_time = DASDateTime.fromtimestamp(
|
|
266
|
+
h5_file['timestamp'][0]).astimezone(utc)
|
|
267
|
+
warnings.warn('This data format doesn\'t include channel interval. '
|
|
268
|
+
'Please set manually')
|
|
269
|
+
metadata = {'dx': None, 'fs': fs, 'start_channel': ch1,
|
|
270
|
+
'start_time': start_time}
|
|
271
|
+
elif group == 'data_product':
|
|
272
|
+
# read data
|
|
273
|
+
nch = h5_file.attrs['nx']
|
|
274
|
+
ch1 = kwargs.pop('ch1', 0)
|
|
275
|
+
ch2 = kwargs.pop('ch2', nch)
|
|
276
|
+
array_shape = h5_file['data_product/data'].shape
|
|
277
|
+
if array_shape[0] == nch:
|
|
278
|
+
if headonly:
|
|
279
|
+
data = np.zeros_like(h5_file['data_product/data'])
|
|
280
|
+
else:
|
|
281
|
+
data = h5_file['data_product/data'][ch1:ch2:dch, :]
|
|
282
|
+
else:
|
|
283
|
+
if headonly:
|
|
284
|
+
data = np.zeros_like(h5_file['data_product/data']).T
|
|
285
|
+
else:
|
|
286
|
+
data = h5_file['data_product/data'][:, ch1:ch2:dch].T
|
|
287
|
+
|
|
288
|
+
# read metadata
|
|
289
|
+
fs = 1 / h5_file.attrs['dt_computer']
|
|
290
|
+
dx = h5_file.attrs['dx']
|
|
291
|
+
gauge_length = h5_file.attrs['gauge_length']
|
|
292
|
+
if h5_file.attrs['saving_start_gps_time'] > 0:
|
|
293
|
+
start_time = DASDateTime.fromtimestamp(
|
|
294
|
+
h5_file.attrs['file_start_gps_time'])
|
|
295
|
+
else:
|
|
296
|
+
start_time = DASDateTime.fromtimestamp(
|
|
297
|
+
h5_file.attrs['file_start_computer_time'])
|
|
298
|
+
data_type = h5_file.attrs['data_product']
|
|
299
|
+
|
|
300
|
+
metadata = {'dx': dx * dch, 'fs': fs, 'start_channel': ch1,
|
|
301
|
+
'start_distance': ch1 * dx,
|
|
302
|
+
'start_time': start_time.astimezone(utc),
|
|
303
|
+
'gauge_length': gauge_length, 'data_type': data_type}
|
|
304
|
+
else: # Febus
|
|
305
|
+
acquisition = list(h5_file[f'{group}/Source1/Zone1'].keys())[0]
|
|
306
|
+
# read data
|
|
307
|
+
start_channel = int(h5_file[f'{group}/Source1/Zone1'].
|
|
308
|
+
attrs['Extent'][0])
|
|
309
|
+
dataset = h5_file[f'{group}/Source1/Zone1/{acquisition}']
|
|
310
|
+
nch = dataset.shape[-1]
|
|
311
|
+
ch1 = kwargs.pop('ch1', start_channel)
|
|
312
|
+
ch2 = kwargs.pop('ch2', start_channel + nch)
|
|
313
|
+
if headonly:
|
|
314
|
+
data = np.zeros_like(dataset).T.reshape((nch, -1))
|
|
315
|
+
else:
|
|
316
|
+
if len(dataset.shape) == 3: # Febus A1-R
|
|
317
|
+
data = dataset[:, :, ch1 - start_channel:ch2 - start_channel
|
|
318
|
+
:dch].T.reshape(((ch2 - ch1) // dch, -1))
|
|
319
|
+
elif len(dataset.shape) == 2: # Febus A1
|
|
320
|
+
data = dataset[:, ch1 - start_channel:ch2 - start_channel:
|
|
321
|
+
dch].T
|
|
322
|
+
# read metadata
|
|
323
|
+
attrs = h5_file[f'{group}/Source1/Zone1'].attrs
|
|
324
|
+
dx = attrs['Spacing'][0]
|
|
325
|
+
try:
|
|
326
|
+
fs = float(attrs['FreqRes'])
|
|
327
|
+
except KeyError:
|
|
328
|
+
try:
|
|
329
|
+
fs = (attrs['PulseRateFreq'][0] /
|
|
330
|
+
attrs['SamplingRes'][0]) / 1000
|
|
331
|
+
except KeyError:
|
|
332
|
+
fs = attrs['SamplingRate'][0]
|
|
333
|
+
start_distance = attrs['Origin'][0]
|
|
334
|
+
time = h5_file[f'{group}/Source1/time']
|
|
335
|
+
if len(time.shape) == 2: # Febus A1-R
|
|
336
|
+
start_time = DASDateTime.fromtimestamp(time[0, 0]).\
|
|
337
|
+
astimezone(utc)
|
|
338
|
+
elif len(time.shape) == 1: # Febus A1
|
|
339
|
+
start_time = DASDateTime.fromtimestamp(time[0]).astimezone(utc)
|
|
340
|
+
gauge_length = attrs['GaugeLength'][0]
|
|
341
|
+
metadata = {'dx': dx * dch, 'fs': fs, 'start_channel': ch1,
|
|
342
|
+
'start_distance': start_distance +
|
|
343
|
+
(ch1 - start_channel) * dx,
|
|
344
|
+
'start_time': start_time, 'gauge_length': gauge_length}
|
|
345
|
+
|
|
346
|
+
metadata['headers'] = _read_h5_headers(h5_file)
|
|
347
|
+
|
|
348
|
+
return data, metadata
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _read_tdms(fname, headonly=False, **kwargs):
|
|
352
|
+
# https://nptdms.readthedocs.io/en/stable/quickstart.html
|
|
353
|
+
with TdmsFile.read(fname) as tdms_file:
|
|
354
|
+
group_name = [group.name for group in tdms_file.groups()]
|
|
355
|
+
if 'Measurement' in group_name:
|
|
356
|
+
key = 'Measurement'
|
|
357
|
+
elif 'DAS' in group_name:
|
|
358
|
+
key = 'DAS'
|
|
359
|
+
else:
|
|
360
|
+
key = group_name[0]
|
|
361
|
+
|
|
362
|
+
headers = {**tdms_file.properties, **tdms_file[key].properties}
|
|
363
|
+
nch = len(tdms_file[key])
|
|
364
|
+
dch = kwargs.pop('dch', 1)
|
|
365
|
+
# read data
|
|
366
|
+
if nch > 1:
|
|
367
|
+
start_channel = min(int(channel.name) for channel in
|
|
368
|
+
tdms_file[key].channels())
|
|
369
|
+
ch1 = max(kwargs.pop('ch1', start_channel), start_channel)
|
|
370
|
+
ch2 = min(kwargs.pop('ch2', start_channel + nch),
|
|
371
|
+
start_channel + nch)
|
|
372
|
+
if headonly:
|
|
373
|
+
nt = len(tdms_file[key][str(start_channel)])
|
|
374
|
+
data = np.zeros((nch, nt))
|
|
375
|
+
else:
|
|
376
|
+
data = np.asarray([tdms_file[key][str(ch)]
|
|
377
|
+
for ch in range(ch1, ch2, dch)])
|
|
378
|
+
elif nch == 1:
|
|
379
|
+
try:
|
|
380
|
+
start_channel = int(headers['Initial Channel'])
|
|
381
|
+
except KeyError:
|
|
382
|
+
start_channel = 0
|
|
383
|
+
|
|
384
|
+
ch1 = max(kwargs.pop('ch1', start_channel), start_channel)
|
|
385
|
+
nch = int(headers['Total Channels'])
|
|
386
|
+
ch2 = min(kwargs.pop('ch2', start_channel + nch),
|
|
387
|
+
start_channel + nch)
|
|
388
|
+
if headonly:
|
|
389
|
+
data = np.zeros(len(tdms_file[key].channels()[0])).\
|
|
390
|
+
reshape((nch, -1))
|
|
391
|
+
else:
|
|
392
|
+
data = np.asarray(tdms_file[key].channels()[0]).\
|
|
393
|
+
reshape((-1, nch)).T
|
|
394
|
+
data = data[ch1 - start_channel:ch2 - start_channel:dch]
|
|
395
|
+
|
|
396
|
+
# read metadata
|
|
397
|
+
try:
|
|
398
|
+
dx = headers['SpatialResolution[m]']
|
|
399
|
+
except KeyError:
|
|
400
|
+
try:
|
|
401
|
+
dx = headers['Spatial Resolution']
|
|
402
|
+
except KeyError:
|
|
403
|
+
dx = None
|
|
404
|
+
|
|
405
|
+
try:
|
|
406
|
+
fs = headers['SamplingFrequency[Hz]']
|
|
407
|
+
except KeyError:
|
|
408
|
+
try:
|
|
409
|
+
fs = 1 / headers['Time Base']
|
|
410
|
+
except KeyError:
|
|
411
|
+
fs = None
|
|
412
|
+
|
|
413
|
+
try:
|
|
414
|
+
start_distance = headers['Start Distance (m)'] + \
|
|
415
|
+
dx * (ch1 - start_channel)
|
|
416
|
+
except KeyError:
|
|
417
|
+
start_distance = dx * ch1
|
|
418
|
+
|
|
419
|
+
try:
|
|
420
|
+
start_time = DASDateTime.strptime(headers['ISO8601 Timestamp'],
|
|
421
|
+
'%Y-%m-%dT%H:%M:%S.%f%z')
|
|
422
|
+
except ValueError:
|
|
423
|
+
start_time = DASDateTime.strptime(headers['ISO8601 Timestamp'],
|
|
424
|
+
'%Y-%m-%dT%H:%M:%S.%f')
|
|
425
|
+
except KeyError:
|
|
426
|
+
start_time = 0
|
|
427
|
+
for key in ['GPSTimeStamp', 'CPUTimeStamp', 'Trigger Time']:
|
|
428
|
+
if key in headers.keys():
|
|
429
|
+
if headers[key]:
|
|
430
|
+
start_time = DASDateTime.from_datetime(headers[key].
|
|
431
|
+
item())
|
|
432
|
+
break
|
|
433
|
+
|
|
434
|
+
if dx is not None:
|
|
435
|
+
dx *= dch
|
|
436
|
+
metadata = {'dx': dx, 'fs': fs, 'start_channel': ch1,
|
|
437
|
+
'start_distance': start_distance, 'start_time': start_time,
|
|
438
|
+
'headers': headers}
|
|
439
|
+
|
|
440
|
+
if 'GaugeLength' in headers.keys():
|
|
441
|
+
metadata['gauge_length'] = headers['GaugeLength']
|
|
442
|
+
|
|
443
|
+
return data, metadata
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def _read_segy(fname, headonly=False, **kwargs):
|
|
447
|
+
# https://github.com/equinor/segyio-notebooks/blob/master/notebooks/basic/02_segy_quicklook.ipynb
|
|
448
|
+
with segyio.open(fname, ignore_geometry=True) as segy_file:
|
|
449
|
+
nch = segy_file.tracecount
|
|
450
|
+
ch1 = kwargs.pop('ch1', 0)
|
|
451
|
+
ch2 = kwargs.pop('ch2', nch)
|
|
452
|
+
dch = kwargs.pop('dch', 1)
|
|
453
|
+
|
|
454
|
+
# read data
|
|
455
|
+
if headonly:
|
|
456
|
+
data = np.zeros_like(segy_file.trace.raw[:])
|
|
457
|
+
else:
|
|
458
|
+
data = segy_file.trace.raw[ch1:ch2:dch]
|
|
459
|
+
|
|
460
|
+
# read metadata:
|
|
461
|
+
fs = 1 / (segyio.tools.dt(segy_file) / 1e6)
|
|
462
|
+
metadata = {'dx': None, 'fs': fs, 'start_channel': ch1}
|
|
463
|
+
warnings.warn('This data format doesn\'t include channel interval.'
|
|
464
|
+
'Please set manually')
|
|
465
|
+
|
|
466
|
+
return data, metadata
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _read_npy(fname, headonly=False, **kwargs):
|
|
470
|
+
data = np.load(fname)
|
|
471
|
+
if headonly:
|
|
472
|
+
return np.zeros_like(data), {'dx': None, 'fs': None}
|
|
473
|
+
else:
|
|
474
|
+
ch1 = kwargs.pop('ch1', 0)
|
|
475
|
+
ch2 = kwargs.pop('ch2', len(data))
|
|
476
|
+
dch = kwargs.pop('dch', 1)
|
|
477
|
+
warnings.warn('This data format doesn\'t include channel interval and '
|
|
478
|
+
'sampling rate. Please set manually')
|
|
479
|
+
return data[ch1:ch2:dch], {'dx': None, 'fs': None}
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def read_json(fname, output_type='dict'):
|
|
483
|
+
"""
|
|
484
|
+
Read .json metadata file. See {Lai et al. , 2024, Seismol. Res. Lett.}
|
|
485
|
+
|
|
486
|
+
:param fname: str or pathlib.PosixPath. Path of json file.
|
|
487
|
+
:param output_type: str. 'dict' means return a dictionary, and 'Section'
|
|
488
|
+
means return a empty daspy.Section instance with metadata.
|
|
489
|
+
:return: A dictionary of metadata or an instance of daspy.Section without
|
|
490
|
+
data.
|
|
491
|
+
"""
|
|
492
|
+
with open(fname, 'r') as fcc_file:
|
|
493
|
+
headers = json.load(fcc_file)
|
|
494
|
+
if output_type.lower() == 'dict':
|
|
495
|
+
return headers
|
|
496
|
+
elif output_type.lower() in ['section', 'sec']:
|
|
497
|
+
if len(headers['Overview']['Interrogator']) > 1:
|
|
498
|
+
case_type = 'Multiple interrogators, single cable'
|
|
499
|
+
sec_num = len(headers['Overview']['Interrogator'])
|
|
500
|
+
sec = []
|
|
501
|
+
for interrogator in headers['Overview']['Interrogator']:
|
|
502
|
+
nch = interrogator['Acquisition'][0]['Attributes']['number_of_channels']
|
|
503
|
+
data = np.zeros((nch, 0))
|
|
504
|
+
dx = interrogator['Acquisition'][0]['Attributes']['spatial_sampling_interval']
|
|
505
|
+
fs = interrogator['Acquisition'][0]['Attributes']['acquisition_sample_rate']
|
|
506
|
+
gauge_length = interrogator['Acquisition'][0]['Attributes']['gauge_length']
|
|
507
|
+
sec.append(Section(data, dx, fs, gauge_length=gauge_length,
|
|
508
|
+
headers=headers))
|
|
509
|
+
elif len(headers['Overview']['Interrogator'][0]['Acquisition']) > 1:
|
|
510
|
+
case_type = 'Active survey'
|
|
511
|
+
sec_num = len(
|
|
512
|
+
headers['Overview']['Interrogator'][0]['Acquisition'])
|
|
513
|
+
sec = []
|
|
514
|
+
for acquisition in headers['Overview']['Interrogator'][0]['Acquisition']:
|
|
515
|
+
nch = acquisition['Attributes']['number_of_channels']
|
|
516
|
+
data = np.zeros((nch, 0))
|
|
517
|
+
dx = acquisition['Attributes']['spatial_sampling_interval']
|
|
518
|
+
fs = acquisition['Attributes']['acquisition_sample_rate']
|
|
519
|
+
gauge_length = acquisition['Attributes']['gauge_length']
|
|
520
|
+
sec.append(Section(data, dx, fs, gauge_length=gauge_length,
|
|
521
|
+
headers=headers))
|
|
522
|
+
else:
|
|
523
|
+
sec_num = 1
|
|
524
|
+
if len(headers['Overview']['Cable']) > 1:
|
|
525
|
+
case_type = 'Single interrogators, multiple cable'
|
|
526
|
+
else:
|
|
527
|
+
env = headers['Overview']['Cable'][0]['Attributes']['cable_environment']
|
|
528
|
+
if env == 'trench':
|
|
529
|
+
case_type = 'Direct buried'
|
|
530
|
+
elif env == 'conduit':
|
|
531
|
+
case_type = 'Dark fiber'
|
|
532
|
+
elif env in ['wireline', 'outside borehole casing']:
|
|
533
|
+
case_type = 'Borehole cable'
|
|
534
|
+
nch = headers['Overview']['Interrogator'][0]['Acquisition'][0]['Attributes']['number_of_channels']
|
|
535
|
+
dx = headers['Overview']['Interrogator'][0]['Acquisition'][0]['Attributes']['spatial_sampling_interval']
|
|
536
|
+
fs = headers['Overview']['Interrogator'][0]['Acquisition'][0]['Attributes']['acquisition_sample_rate']
|
|
537
|
+
gauge_length = headers['Overview']['Interrogator'][0]['Acquisition'][0]['Attributes']['gauge_length']
|
|
538
|
+
data = np.zeros((nch, 0))
|
|
539
|
+
sec = Section(data, dx, fs, gauge_length=gauge_length,
|
|
540
|
+
headers=headers)
|
|
541
|
+
|
|
542
|
+
print(f'For case of {case_type}, create {sec_num} empty daspy.Section '
|
|
543
|
+
'instance(s)')
|
|
544
|
+
return sec
|