DASPy-toolbox 1.2.0__tar.gz → 1.2.2__tar.gz
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.2.0 → daspy_toolbox-1.2.2}/DASPy_toolbox.egg-info/PKG-INFO +1 -1
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/DASPy_toolbox.egg-info/SOURCES.txt +1 -0
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/PKG-INFO +1 -1
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/advanced_tools/denoising.py +2 -2
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/advanced_tools/strain2vel.py +19 -17
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/basic_tools/freqattributes.py +11 -1
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/basic_tools/preprocessing.py +28 -11
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/core/collection.py +28 -28
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/core/dasdatetime.py +1 -1
- daspy_toolbox-1.2.2/daspy/core/make_example.py +32 -0
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/core/read.py +113 -32
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/core/section.py +41 -19
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/core/util.py +16 -5
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/core/write.py +113 -11
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/setup.py +3 -3
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/DASPy_toolbox.egg-info/dependency_links.txt +0 -0
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/DASPy_toolbox.egg-info/entry_points.txt +0 -0
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/DASPy_toolbox.egg-info/requires.txt +0 -0
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/DASPy_toolbox.egg-info/top_level.txt +0 -0
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/LICENSE +0 -0
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/README.md +0 -0
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/__init__.py +0 -0
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/advanced_tools/__init__.py +0 -0
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/advanced_tools/channel.py +0 -0
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/advanced_tools/decomposition.py +0 -0
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/advanced_tools/fdct.py +0 -0
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/basic_tools/__init__.py +0 -0
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/basic_tools/filter.py +0 -0
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/basic_tools/visualization.py +0 -0
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/core/__init__.py +0 -0
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/core/example.pkl +0 -0
- {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: DASPy-toolbox
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: DASPy is an open-source project dedicated to provide a python package for DAS (Distributed Acoustic Sensing) data processing, which comprises classic seismic data processing techniques and Specialized algorithms for DAS applications.
|
|
5
5
|
Home-page: https://github.com/HMZ-03/DASPy
|
|
6
6
|
Author: Minzhe Hu, Zefeng Li
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: DASPy-toolbox
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: DASPy is an open-source project dedicated to provide a python package for DAS (Distributed Acoustic Sensing) data processing, which comprises classic seismic data processing techniques and Specialized algorithms for DAS applications.
|
|
5
5
|
Home-page: https://github.com/HMZ-03/DASPy
|
|
6
6
|
Author: Minzhe Hu, Zefeng Li
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Purpose: Remove noise from data
|
|
2
2
|
# Author: Minzhe Hu, Zefeng Li
|
|
3
|
-
# Date:
|
|
3
|
+
# Date: 2025.9.18
|
|
4
4
|
# Email: hmz2018@mail.ustc.edu.cn
|
|
5
5
|
import numpy as np
|
|
6
6
|
from copy import deepcopy
|
|
@@ -57,7 +57,7 @@ def common_mode_noise_removal(data, method='median'):
|
|
|
57
57
|
common = np.mean(data, 0)
|
|
58
58
|
|
|
59
59
|
xx = np.sum(common ** 2)
|
|
60
|
-
data_dn = np.zeros((nch, nt))
|
|
60
|
+
data_dn = np.zeros((nch, nt), dtype=data.dtype)
|
|
61
61
|
for i in range(nch):
|
|
62
62
|
xc = np.sum(common * data[i])
|
|
63
63
|
data_dn[i] = data[i] - xc / xx * common
|
|
@@ -23,14 +23,14 @@ def fk_rescaling(data, dx, fs, taper=(0.02, 0.05), pad='default', fmax=None,
|
|
|
23
23
|
:param dx: Channel interval in m.
|
|
24
24
|
:param fs: Sampling rate in Hz.
|
|
25
25
|
:param taper: float or sequence of floats. Each float means decimal
|
|
26
|
-
percentage of Tukey taper for corresponding dimension (ranging from 0
|
|
27
|
-
1). Default is 0.1 which tapers 5% from the beginning and 5% from
|
|
28
|
-
end.
|
|
29
|
-
:param pad: Pad the data or not. It can be float or sequence of floats.
|
|
30
|
-
float means padding percentage before FFT for corresponding
|
|
31
|
-
If set to 0.1 will pad 5% before the beginning and after the
|
|
32
|
-
'default' means pad both dimensions to next power of 2. None or
|
|
33
|
-
means don't pad data before or during Fast Fourier Transform.
|
|
26
|
+
percentage of Tukey taper for corresponding dimension (ranging from 0
|
|
27
|
+
to 1). Default is 0.1 which tapers 5% from the beginning and 5% from
|
|
28
|
+
the end.
|
|
29
|
+
:param pad: Pad the data or not. It can be float or sequence of floats.
|
|
30
|
+
Each float means padding percentage before FFT for corresponding
|
|
31
|
+
dimension. If set to 0.1 will pad 5% before the beginning and after the
|
|
32
|
+
end. 'default' means pad both dimensions to next power of 2. None or
|
|
33
|
+
False means don't pad data before or during Fast Fourier Transform.
|
|
34
34
|
:param fmax, kmin, vmax: float or or sequence of 2 floats. Sequence of 2
|
|
35
35
|
floats represents the start and end of taper. Setting these parameters
|
|
36
36
|
can reduce artifacts.
|
|
@@ -71,7 +71,8 @@ def fk_rescaling(data, dx, fs, taper=(0.02, 0.05), pad='default', fmax=None,
|
|
|
71
71
|
kk = np.tile(k, (len(f), 1)).T
|
|
72
72
|
vv = - np.divide(ff, kk, out=np.ones_like(ff) * 1e10, where=kk != 0)
|
|
73
73
|
|
|
74
|
-
mask = fk_fan_mask(f, k, fmax=fmax, kmin=kmin, vmax=vmax, edge=edge)
|
|
74
|
+
mask = fk_fan_mask(f, k, fmax=fmax, kmin=kmin, vmax=vmax, edge=edge) \
|
|
75
|
+
* vv
|
|
75
76
|
mask[kk == 0] = 0
|
|
76
77
|
|
|
77
78
|
data_vel = irfft2(ifftshift(fk * mask, axes=0)).real[:nch, :nt]
|
|
@@ -91,12 +92,12 @@ def curvelet_conversion(data, dx, fs, pad=0.3, scale_begin=2, nbscales=None,
|
|
|
91
92
|
:param data: numpy.ndarray. Data to convert.
|
|
92
93
|
:param dx: Channel interval in m.
|
|
93
94
|
:param fs: Sampling rate in Hz.
|
|
94
|
-
:param pad: float or sequence of floats. Each float means padding
|
|
95
|
-
before FFT for corresponding dimension. If set to 0.1 will
|
|
96
|
-
the beginning and after the end.
|
|
95
|
+
:param pad: float or sequence of floats. Each float means padding
|
|
96
|
+
percentage before FFT for corresponding dimension. If set to 0.1 will
|
|
97
|
+
pad 5% before the beginning and after the end.
|
|
97
98
|
:param scale_begin: int. The beginning scale to do conversion.
|
|
98
|
-
:param nbscales: int. Number of scales including the coarsest wavelet
|
|
99
|
-
Default set to ceil(log2(min(M,N)) - 3).
|
|
99
|
+
:param nbscales: int. Number of scales including the coarsest wavelet
|
|
100
|
+
level. Default set to ceil(log2(min(M,N)) - 3).
|
|
100
101
|
:param nbangles: int. Number of angles at the 2nd coarsest level,
|
|
101
102
|
minimum 8, must be a multiple of 4.
|
|
102
103
|
:param turning: Sequence of int. Channel number of turning points.
|
|
@@ -110,7 +111,8 @@ def curvelet_conversion(data, dx, fs, pad=0.3, scale_begin=2, nbscales=None,
|
|
|
110
111
|
data_vel[s:e] = curvelet_conversion(data[s:e], dx, fs, pad=pad,
|
|
111
112
|
scale_begin=scale_begin,
|
|
112
113
|
nbscales=nbscales,
|
|
113
|
-
nbangles=nbangles,
|
|
114
|
+
nbangles=nbangles,
|
|
115
|
+
turning=None)
|
|
114
116
|
else:
|
|
115
117
|
if pad is None or pad is False:
|
|
116
118
|
pad = 0
|
|
@@ -227,8 +229,8 @@ def slant_stacking(data, dx, fs, L=None, slm=0.01,
|
|
|
227
229
|
for (s, e) in zip(start_ch, end_ch):
|
|
228
230
|
channel_seg = [ch-s for ch in range(s,e) if ch in channel]
|
|
229
231
|
if len(channel_seg):
|
|
230
|
-
d_vel = slant_stacking(data[s:e], dx, fs, L=L, slm=slm,
|
|
231
|
-
frqlow=frqlow, frqhigh=frqhigh,
|
|
232
|
+
d_vel = slant_stacking(data[s:e], dx, fs, L=L, slm=slm,
|
|
233
|
+
sls=sls, frqlow=frqlow, frqhigh=frqhigh,
|
|
232
234
|
turning=None, channel=channel_seg)
|
|
233
235
|
data_vel = np.vstack((data_vel, d_vel))
|
|
234
236
|
else:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Purpose: Analyze frequency attribute and transform in frequency domain
|
|
2
2
|
# Author: Minzhe Hu
|
|
3
|
-
# Date:
|
|
3
|
+
# Date: 2025.11.12
|
|
4
4
|
# Email: hmz2018@mail.ustc.edu.cn
|
|
5
5
|
import numpy as np
|
|
6
6
|
from numpy.fft import rfft, rfft2, fftshift, fftfreq, rfftfreq
|
|
@@ -147,3 +147,13 @@ def fk_transform(data, dx, fs, taper=(0, 0.05), nfft='default'):
|
|
|
147
147
|
f = rfftfreq(nfft[1], d=1. / fs)
|
|
148
148
|
k = fftshift(fftfreq(nfft[0], d=dx))
|
|
149
149
|
return fk, f, k
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def power(data):
|
|
153
|
+
"""
|
|
154
|
+
Calculate the power of each channel.
|
|
155
|
+
|
|
156
|
+
:param data: numpy.ndarray. Data to calculate the power.
|
|
157
|
+
:return: numpy.ndarray. Power of each channel.
|
|
158
|
+
"""
|
|
159
|
+
return np.sqrt(np.mean(data ** 2, axis=-1))
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# Purpose: Some preprocess methods
|
|
2
2
|
# Author: Minzhe Hu
|
|
3
|
-
# Date: 2025.
|
|
3
|
+
# Date: 2025.10.30
|
|
4
4
|
# Email: hmz2018@mail.ustc.edu.cn
|
|
5
5
|
import warnings
|
|
6
6
|
import numpy as np
|
|
7
|
+
from numpy.fft import rfft, irfft, rfftfreq
|
|
7
8
|
from scipy.signal import detrend
|
|
8
9
|
from scipy.signal.windows import tukey
|
|
9
10
|
from daspy.basic_tools.filter import lowpass_cheby_2
|
|
@@ -18,10 +19,10 @@ def phase2strain(data, lam, e, n, gl):
|
|
|
18
19
|
:param e: float. photo-slastic scaling factor for logitudinal strain in
|
|
19
20
|
isotropic material.
|
|
20
21
|
:param n: float. Refractive index of the sensing fiber.
|
|
21
|
-
:paran
|
|
22
|
+
:paran guage_length: float. Gauge length.
|
|
22
23
|
:return: Strain data.
|
|
23
24
|
"""
|
|
24
|
-
return data *
|
|
25
|
+
return data * lam / (e * 4 * np.pi * n * gl)
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
def normalization(data, method='z-score'):
|
|
@@ -320,7 +321,7 @@ def padding(data, dn, reverse=False):
|
|
|
320
321
|
return data_pd
|
|
321
322
|
|
|
322
323
|
|
|
323
|
-
def time_integration(data, fs, c=0):
|
|
324
|
+
def time_integration(data, fs, domain='time', c=0):
|
|
324
325
|
"""
|
|
325
326
|
Integrate DAS data in time.
|
|
326
327
|
|
|
@@ -329,10 +330,19 @@ def time_integration(data, fs, c=0):
|
|
|
329
330
|
:param c: float. A constant added to the result.
|
|
330
331
|
:return: Integrated data.
|
|
331
332
|
"""
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
333
|
+
if domain == 'time':
|
|
334
|
+
return np.cumsum(data, axis=1) / fs + c
|
|
335
|
+
elif domain in ['frequency', 'freq']:
|
|
336
|
+
nsp = data.shape[1]
|
|
337
|
+
freqs = rfftfreq(nsp, d=1/fs)
|
|
338
|
+
spectrum = rfft(data, axis=1)
|
|
339
|
+
H = np.zeros_like(freqs, dtype=complex)
|
|
340
|
+
nonzero = freqs != 0
|
|
341
|
+
H[nonzero] = 1 / (1j * 2 * np.pi * freqs[nonzero])
|
|
342
|
+
return np.real(irfft(spectrum * H))
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def time_differential(data, fs, domain='time', prepend=0):
|
|
336
346
|
"""
|
|
337
347
|
Differentiate DAS data in time.
|
|
338
348
|
|
|
@@ -342,9 +352,16 @@ def time_differential(data, fs, prepend=0):
|
|
|
342
352
|
performing the difference.
|
|
343
353
|
:return: Differentiated data.
|
|
344
354
|
"""
|
|
345
|
-
if
|
|
346
|
-
prepend
|
|
347
|
-
|
|
355
|
+
if domain == 'time':
|
|
356
|
+
if prepend == 'mean':
|
|
357
|
+
prepend = np.mean(data, axis=1).reshape((-1, 1))
|
|
358
|
+
return np.diff(data, axis=1, prepend=prepend) * fs
|
|
359
|
+
elif domain in ['frequency', 'freq']:
|
|
360
|
+
nsp = data.shape[1]
|
|
361
|
+
freqs = rfftfreq(nsp, d=1./fs)
|
|
362
|
+
spectrum = rfft(data, axis=1)
|
|
363
|
+
H = 1j * 2 * np.pi * freqs
|
|
364
|
+
return np.real(irfft(spectrum * H))
|
|
348
365
|
|
|
349
366
|
|
|
350
367
|
def distance_integration(data, dx, c=0):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Purpose: Module for handling Collection objects.
|
|
2
2
|
# Author: Minzhe Hu
|
|
3
|
-
# Date: 2025.9.
|
|
3
|
+
# Date: 2025.9.18
|
|
4
4
|
# Email: hmz2018@mail.ustc.edu.cn
|
|
5
5
|
import os
|
|
6
6
|
import warnings
|
|
@@ -277,10 +277,11 @@ class Collection(object):
|
|
|
277
277
|
if len(flist) == 1:
|
|
278
278
|
sec = read(flist[0], tmin=tmin, tmax=tmax, **kwargs)
|
|
279
279
|
else:
|
|
280
|
-
sec = read(flist[0], tmin=tmin, **kwargs)
|
|
280
|
+
sec = read(flist[0], tmin=tmin-tolerance, **kwargs)
|
|
281
281
|
for f in flist[1:-1]:
|
|
282
282
|
sec += read(f, **kwargs)
|
|
283
|
-
sec += read(flist[-1], tmax=tmax, **kwargs)
|
|
283
|
+
sec += read(flist[-1], tmax=tmax+tolerance, **kwargs)
|
|
284
|
+
sec.trimming(tmin=tmin, tmax=tmax)
|
|
284
285
|
return sec
|
|
285
286
|
else:
|
|
286
287
|
self.flist = flist
|
|
@@ -326,8 +327,8 @@ class Collection(object):
|
|
|
326
327
|
kwargs_list[j]['zi'] = 0
|
|
327
328
|
|
|
328
329
|
def process(self, operations, savepath='./processed', merge=1,
|
|
329
|
-
suffix='_pro', ftype=None, dtype=None,
|
|
330
|
-
tolerance=0.5, **read_kwargs):
|
|
330
|
+
suffix='_pro', ftype=None, dtype=None, file_format='auto',
|
|
331
|
+
save_operations=False, tolerance=0.5, **read_kwargs):
|
|
331
332
|
"""
|
|
332
333
|
:param operations: list or None. Each element of operations list
|
|
333
334
|
should be [str of method name, dict of kwargs]. None for read
|
|
@@ -339,6 +340,8 @@ class Collection(object):
|
|
|
339
340
|
:param ftype: None or str. File format for saving. None for automatic
|
|
340
341
|
detection, or 'pkl', 'pickle', 'tdms', 'h5', 'hdf5', 'segy', 'sgy',
|
|
341
342
|
'npy'.
|
|
343
|
+
:param file_format: Format in which the file is saved.
|
|
344
|
+
:type file_format: str or function
|
|
342
345
|
:param dtype: str. The data type of the saved data.
|
|
343
346
|
:parma save_operations: bool. If True, save the operations to
|
|
344
347
|
method_list.pkl and kwargs_list.pkl in savepath.
|
|
@@ -369,7 +372,8 @@ class Collection(object):
|
|
|
369
372
|
warnings.warn(f'{f} is an empty file. Continuous data is '
|
|
370
373
|
'interrupted here.')
|
|
371
374
|
if m > 0:
|
|
372
|
-
sec_merge.save(filepath,
|
|
375
|
+
sec_merge.save(filepath, file_format=file_format,
|
|
376
|
+
dtype=dtype)
|
|
373
377
|
m = 0
|
|
374
378
|
self._kwargs_initialization(method_list, kwargs_list)
|
|
375
379
|
continue
|
|
@@ -377,7 +381,8 @@ class Collection(object):
|
|
|
377
381
|
sec = read(f, ftype=self.ftype, **read_kwargs)
|
|
378
382
|
if sec.data.size == 0:
|
|
379
383
|
if m > 0:
|
|
380
|
-
sec_merge.save(filepath,
|
|
384
|
+
sec_merge.save(filepath, ftype=ftype,
|
|
385
|
+
file_format=file_format, dtype=dtype)
|
|
381
386
|
m = 0
|
|
382
387
|
self._kwargs_initialization(method_list, kwargs_list)
|
|
383
388
|
continue
|
|
@@ -385,23 +390,24 @@ class Collection(object):
|
|
|
385
390
|
warnings.warn(f'Error reading {f}: {e}. Continuous data is '
|
|
386
391
|
'interrupted here.')
|
|
387
392
|
if m > 0:
|
|
388
|
-
sec_merge.save(filepath,
|
|
393
|
+
sec_merge.save(filepath, ftype=ftype,
|
|
394
|
+
file_format=file_format, dtype=dtype)
|
|
389
395
|
m = 0
|
|
390
396
|
self._kwargs_initialization(method_list, kwargs_list)
|
|
391
397
|
continue
|
|
392
398
|
for j, method in enumerate(method_list):
|
|
393
399
|
if method in ['taper', 'cosine_taper']:
|
|
394
400
|
if not ((i==0 and kwargs_list[j]['side'] != 'right') or
|
|
395
|
-
(i == len(self) - 1 and kwargs_list[j]['side']
|
|
396
|
-
|
|
401
|
+
(i == len(self) - 1 and kwargs_list[j]['side']
|
|
402
|
+
!= 'left')):
|
|
397
403
|
continue
|
|
398
404
|
out = getattr(sec, method)(**kwargs_list[j])
|
|
399
405
|
if method == 'time_integration':
|
|
400
406
|
kwargs_list[j]['c'] = sec.data[:, -1].copy()
|
|
401
407
|
elif method == 'time_differential':
|
|
402
408
|
kwargs_list[j]['prepend'] = sec.data[:, -1].copy()
|
|
403
|
-
elif method in ['bandpass', 'bandstop', 'lowpass',
|
|
404
|
-
'lowpass_cheby_2']:
|
|
409
|
+
elif method in ['bandpass', 'bandstop', 'lowpass',
|
|
410
|
+
'highpass', 'lowpass_cheby_2']:
|
|
405
411
|
kwargs_list[j]['zi'] = out
|
|
406
412
|
|
|
407
413
|
if m == 0:
|
|
@@ -415,7 +421,8 @@ class Collection(object):
|
|
|
415
421
|
warnings.warn(f'The start time of {f} does not correspond '
|
|
416
422
|
'to the end time of the previous file. '
|
|
417
423
|
'Continuous data is interrupted here.')
|
|
418
|
-
sec_merge.save(filepath,
|
|
424
|
+
sec_merge.save(filepath, ftype=ftype,
|
|
425
|
+
file_format=file_format, dtype=dtype)
|
|
419
426
|
sec_merge = sec
|
|
420
427
|
f0, f1 = os.path.splitext(os.path.basename(f))
|
|
421
428
|
f1 = f1 if ftype is None else ftype
|
|
@@ -423,10 +430,12 @@ class Collection(object):
|
|
|
423
430
|
m = 0
|
|
424
431
|
m += 1
|
|
425
432
|
if m == merge:
|
|
426
|
-
sec_merge.save(filepath,
|
|
433
|
+
sec_merge.save(filepath, ftype=ftype,
|
|
434
|
+
file_format=file_format, dtype=dtype)
|
|
427
435
|
m = 0
|
|
428
436
|
if m > 0:
|
|
429
|
-
sec_merge.save(filepath,
|
|
437
|
+
sec_merge.save(filepath, ftype=ftype, file_format=file_format,
|
|
438
|
+
dtype=dtype)
|
|
430
439
|
except KeyboardInterrupt as e:
|
|
431
440
|
with open(method_file, 'wb') as f:
|
|
432
441
|
pickle.dump(method_list, f)
|
|
@@ -452,25 +461,16 @@ class Collection(object):
|
|
|
452
461
|
def _create_cascade_method(method_name):
|
|
453
462
|
def cascade_method(self, savepath='./processed', merge=1,
|
|
454
463
|
suffix=f'_{method_name}', ftype=None, dtype=None,
|
|
455
|
-
save_operations=False,
|
|
464
|
+
file_format='auto', save_operations=False, tolerance=0.5,
|
|
465
|
+
**kwargs):
|
|
456
466
|
"""
|
|
457
467
|
Automatically generated method for {method_name}.
|
|
458
468
|
Applies the {method_name} operation to the data and saves the result.
|
|
459
|
-
|
|
460
|
-
:param savepath: str. Path to save processed files.
|
|
461
|
-
:param merge: int or str. int for merge several processed files into 1.
|
|
462
|
-
'all' for merge all files.
|
|
463
|
-
:param suffix: str. Suffix for processed files.
|
|
464
|
-
:param ftype: None or str. None for automatic detection, or 'pkl',
|
|
465
|
-
'pickle', 'tdms', 'h5', 'hdf5', 'segy', 'sgy', 'npy'.
|
|
466
|
-
:param dtype: str. The data type of the saved data.
|
|
467
|
-
:parma save_operations: bool. If True, save the operations to
|
|
468
|
-
method_list.pkl and kwargs_list.pkl in savepath.
|
|
469
|
-
:param kwargs: dict. Parameters for the {method_name} operation.
|
|
470
469
|
"""
|
|
471
470
|
operations = [[method_name, kwargs]]
|
|
472
471
|
self.process(operations, savepath=savepath, merge=merge, suffix=suffix,
|
|
473
|
-
ftype=ftype, dtype=dtype,
|
|
472
|
+
ftype=ftype, dtype=dtype, file_format=file_format,
|
|
473
|
+
save_operations=save_operations, tolerance=tolerance)
|
|
474
474
|
return cascade_method
|
|
475
475
|
|
|
476
476
|
|
|
@@ -84,7 +84,7 @@ class DASDateTime(datetime):
|
|
|
84
84
|
match = re.match(r'(.*)(UTC|GMT)([+-]?\d{1,2})(.*)', date_string,
|
|
85
85
|
re.IGNORECASE)
|
|
86
86
|
if match:
|
|
87
|
-
dt1,
|
|
87
|
+
dt1, _, offset, dt2 = match.groups()
|
|
88
88
|
offset_hours = int(offset)
|
|
89
89
|
tz = timezone(timedelta(hours=offset_hours))
|
|
90
90
|
return cls.strptime(dt1 + dt2, format.replace('%Z', '')).\
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import h5py
|
|
2
|
+
import numpy as np
|
|
3
|
+
from daspy import read, Section, DASDateTime
|
|
4
|
+
from daspy.core.dasdatetime import utc
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
origin_time = DASDateTime(2016, 3, 21, 7, 37, 10, 535000, tzinfo=utc)
|
|
8
|
+
|
|
9
|
+
# read DAS data
|
|
10
|
+
dx, fs = 1, 1000
|
|
11
|
+
sec = Section(np.zeros((8721, 0)), dx, fs, data_type='Strain rate')
|
|
12
|
+
filename = ['PoroTomo_iDAS16043_160321073651.h5',
|
|
13
|
+
'PoroTomo_iDAS16043_160321073721.h5',
|
|
14
|
+
'PoroTomo_iDAS16043_160321073751.h5',
|
|
15
|
+
'PoroTomo_iDAS16043_160321073821.h5']
|
|
16
|
+
|
|
17
|
+
first = True
|
|
18
|
+
for fn in filename:
|
|
19
|
+
with h5py.File(fn,'r') as fp:
|
|
20
|
+
if first:
|
|
21
|
+
sec.start_time = DASDateTime.fromtimestamp(fp['t'][0]).astimezone(utc)
|
|
22
|
+
sec.start_channel = fp['channel'][0]
|
|
23
|
+
first = False
|
|
24
|
+
sec += fp['das'][()].T
|
|
25
|
+
|
|
26
|
+
sec.origin_time = origin_time
|
|
27
|
+
sec.trimming(mode=0, xmin=2500, xmax=3000)
|
|
28
|
+
sec.trimming(tmin=origin_time, tmax=origin_time+90)
|
|
29
|
+
sec.downsampling(tint=10)
|
|
30
|
+
sec.trimming(tmin=origin_time+20, tmax=origin_time+70)
|
|
31
|
+
|
|
32
|
+
sec.save('example.py')
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# Purpose: Module for reading DAS data.
|
|
2
|
-
# Author: Minzhe Hu
|
|
3
|
-
# Date: 2025.
|
|
2
|
+
# Author: Minzhe Hu, Ji Zhang
|
|
3
|
+
# Date: 2025.11.19
|
|
4
4
|
# Email: hmz2018@mail.ustc.edu.cn
|
|
5
5
|
# Partially modified from
|
|
6
6
|
# https://github.com/RobbinLuo/das-toolkit/blob/main/DasTools/DasPrep.py
|
|
7
7
|
import warnings
|
|
8
|
+
import inspect
|
|
8
9
|
import json
|
|
9
10
|
import pickle
|
|
10
11
|
import numpy as np
|
|
11
12
|
import h5py
|
|
12
13
|
import segyio
|
|
14
|
+
from functools import wraps
|
|
13
15
|
from typing import Union
|
|
14
16
|
from pathlib import Path
|
|
15
17
|
from nptdms import TdmsFile
|
|
@@ -73,13 +75,8 @@ def read(fname=None, output_type='section', ftype=None, file_format='auto',
|
|
|
73
75
|
kwargs['chmin'] = kwargs.pop('ch1', None)
|
|
74
76
|
kwargs['chmax'] = kwargs.pop('ch2', None)
|
|
75
77
|
if callable(ftype):
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
except TypeError:
|
|
79
|
-
data, metadata = ftype(fname)
|
|
80
|
-
si, sj, metadata = _trimming_slice_metadata(data.shape,
|
|
81
|
-
metadata=metadata, **kwargs)
|
|
82
|
-
data = data[si, sj]
|
|
78
|
+
ftype = with_trimming(ftype)
|
|
79
|
+
data, metadata = ftype(fname, headonly=headonly, **kwargs)
|
|
83
80
|
else:
|
|
84
81
|
for rtp in [('pickle', 'pkl'), ('hdf5', 'h5'), ('segy', 'sgy')]:
|
|
85
82
|
ftype = ftype.replace(*rtp)
|
|
@@ -97,6 +94,43 @@ def read(fname=None, output_type='section', ftype=None, file_format='auto',
|
|
|
97
94
|
return data, metadata
|
|
98
95
|
|
|
99
96
|
|
|
97
|
+
def with_trimming(func):
|
|
98
|
+
"""
|
|
99
|
+
Decorator that wraps a custom reader so it automatically supports
|
|
100
|
+
trimming parameters (chmin, chmax, dch, xmin, xmax, tmin, tmax, spmin, spmax).
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
@wraps(func)
|
|
104
|
+
def wrapper(fname, headonly=False, **kwargs):
|
|
105
|
+
# trimming-related parameters
|
|
106
|
+
trim_keys = ['chmin', 'chmax', 'dch', 'xmin', 'xmax', 'tmin', 'tmax',
|
|
107
|
+
'spmin', 'spmax']
|
|
108
|
+
sig = inspect.signature(func)
|
|
109
|
+
reader_params = set(sig.parameters.keys())
|
|
110
|
+
trim_for_reader = {k: kwargs.pop(k) for k in trim_keys if k in kwargs \
|
|
111
|
+
and k in reader_params}
|
|
112
|
+
trim_for_trimming = {k: kwargs.pop(k) for k in trim_keys if k in \
|
|
113
|
+
kwargs and k not in reader_params}
|
|
114
|
+
try:
|
|
115
|
+
data, metadata = func(fname, headonly=headonly, **trim_for_reader,
|
|
116
|
+
**kwargs)
|
|
117
|
+
except TypeError:
|
|
118
|
+
headonly = False
|
|
119
|
+
data, metadata = func(fname, **trim_for_reader)
|
|
120
|
+
|
|
121
|
+
shape = data.shape
|
|
122
|
+
si, sj, metadata = _trimming_slice_metadata(shape, metadata=metadata,
|
|
123
|
+
**trim_for_trimming)
|
|
124
|
+
if headonly:
|
|
125
|
+
data = np.zeros(shape)[si, sj]
|
|
126
|
+
else:
|
|
127
|
+
data = data[si, sj]
|
|
128
|
+
|
|
129
|
+
return data, metadata
|
|
130
|
+
|
|
131
|
+
return wrapper
|
|
132
|
+
|
|
133
|
+
|
|
100
134
|
def _read_pkl(fname, headonly=False, file_format='auto', chmin=None, chmax=None,
|
|
101
135
|
dch=1, xmin=None, xmax=None, tmin=None, tmax=None, spmin=None,
|
|
102
136
|
spmax=None):
|
|
@@ -188,7 +222,8 @@ def _read_h5(fname, headonly=False, file_format='auto', chmin=None, chmax=None,
|
|
|
188
222
|
start_time = DASDateTime.fromisoformat(h5_file[data_key].
|
|
189
223
|
attrs['start_time'])
|
|
190
224
|
else:
|
|
191
|
-
start_time = DASDateTime.fromtimestamp(float(header[100]))
|
|
225
|
+
start_time = DASDateTime.fromtimestamp(float(header[100])).\
|
|
226
|
+
utc()
|
|
192
227
|
transpose = True
|
|
193
228
|
metadata = {'dx': header[1],
|
|
194
229
|
'fs': header[6] / header[15] / header[98],
|
|
@@ -216,13 +251,9 @@ def _read_h5(fname, headonly=False, file_format='auto', chmin=None, chmax=None,
|
|
|
216
251
|
fs = float(attrs['FreqRes'])
|
|
217
252
|
except KeyError:
|
|
218
253
|
try:
|
|
219
|
-
fs = (attrs['
|
|
220
|
-
attrs['SamplingRes'][0]) / 1000
|
|
254
|
+
fs = float(1000 / attrs['Spacing'][1])
|
|
221
255
|
except KeyError:
|
|
222
|
-
|
|
223
|
-
fs = 1000 / attrs['Spacing'][1]
|
|
224
|
-
except KeyError:
|
|
225
|
-
fs = attrs['SamplingRate'][0]
|
|
256
|
+
fs = attrs['PulseRateFreq'][0]
|
|
226
257
|
time = h5_file[f'{group}/Source1/time']
|
|
227
258
|
if len(time.shape) == 2: # Febus A1-R
|
|
228
259
|
start_time = DASDateTime.fromtimestamp(time[0, 0]).utc()
|
|
@@ -260,11 +291,9 @@ def _read_h5(fname, headonly=False, file_format='auto', chmin=None, chmax=None,
|
|
|
260
291
|
'Smart Earth ZD-DAS', 'Unknown']:
|
|
261
292
|
dataset = h5_file['Acquisition/Raw[0]/RawData/']
|
|
262
293
|
attrs = h5_file['Acquisition'].attrs
|
|
263
|
-
|
|
294
|
+
if 'NumberOfLoci' in attrs.keys():
|
|
264
295
|
if dataset.shape[0] != attrs['NumberOfLoci']:
|
|
265
296
|
transpose = True
|
|
266
|
-
except KeyError:
|
|
267
|
-
pass
|
|
268
297
|
|
|
269
298
|
try:
|
|
270
299
|
fs = h5_file['Acquisition/Raw[0]'].attrs['OutputDataRate']
|
|
@@ -282,7 +311,7 @@ def _read_h5(fname, headonly=False, file_format='auto', chmin=None, chmax=None,
|
|
|
282
311
|
stime = h5_file['Acquisition/Raw[0]/RawDataTime/'][0]
|
|
283
312
|
except KeyError:
|
|
284
313
|
stime = 0
|
|
285
|
-
|
|
314
|
+
|
|
286
315
|
if isinstance(stime, bytes):
|
|
287
316
|
stime = stime.decode('ascii')
|
|
288
317
|
|
|
@@ -295,6 +324,34 @@ def _read_h5(fname, headonly=False, file_format='auto', chmin=None, chmax=None,
|
|
|
295
324
|
'start_time': stime,
|
|
296
325
|
'gauge_length': attrs['GaugeLength']}
|
|
297
326
|
|
|
327
|
+
elif file_format == 'Puniu Tech HiFi-DAS':
|
|
328
|
+
dataset = h5_file['default']
|
|
329
|
+
if 'time,channel' in attrs.get('row_major_order', 'time, channel')\
|
|
330
|
+
.replace(' ', '').lower():
|
|
331
|
+
transpose = True
|
|
332
|
+
|
|
333
|
+
attrs = {k: (v.decode() if isinstance(v, bytes) else v) for k, v
|
|
334
|
+
in dataset.attrs.items()}
|
|
335
|
+
step = int(attrs['step'])
|
|
336
|
+
dx = step * attrs.get('spatial_sampling_rate', 1.0)
|
|
337
|
+
start_channel = int(attrs['start_channel'])
|
|
338
|
+
if step != 1:
|
|
339
|
+
if chmin:
|
|
340
|
+
chmin = (chmin - start_channel) / step + start_channel
|
|
341
|
+
if chmax:
|
|
342
|
+
chmax = (chmin - start_channel) / step + start_channel
|
|
343
|
+
t0 = int(attrs.get('epoch', 0)) + int(attrs.get('ns', 0)) * 1e-9
|
|
344
|
+
data_type = 'strain rate' if attrs.get('format', '') == \
|
|
345
|
+
'differential' else 'strain',
|
|
346
|
+
metadata = {'dx': dx, 'fs': float(attrs['sampling_rate']),
|
|
347
|
+
'start_channel': start_channel,
|
|
348
|
+
'start_distance': start_channel * dx,
|
|
349
|
+
'start_time': DASDateTime.fromtimestamp(t0, tz=utc),
|
|
350
|
+
'scale': 110.37, 'data_type': data_type,
|
|
351
|
+
'cid': attrs.get('cid', '')}
|
|
352
|
+
if 'spatial_resolution' in attrs.keys():
|
|
353
|
+
metadata['gauge_length'] = float(attrs['spatial_resolution'])
|
|
354
|
+
|
|
298
355
|
elif file_format == 'Silixa iDAS':
|
|
299
356
|
dataset = h5_file['Acquisition/Raw[0]/RawData/']
|
|
300
357
|
attrs = h5_file['Acquisition/Raw[0]'].attrs
|
|
@@ -361,13 +418,36 @@ def _read_h5(fname, headonly=False, file_format='auto', chmin=None, chmax=None,
|
|
|
361
418
|
metadata = {'dx': h5_file['Sampling_interval_in_space'][0],
|
|
362
419
|
'fs': 1 / h5_file['Sampling_interval_in_time'][0]}
|
|
363
420
|
|
|
421
|
+
elif file_format == 'NEC':
|
|
422
|
+
dataset = h5_file['data']
|
|
423
|
+
dx = dataset.attrs['Interval of monitor point']
|
|
424
|
+
fs = 1.0 / (dataset.attrs['Interval time of data'] / 1000.0) # Hz
|
|
425
|
+
if dataset.shape[0] != \
|
|
426
|
+
dataset.attrs['Number of requested location points']:
|
|
427
|
+
transpose = True
|
|
428
|
+
try:
|
|
429
|
+
scale = dataset.attrs['Radians peer digital value']
|
|
430
|
+
except KeyError:
|
|
431
|
+
try:
|
|
432
|
+
scale = dataset.attrs['Radians per digital value']
|
|
433
|
+
except KeyError:
|
|
434
|
+
scale = 1
|
|
435
|
+
# start_time = datetime(1970, 1, 1) + \
|
|
436
|
+
# timedelta(milliseconds=start_unix_epoch_in_ms)
|
|
437
|
+
start_time = DASDateTime.fromtimestamp(
|
|
438
|
+
np.float64(dataset.attrs['Time of sending request']) / 1e3
|
|
439
|
+
).utc()
|
|
440
|
+
metadata = {'fs': fs, 'dx': dx, 'start_time': start_time,
|
|
441
|
+
'gauge_length': dataset.attrs['Gauge length'],
|
|
442
|
+
'scale': scale, 'data_type':'strain rate'}
|
|
443
|
+
|
|
364
444
|
elif file_format == 'FORESEE':
|
|
365
445
|
dataset = h5_file['raw']
|
|
366
446
|
fs = round(1 / np.diff(h5_file['timestamp']).mean())
|
|
367
447
|
start_time = DASDateTime.fromtimestamp(
|
|
368
448
|
h5_file['timestamp'][0]).astimezone(utc)
|
|
369
|
-
warnings.warn('This data format doesn\'t include channel interval.
|
|
370
|
-
|
|
449
|
+
warnings.warn('This data format doesn\'t include channel interval.'
|
|
450
|
+
' Please set manually')
|
|
371
451
|
metadata = {'dx': None, 'fs': fs, 'start_time': start_time}
|
|
372
452
|
|
|
373
453
|
elif file_format == 'AI4EPS': # https://ai4eps.github.io/homepage/ml4earth/seismic_event_format_das/
|
|
@@ -415,11 +495,14 @@ def _read_h5(fname, headonly=False, file_format='auto', chmin=None, chmax=None,
|
|
|
415
495
|
elif transpose:
|
|
416
496
|
if len(dataset.shape) == 3:
|
|
417
497
|
fs = int(metadata['fs'])
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
498
|
+
fs_b = attrs.get('BlockRate', [1000])[0] / 1e3
|
|
499
|
+
nsp_b = round(fs/fs_b)
|
|
500
|
+
half_ol = round((dataset.shape[1] - nsp_b) / 2)
|
|
501
|
+
j0, k0 = divmod(sj.start, nsp_b)
|
|
502
|
+
j1, k1 = divmod(sj.stop, nsp_b)
|
|
503
|
+
k1 = (j1 - j0) * nsp_b + k1
|
|
421
504
|
j1 += 1
|
|
422
|
-
data = dataset[j0:j1,
|
|
505
|
+
data = dataset[j0:j1, half_ol:half_ol+nsp_b, si]
|
|
423
506
|
data = data.reshape((-1, data.shape[-1]))[k0:k1, :].T
|
|
424
507
|
else:
|
|
425
508
|
data = dataset[sj, si].T
|
|
@@ -444,9 +527,9 @@ def _read_tdms(fname, headonly=False, file_format='auto', chmin=None,
|
|
|
444
527
|
version = float(properties['iDASVersion'][:3])
|
|
445
528
|
if version < 2.3:
|
|
446
529
|
file_format = 'Silixa iDAS'
|
|
447
|
-
elif 2.3 <= version < 2.
|
|
530
|
+
elif 2.3 <= version < 2.7:
|
|
448
531
|
file_format = 'Silixa iDAS-v2'
|
|
449
|
-
elif version >= 2.
|
|
532
|
+
elif version >= 2.7:
|
|
450
533
|
file_format = 'Silixa iDAS-v3'
|
|
451
534
|
elif group_name == ['DAS']:
|
|
452
535
|
key = 'DAS'
|
|
@@ -483,12 +566,11 @@ def _read_tdms(fname, headonly=False, file_format='auto', chmin=None,
|
|
|
483
566
|
if isinstance(properties[time_key], str):
|
|
484
567
|
start_time = DASDateTime.fromisoformat(
|
|
485
568
|
properties[time_key])
|
|
569
|
+
break
|
|
486
570
|
elif isinstance(properties[time_key], np.datetime64):
|
|
487
571
|
start_time = DASDateTime.from_datetime(
|
|
488
572
|
properties[time_key].item())
|
|
489
|
-
|
|
490
|
-
continue
|
|
491
|
-
break
|
|
573
|
+
break
|
|
492
574
|
metadata['start_time'] = start_time
|
|
493
575
|
if 'GaugeLength' in properties.keys():
|
|
494
576
|
metadata['gauge_length'] = properties['GaugeLength']
|
|
@@ -503,7 +585,6 @@ def _read_tdms(fname, headonly=False, file_format='auto', chmin=None,
|
|
|
503
585
|
data = np.asarray([tdms_file[key][str(ch)][sj] for ch in
|
|
504
586
|
range(si.start, si.stop, si.step)])
|
|
505
587
|
elif file_format == 'Institute of Semiconductors, CAS':
|
|
506
|
-
|
|
507
588
|
try:
|
|
508
589
|
start_channel = int(properties['Initial Channel'])
|
|
509
590
|
except KeyError:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Purpose: Module for handling Section objects.
|
|
2
2
|
# Author: Minzhe Hu
|
|
3
|
-
# Date: 2025.
|
|
3
|
+
# Date: 2025.11.19
|
|
4
4
|
# Email: hmz2018@mail.ustc.edu.cn
|
|
5
5
|
import warnings
|
|
6
6
|
import os
|
|
@@ -21,7 +21,7 @@ from daspy.basic_tools.preprocessing import (phase2strain, normalization,
|
|
|
21
21
|
from daspy.basic_tools.filter import (bandpass, bandstop, lowpass,
|
|
22
22
|
lowpass_cheby_2, highpass, envelope)
|
|
23
23
|
from daspy.basic_tools.freqattributes import (spectrum, spectrogram, psd,
|
|
24
|
-
fk_transform)
|
|
24
|
+
fk_transform, power)
|
|
25
25
|
from daspy.advanced_tools.channel import channel_checking, turning_points
|
|
26
26
|
from daspy.advanced_tools.denoising import (curvelet_denoising,
|
|
27
27
|
common_mode_noise_removal,
|
|
@@ -68,6 +68,7 @@ class Section(object):
|
|
|
68
68
|
opt_attrs = ['origin_time', 'gauge_length', 'data_type', 'scale',
|
|
69
69
|
'geometry', 'turning_channels', 'headers', 'source',
|
|
70
70
|
'source_type', 'file_format']
|
|
71
|
+
kwargs.setdefault('scale', 1)
|
|
71
72
|
for attr in opt_attrs:
|
|
72
73
|
if attr in kwargs:
|
|
73
74
|
setattr(self, attr, kwargs.pop(attr))
|
|
@@ -150,15 +151,15 @@ class Section(object):
|
|
|
150
151
|
else:
|
|
151
152
|
raise TypeError('The input should be Section or np.ndarray.')
|
|
152
153
|
|
|
153
|
-
if
|
|
154
|
-
if len(data[0]) == self.nch:
|
|
155
|
-
data = data.T
|
|
156
|
-
else:
|
|
157
|
-
raise ValueError('These two Sections have different number of '
|
|
158
|
-
'channels, please check.')
|
|
159
|
-
if out.data is None:
|
|
154
|
+
if (out.data is None) or (out.nch * out.nsp == 0):
|
|
160
155
|
out.data = data
|
|
161
156
|
else:
|
|
157
|
+
if len(data) != self.nch:
|
|
158
|
+
if len(data[0]) == self.nch:
|
|
159
|
+
data = data.T
|
|
160
|
+
else:
|
|
161
|
+
raise ValueError('These two Sections have different number '
|
|
162
|
+
'of channels, please check.')
|
|
162
163
|
out.data = np.hstack((out.data, data))
|
|
163
164
|
|
|
164
165
|
return out
|
|
@@ -548,7 +549,7 @@ class Section(object):
|
|
|
548
549
|
self.start_distance += channel[0] * self.dx
|
|
549
550
|
return self
|
|
550
551
|
else:
|
|
551
|
-
return data
|
|
552
|
+
return data * self.scale
|
|
552
553
|
|
|
553
554
|
def plot(self, xmode='distance', tmode='origin', obj='waveform',
|
|
554
555
|
kwargs_pro={}, **kwargs):
|
|
@@ -645,7 +646,10 @@ class Section(object):
|
|
|
645
646
|
if tmode == 'origin':
|
|
646
647
|
if hasattr(self, 'origin_time'):
|
|
647
648
|
kwargs['t0'] -= self.origin_time
|
|
648
|
-
|
|
649
|
+
if ('transpose' in kwargs.keys()) and kwargs['transpose']:
|
|
650
|
+
kwargs.setdefault('xlabel', 'Times(s) after occurance')
|
|
651
|
+
else:
|
|
652
|
+
kwargs.setdefault('ylabel', 'Times(s) after occurance')
|
|
649
653
|
else:
|
|
650
654
|
tmode == 'start'
|
|
651
655
|
if tmode == 'start':
|
|
@@ -765,18 +769,26 @@ class Section(object):
|
|
|
765
769
|
self.data = cosine_taper(self.data, p=p, side=side)
|
|
766
770
|
return self
|
|
767
771
|
|
|
768
|
-
def downsampling(self, xint=None, tint=None, stack=True,
|
|
772
|
+
def downsampling(self, xint=None, tint=None, fs=None, dx=None, stack=True,
|
|
769
773
|
lowpass_filter=True):
|
|
770
774
|
"""
|
|
771
775
|
Downsample DAS data.
|
|
772
776
|
|
|
773
777
|
:param xint: int. Spatial downsampling factor.
|
|
774
778
|
:param tint: int. Time downsampling factor.
|
|
779
|
+
:param fs: float. Target sampling rate after downsampling. It is used
|
|
780
|
+
if tint is None.
|
|
781
|
+
:param dx: float. Target channel interval after downsampling. It is
|
|
782
|
+
used if xint is None.
|
|
775
783
|
:param stack: bool. If True, stacking will replace decimation.
|
|
776
784
|
:param lowpass_filter: bool. Lowpass cheby2 filter before time
|
|
777
785
|
downsampling or not.
|
|
778
786
|
:return: Downsampled data.
|
|
779
787
|
"""
|
|
788
|
+
if xint is None and dx is not None:
|
|
789
|
+
xint = round(dx / self.dx)
|
|
790
|
+
if tint is None and fs is not None:
|
|
791
|
+
tint = round(self.fs / fs)
|
|
780
792
|
self.data = downsampling(self.data, xint=xint, tint=tint, stack=stack,
|
|
781
793
|
lowpass_filter=lowpass_filter)
|
|
782
794
|
if xint and xint > 1:
|
|
@@ -851,25 +863,26 @@ class Section(object):
|
|
|
851
863
|
return self
|
|
852
864
|
warnings.warn('Unable to convert data type.')
|
|
853
865
|
|
|
854
|
-
def time_integration(self, c=0):
|
|
866
|
+
def time_integration(self, domain='time', c=0):
|
|
855
867
|
"""
|
|
856
868
|
Integrate DAS data in time.
|
|
857
869
|
|
|
858
870
|
:param c: float. A constant added to the result.
|
|
859
871
|
"""
|
|
860
|
-
self.data = time_integration(self.data, self.fs, c=c)
|
|
872
|
+
self.data = time_integration(self.data, self.fs, domain=domain, c=c)
|
|
861
873
|
if hasattr(self, 'data_type'):
|
|
862
874
|
self._time_int_dif_attr(mode=1)
|
|
863
875
|
return self
|
|
864
876
|
|
|
865
|
-
def time_differential(self, prepend=0):
|
|
877
|
+
def time_differential(self, domain='time', prepend=0):
|
|
866
878
|
"""
|
|
867
879
|
Differentiate DAS data in time.
|
|
868
880
|
|
|
869
|
-
:param prepend: 'mean' or values to prepend to `data` along axis prior
|
|
870
|
-
performing the difference.
|
|
881
|
+
:param prepend: 'mean' or values to prepend to `data` along axis prior
|
|
882
|
+
to performing the difference.
|
|
871
883
|
"""
|
|
872
|
-
self.data = time_differential(self.data, self.fs,
|
|
884
|
+
self.data = time_differential(self.data, self.fs, domain=domain,
|
|
885
|
+
prepend=prepend)
|
|
873
886
|
if hasattr(self, 'data_type'):
|
|
874
887
|
self._time_int_dif_attr(mode=-1)
|
|
875
888
|
return self
|
|
@@ -901,7 +914,8 @@ class Section(object):
|
|
|
901
914
|
None.
|
|
902
915
|
"""
|
|
903
916
|
if zi is None:
|
|
904
|
-
self.data = bandpass(self.data, self.fs, freqmin, freqmax,
|
|
917
|
+
self.data = bandpass(self.data, self.fs, freqmin, freqmax,
|
|
918
|
+
**kwargs)
|
|
905
919
|
return self
|
|
906
920
|
else:
|
|
907
921
|
self.data, zf = bandpass(self.data, self.fs, freqmin, freqmax,
|
|
@@ -1119,6 +1133,14 @@ class Section(object):
|
|
|
1119
1133
|
"""
|
|
1120
1134
|
return fk_transform(self.data, self.dx, self.fs, **kwargs)
|
|
1121
1135
|
|
|
1136
|
+
def power(self):
|
|
1137
|
+
"""
|
|
1138
|
+
Calculate the power of each channel.
|
|
1139
|
+
|
|
1140
|
+
:return: numpy.ndarray. Power of each channel.
|
|
1141
|
+
"""
|
|
1142
|
+
return power(self.data)
|
|
1143
|
+
|
|
1122
1144
|
def channel_checking(self, use=False, **kwargs):
|
|
1123
1145
|
"""
|
|
1124
1146
|
Use the energy of each channel to determine which channels are bad.
|
|
@@ -21,13 +21,16 @@ def _device_standardized_name(file_format: str) -> str:
|
|
|
21
21
|
'OptaSense ODH4+': ['optasenseodh4+', 'odh4+', 'optasenseodh4plus',
|
|
22
22
|
'odh4plus'],
|
|
23
23
|
'OptaSense QuantX': ['optasensequantx', 'quantx'],
|
|
24
|
+
'Puniu Tech HiFi-DAS': ['puniutechhifidas, puniu, puniutech, hifidas',
|
|
25
|
+
'puniuhifidas', 'puniudas'],
|
|
24
26
|
'Silixa iDAS': ['silixaidas', 'silixaidasv1', 'idasv1', 'idas'],
|
|
25
27
|
'Silixa iDAS-v2': ['silixaidasv2', 'idasv2'],
|
|
26
28
|
'Silixa iDAS-v3': ['silixaidasv3', 'idasv3'],
|
|
27
29
|
'Silixa iDAS-MG': ['silixaidasmg', 'idasmg'],
|
|
28
30
|
'Silixa Carina': ['silixacarina', 'carina'],
|
|
29
|
-
'Sintela Onyx v1.0': ['sintelaonyxv1.0', 'sintelaonyxv1',
|
|
30
|
-
'sintela', 'onyxv1.0', 'onyxv1',
|
|
31
|
+
'Sintela Onyx v1.0': ['sintelaonyxv1.0', 'sintelaonyxv1',
|
|
32
|
+
'sintalaonyx', 'sintela', 'onyxv1.0', 'onyxv1',
|
|
33
|
+
'onyx'],
|
|
31
34
|
'T8 Sensor': ['t8sensor', 't8'],
|
|
32
35
|
'Smart Earth ZD-DAS': ['smartearthzddas', 'smartearth', 'zddas',
|
|
33
36
|
'smartearthsensingzddas', 'smartearthsensing',
|
|
@@ -37,7 +40,9 @@ def _device_standardized_name(file_format: str) -> str:
|
|
|
37
40
|
'instituteofsemiconductorscas'],
|
|
38
41
|
'AI4EPS': ['ai4eps', 'daseventdata'],
|
|
39
42
|
'INGV': ['ingv', 'istitutonazionaledigeofisicaevulcanologia'],
|
|
40
|
-
'JAMSTEC': ['jamstec',
|
|
43
|
+
'JAMSTEC': ['jamstec',
|
|
44
|
+
'japanagencyformarineearthscienceandtechnology'],
|
|
45
|
+
'NEC': ['nec', 'nipponelectriccompany'],
|
|
41
46
|
'FORESEE': ['forsee', 'fiberopticforenvironmentsenseing'],
|
|
42
47
|
'Unknown0': ['unknown0'],
|
|
43
48
|
'Unknown': ['unknown', 'other']
|
|
@@ -88,16 +93,22 @@ def _h5_file_format(h5_file):
|
|
|
88
93
|
file_format = 'Sintela Onyx v1.0'
|
|
89
94
|
except KeyError:
|
|
90
95
|
pass
|
|
96
|
+
elif 'default' in keys:
|
|
97
|
+
file_format = 'Puniu Tech HiFi-DAS'
|
|
91
98
|
elif set(keys) == {'Mapping', 'Acquisition'}:
|
|
92
99
|
file_format = 'Silixa iDAS'
|
|
93
|
-
elif list(keys) == ['data']:
|
|
94
|
-
file_format == 'AI4EPS'
|
|
95
100
|
elif set(keys) == {'ChannelMap', 'Fiber', 'cm', 't', 'x'}:
|
|
96
101
|
file_format = 'INGV'
|
|
97
102
|
elif set(keys) == {'DAS_record', 'Sampling_interval_in_space',
|
|
98
103
|
'Sampling_interval_in_time', 'Sampling_points_in_space',
|
|
99
104
|
'Sampling_points_in_time'}:
|
|
100
105
|
file_format = 'JAMSTEC'
|
|
106
|
+
elif list(keys) == ['data']:
|
|
107
|
+
if 'Interval of monitor point' in \
|
|
108
|
+
list(h5_file['data'].attrs['Interval of monitor point'].keys()):
|
|
109
|
+
file_format = 'NEC'
|
|
110
|
+
else:
|
|
111
|
+
file_format = 'AI4EPS'
|
|
101
112
|
elif set(keys) == {'raw', 'timestamp'}:
|
|
102
113
|
file_format = 'FORESEE'
|
|
103
114
|
elif list(keys) == ['ProcessedData']:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Purpose: Module for writing DAS data.
|
|
2
2
|
# Author: Minzhe Hu
|
|
3
|
-
# Date: 2025.
|
|
3
|
+
# Date: 2025.11.19
|
|
4
4
|
# Email: hmz2018@mail.ustc.edu.cn
|
|
5
5
|
import os
|
|
6
6
|
import warnings
|
|
@@ -168,7 +168,6 @@ def _write_h5(sec, fname, raw_fname=None, file_format='auto'):
|
|
|
168
168
|
start_time = sec.start_time.utc() if \
|
|
169
169
|
isinstance(sec.start_time, datetime) else \
|
|
170
170
|
DASDateTime.fromtimestamp(sec.start_time)
|
|
171
|
-
|
|
172
171
|
if file_format in ['OptaSense ODH4+', 'OptaSense QuantX',
|
|
173
172
|
'Unknown']:
|
|
174
173
|
h5_file.get('Acquisition/Raw[0]/').create_dataset('RawData',
|
|
@@ -186,15 +185,46 @@ def _write_h5(sec, fname, raw_fname=None, file_format='auto'):
|
|
|
186
185
|
attrs['PartStartTime'] = stime_str
|
|
187
186
|
h5_file['Acquisition'].attrs['MeasurementStartTime'] = \
|
|
188
187
|
stime_str
|
|
188
|
+
h5_file['Acquisition'].attrs['NumberOfLoci'] = sec.nch
|
|
189
189
|
stimestamp = start_time.timestamp()
|
|
190
190
|
datatime = (np.arange(stimestamp, stimestamp + sec.nsp /
|
|
191
191
|
sec.fs, 1 / sec.fs) * 1e6).astype(int)
|
|
192
192
|
h5_file.get('Acquisition/Raw[0]/').\
|
|
193
193
|
create_dataset('RawDataTime', data=datatime)
|
|
194
194
|
h5_file['Acquisition/Raw[0]'].attrs['OutputDataRate'] = sec.fs
|
|
195
|
-
h5_file['Acquisition'].attrs['SpatialSamplingInterval'] =
|
|
196
|
-
|
|
197
|
-
|
|
195
|
+
h5_file['Acquisition'].attrs['SpatialSamplingInterval'] = \
|
|
196
|
+
sec.dx
|
|
197
|
+
h5_file['Acquisition'].attrs['GaugeLength'] = \
|
|
198
|
+
sec.gauge_length if hasattr(sec, 'gauge_length') else -1
|
|
199
|
+
|
|
200
|
+
elif file_format == 'Puniu Tech HiFi-DAS':
|
|
201
|
+
h5_file.create_dataset('default', data=sec.data)
|
|
202
|
+
h5_file['default'].attrs['row_major_order'] = 'channel, time'
|
|
203
|
+
h5_file['default'].attrs['spatial_sampling_rate'] = sec.dx
|
|
204
|
+
h5_file['default'].attrs['step'] = 1
|
|
205
|
+
h5_file['default'].attrs['sampling_rate'] = sec.fs
|
|
206
|
+
h5_file['default'].attrs['start_channel'] = sec.start_channel
|
|
207
|
+
if isinstance(sec.start_time, datetime):
|
|
208
|
+
t0 = sec.start_time.timestamp()
|
|
209
|
+
else:
|
|
210
|
+
t0 = sec.start_time
|
|
211
|
+
epoch = int(t0)
|
|
212
|
+
h5_file['default'].attrs['epoch'] = epoch
|
|
213
|
+
h5_file['default'].attrs['ns'] = int(round((t0 - epoch) * 1e9))
|
|
214
|
+
if hasattr(sec, 'data_type'):
|
|
215
|
+
h5_file['default'].attrs['format'] = 'differential' if \
|
|
216
|
+
sec.data_type == 'strain rate' else sec.data_type
|
|
217
|
+
else:
|
|
218
|
+
h5_file['default'].attrs['format'] = 'unknown'
|
|
219
|
+
|
|
220
|
+
if hasattr(sec, 'gauge_length'):
|
|
221
|
+
h5_file['default'].attrs['spatial_resolution'] = \
|
|
222
|
+
sec.gauge_length
|
|
223
|
+
try:
|
|
224
|
+
h5_file['default'].attrs['cid'] = \
|
|
225
|
+
sec.headers['default']['attrs']['cid']
|
|
226
|
+
except KeyError:
|
|
227
|
+
h5_file['default'].attrs['cid'] = 'unknown'
|
|
198
228
|
|
|
199
229
|
elif file_format == 'Silixa iDAS':
|
|
200
230
|
h5_file.create_group('Acquisition/Raw[0]')
|
|
@@ -273,7 +303,6 @@ def _write_h5(sec, fname, raw_fname=None, file_format='auto'):
|
|
|
273
303
|
h5_file.attrs['FilterGain'] = 116e-9 * sec.fs / gauge_length / \
|
|
274
304
|
8192 / scale
|
|
275
305
|
|
|
276
|
-
|
|
277
306
|
elif file_format in 'JAMSTEC':
|
|
278
307
|
h5_file.create_dataset('DAS_record', data=sec.data)
|
|
279
308
|
h5_file.create_dataset('Sampling_interval_in_space',
|
|
@@ -281,6 +310,23 @@ def _write_h5(sec, fname, raw_fname=None, file_format='auto'):
|
|
|
281
310
|
h5_file.create_dataset('Sampling_interval_in_time',
|
|
282
311
|
data=[1/sec.fs])
|
|
283
312
|
|
|
313
|
+
elif file_format == 'NEC':
|
|
314
|
+
h5_file.create_dataset('data', data=sec.data)
|
|
315
|
+
h5_file['data'].attrs['Interval of monitor point'] = sec.dx
|
|
316
|
+
h5_file['data'].attrs['Number of requested location points'] \
|
|
317
|
+
= sec.nch
|
|
318
|
+
h5_file['data'].attrs['Interval time of data'] = \
|
|
319
|
+
1 / sec.fs * 1e3
|
|
320
|
+
h5_file['data'].attrs['Radians per digital value'] = \
|
|
321
|
+
sec.scale if hasattr(sec, 'scale') else 1
|
|
322
|
+
if isinstance(sec.start_time, datatime):
|
|
323
|
+
t0 = sec.start_time.timestamp()
|
|
324
|
+
else:
|
|
325
|
+
t0 = sec.start_time
|
|
326
|
+
h5_file['data'].attrs['Time of sending request'] = t0 * 1e3
|
|
327
|
+
h5_file['data'].attrs['Gauge length'] = sec.gauge_length if \
|
|
328
|
+
hasattr(sec, 'gauge_length') else -1
|
|
329
|
+
|
|
284
330
|
elif file_format == 'FORESEE':
|
|
285
331
|
h5_file.create_dataset('raw', data=sec.data)
|
|
286
332
|
start_time = sec.start_time.timestamp() if \
|
|
@@ -434,9 +480,14 @@ def _write_h5(sec, fname, raw_fname=None, file_format='auto'):
|
|
|
434
480
|
elif file_format in ['OptaSense ODH4+', 'OptaSense QuantX',
|
|
435
481
|
'Silixa iDAS-MG', 'Sintela Onyx v1.0',
|
|
436
482
|
'Smart Earth ZD-DAS', 'Unknown']:
|
|
483
|
+
if file_format in ['Silixa iDAS-MG', 'Sintela Onyx v1.0',
|
|
484
|
+
'Smart Earth ZD-DAS']:
|
|
485
|
+
_update_h5_dataset(h5_file, 'Acquisition/Raw[0]/',
|
|
486
|
+
'RawData', sec.data.T)
|
|
487
|
+
else:
|
|
488
|
+
_update_h5_dataset(h5_file, 'Acquisition/Raw[0]/',
|
|
489
|
+
'RawData', sec.data)
|
|
437
490
|
h5_file['Acquisition'].attrs['NumberOfLoci'] = sec.nch
|
|
438
|
-
_update_h5_dataset(h5_file, 'Acquisition/Raw[0]/', 'RawData',
|
|
439
|
-
sec.data)
|
|
440
491
|
if isinstance(sec.start_time, datetime):
|
|
441
492
|
if isinstance(h5_file['Acquisition/Raw[0]/RawData'].
|
|
442
493
|
attrs['PartStartTime'], bytes):
|
|
@@ -458,9 +509,42 @@ def _write_h5(sec, fname, raw_fname=None, file_format='auto'):
|
|
|
458
509
|
_update_h5_dataset(h5_file, 'Acquisition/Raw[0]/',
|
|
459
510
|
'RawDataTime', DataTime)
|
|
460
511
|
h5_file['Acquisition/Raw[0]'].attrs['OutputDataRate'] = sec.fs
|
|
461
|
-
h5_file['Acquisition'].attrs['SpatialSamplingInterval'] =
|
|
462
|
-
|
|
463
|
-
|
|
512
|
+
h5_file['Acquisition'].attrs['SpatialSamplingInterval'] = \
|
|
513
|
+
sec.dx
|
|
514
|
+
h5_file['Acquisition'].attrs['GaugeLength'] = \
|
|
515
|
+
sec.gauge_length if hasattr(sec, 'gauge_length') else -1
|
|
516
|
+
|
|
517
|
+
elif file_format == 'Puniu Tech HiFi-DAS':
|
|
518
|
+
attrs = {k: (v.decode() if isinstance(v, bytes) else v) for k,
|
|
519
|
+
v in h5_file['default'].attrs.items()}
|
|
520
|
+
if 'time,channel' in attrs.get('row_major_order',
|
|
521
|
+
'time, channel').replace(' ', '').lower():
|
|
522
|
+
_update_h5_dataset(h5_file, '/', 'default', sec.data.T)
|
|
523
|
+
else:
|
|
524
|
+
_update_h5_dataset(h5_file, '/', 'default', sec.data)
|
|
525
|
+
|
|
526
|
+
h5_file['default'].attrs['spatial_sampling_rate'] = sec.dx / \
|
|
527
|
+
attrs['step']
|
|
528
|
+
h5_file['default'].attrs['sampling_rate'] = sec.fs
|
|
529
|
+
h5_file['default'].attrs['start_channel'] = sec.start_channel
|
|
530
|
+
if isinstance(sec.start_time, datetime):
|
|
531
|
+
t0 = sec.start_time.timestamp()
|
|
532
|
+
else:
|
|
533
|
+
t0 = sec.start_time
|
|
534
|
+
epoch = int(t0)
|
|
535
|
+
h5_file['default'].attrs['epoch'] = epoch
|
|
536
|
+
h5_file['default'].attrs['ns'] = int(round((t0 - epoch) * 1e9))
|
|
537
|
+
if hasattr(sec, 'data_type'):
|
|
538
|
+
h5_file['default'].attrs['format'] = 'differential' if \
|
|
539
|
+
sec.data_type == 'strain rate' else sec.data_type
|
|
540
|
+
if hasattr(sec, 'gauge_length'):
|
|
541
|
+
h5_file['default'].attrs['spatial_resolution'] = \
|
|
542
|
+
sec.gauge_length
|
|
543
|
+
try:
|
|
544
|
+
h5_file['default'].attrs['cid'] = \
|
|
545
|
+
sec.headers['default']['attrs']['cid']
|
|
546
|
+
except KeyError:
|
|
547
|
+
pass
|
|
464
548
|
|
|
465
549
|
elif file_format == 'Silixa iDAS':
|
|
466
550
|
if h5_file['Acquisition/Raw[0]/RawData/'].shape[0] != \
|
|
@@ -533,6 +617,24 @@ def _write_h5(sec, fname, raw_fname=None, file_format='auto'):
|
|
|
533
617
|
_update_h5_dataset(h5_file, '/', 'Sampling_interval_in_time',
|
|
534
618
|
[1/sec.fs])
|
|
535
619
|
|
|
620
|
+
elif file_format == 'NEC':
|
|
621
|
+
_update_h5_dataset(h5_file, '/', 'data', sec.data)
|
|
622
|
+
h5_file['data'].attrs['Interval of monitor point'] = sec.dx
|
|
623
|
+
h5_file['data'].attrs['Number of requested location points'] \
|
|
624
|
+
= sec.nch
|
|
625
|
+
h5_file['data'].attrs['Interval time of data'] = \
|
|
626
|
+
1 / sec.fs * 1e3
|
|
627
|
+
if hasattr(sec, scale):
|
|
628
|
+
h5_file['data'].attrs['Radians per digital value'] = \
|
|
629
|
+
sec.scale
|
|
630
|
+
if isinstance(sec.start_time, datatime):
|
|
631
|
+
t0 = sec.start_time.timestamp()
|
|
632
|
+
else:
|
|
633
|
+
t0 = sec.start_time
|
|
634
|
+
h5_file['data'].attrs['Time of sending request'] = t0 * 1e3
|
|
635
|
+
if hasattr(sec, 'gauge_length'):
|
|
636
|
+
h5_file['data'].attrs['Gauge length'] = sec.gauge_length
|
|
637
|
+
|
|
536
638
|
elif file_format == 'FORESEE':
|
|
537
639
|
_update_h5_dataset(h5_file, '/', 'raw', sec.data)
|
|
538
640
|
DataTime = sec.start_time.timestamp() + \
|
|
@@ -2,14 +2,14 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
setup(
|
|
5
|
-
name='DASPy-toolbox', version='1.2.
|
|
5
|
+
name='DASPy-toolbox', version='1.2.2',
|
|
6
6
|
description=(
|
|
7
7
|
'DASPy is an open-source project dedicated to provide a python package '
|
|
8
8
|
'for DAS (Distributed Acoustic Sensing) data processing, which '
|
|
9
9
|
'comprises classic seismic data processing techniques and Specialized '
|
|
10
10
|
'algorithms for DAS applications.'
|
|
11
11
|
),
|
|
12
|
-
long_description=open('README.md').read(),
|
|
12
|
+
long_description=open('README.md', encoding="utf-8").read(),
|
|
13
13
|
author='Minzhe Hu, Zefeng Li',
|
|
14
14
|
author_email='hmz2018@mail.ustc.edu.cn',
|
|
15
15
|
maintainer='Minzhe Hu',
|
|
@@ -17,7 +17,7 @@ setup(
|
|
|
17
17
|
license='MIT License',
|
|
18
18
|
url='https://github.com/HMZ-03/DASPy',
|
|
19
19
|
packages=find_packages(),
|
|
20
|
-
entry_points={
|
|
20
|
+
entry_points={
|
|
21
21
|
'console_scripts': [
|
|
22
22
|
'daspy = daspy.main:main',
|
|
23
23
|
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|