DASPy-toolbox 1.2.1__tar.gz → 1.2.3__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.
Files changed (32) hide show
  1. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/DASPy_toolbox.egg-info/PKG-INFO +1 -1
  2. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/PKG-INFO +1 -1
  3. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/advanced_tools/channel.py +3 -2
  4. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/advanced_tools/strain2vel.py +19 -17
  5. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/basic_tools/freqattributes.py +11 -1
  6. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/basic_tools/preprocessing.py +4 -4
  7. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/basic_tools/visualization.py +10 -9
  8. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/core/collection.py +31 -9
  9. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/core/dasdatetime.py +2 -2
  10. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/core/read.py +122 -14
  11. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/core/section.py +23 -11
  12. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/core/util.py +16 -5
  13. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/core/write.py +108 -9
  14. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/setup.py +1 -1
  15. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/DASPy_toolbox.egg-info/SOURCES.txt +0 -0
  16. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/DASPy_toolbox.egg-info/dependency_links.txt +0 -0
  17. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/DASPy_toolbox.egg-info/entry_points.txt +0 -0
  18. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/DASPy_toolbox.egg-info/requires.txt +0 -0
  19. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/DASPy_toolbox.egg-info/top_level.txt +0 -0
  20. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/LICENSE +0 -0
  21. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/README.md +0 -0
  22. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/__init__.py +0 -0
  23. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/advanced_tools/__init__.py +0 -0
  24. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/advanced_tools/decomposition.py +0 -0
  25. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/advanced_tools/denoising.py +0 -0
  26. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/advanced_tools/fdct.py +0 -0
  27. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/basic_tools/__init__.py +0 -0
  28. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/basic_tools/filter.py +0 -0
  29. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/core/__init__.py +0 -0
  30. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/core/example.pkl +0 -0
  31. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/daspy/core/make_example.py +0 -0
  32. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DASPy-toolbox
3
- Version: 1.2.1
3
+ Version: 1.2.3
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.1
3
+ Version: 1.2.3
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
@@ -181,7 +181,7 @@ def _channel_location(track_pt):
181
181
  return np.array(seg_interval), np.array(interp_ch)
182
182
 
183
183
 
184
- def location_interpolation(known_pt, track_pt=None, dx=2, data_type='lonlat',
184
+ def location_interpolation(known_pt, track_pt=None, dx=1, data_type='lonlat',
185
185
  verbose=False):
186
186
  """
187
187
  Interpolate to obtain the positions of all channels.
@@ -209,7 +209,8 @@ def location_interpolation(known_pt, track_pt=None, dx=2, data_type='lonlat',
209
209
  .astype(int) + 31
210
210
  DASProj = Proj(proj='utm', zone=zone, ellps='WGS84',
211
211
  preserve_units=False)
212
- known_pt[:, 0], known_pt[:, 1] = DASProj(known_pt[:, 0], known_pt[:, 1])
212
+ known_pt[:, 0], known_pt[:, 1] = DASProj(known_pt[:, 0],
213
+ known_pt[:, 1])
213
214
  else:
214
215
  assert 'xy' in data_type, ('data_type should be \'lonlat\',\''
215
216
  'lonlatheight\', \'xy\' or \'xyz\'')
@@ -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 to
27
- 1). Default is 0.1 which tapers 5% from the beginning and 5% from the
28
- end.
29
- :param pad: Pad the data or not. It can be float or sequence of floats. Each
30
- float means padding percentage before FFT for corresponding dimension.
31
- If set to 0.1 will pad 5% before the beginning and after the end.
32
- 'default' means pad both dimensions to next power of 2. None or False
33
- means don't pad data before or during Fast Fourier Transform.
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) * vv
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 percentage
95
- before FFT for corresponding dimension. If set to 0.1 will pad 5% before
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 level.
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, turning=None)
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, sls=sls,
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: 2024.6.17
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))
@@ -19,10 +19,10 @@ def phase2strain(data, lam, e, n, gl):
19
19
  :param e: float. photo-slastic scaling factor for logitudinal strain in
20
20
  isotropic material.
21
21
  :param n: float. Refractive index of the sensing fiber.
22
- :paran gl: float. Gauge length.
22
+ :paran guage_length: float. Gauge length.
23
23
  :return: Strain data.
24
24
  """
25
- return data * (lam * 1e-9) / (e * 4 * np.pi * n * gl)
25
+ return data * lam / (e * 4 * np.pi * n * gl)
26
26
 
27
27
 
28
28
  def normalization(data, method='z-score'):
@@ -121,9 +121,9 @@ def cosine_taper(data, p=0.1, side='both'):
121
121
  if not isinstance(p, (tuple, list, np.ndarray)):
122
122
  win = tukey(nsp, p)
123
123
  if side == 'left':
124
- win[round(nch/2):] = 1
124
+ win[round(nsp/2):] = 1
125
125
  elif side == 'right':
126
- win[:round(len(win)/2)] = 1
126
+ win[:round(nsp/2)] = 1
127
127
  return data * np.tile(win, (nch, 1))
128
128
  else:
129
129
  if p[0] > 0:
@@ -11,9 +11,9 @@ def plot(data: np.ndarray, dx=None, fs=None, ax=None, obj='waveform', dpi=300,
11
11
  title=None, transpose=False, t0=0, x0=0, pick=None, f=None, k=None,
12
12
  t=None, c=None, cmap=None, vmin=None, vmin_per=None, vmax=None,
13
13
  vmax_per=None, dB=False, xmode='distance', tmode='time', xlim=None,
14
- ylim=None, xlog=False, ylog=False, xinv=False, yinv=False, xlabel=True,
15
- ylabel=True, xticklabels=True, yticklabels=True, colorbar=True,
16
- colorbar_label=None, savefig=None):
14
+ ylim=None, xlog=False, ylog=False, xinv=False, yinv=False,
15
+ xlabel=True, ylabel=True, xticklabels=True, yticklabels=True,
16
+ colorbar=True, colorbar_label=None, savefig=None):
17
17
  """
18
18
  Plot several types of 2-D seismological data.
19
19
 
@@ -42,19 +42,20 @@ def plot(data: np.ndarray, dx=None, fs=None, ax=None, obj='waveform', dpi=300,
42
42
  :param vmin, vmax: Define the data range that the colormap covers.
43
43
  :param vmin_per, vmax_per: float. Define the data range that the colormap
44
44
  covers by percentile.
45
- :param dB: bool. Transfer data unit to dB and take 1 as the reference value.
45
+ :param dB: bool. Transfer data unit to dB and take 1 as the reference
46
+ value.
46
47
  :param xmode: str. 'distance' or 'channel'.
47
48
  :param tmode: str. 'time' or 'sampling'.
48
49
  :param xlim, ylim: Set the x-axis and y-axis view limits.
49
50
  :param xlog, ylog: bool. If True, set the x-axis' or y-axis' scale as log.
50
51
  :param xlabel, yinv: bool. If True, invert x-axis or y-axis.
51
- :param xlabel, ylabel: bool or str. Whether to plot a label or what label to
52
- plot for x-axis or y-axis.
52
+ :param xlabel, ylabel: bool or str. Whether to plot a label or what label
53
+ to plot for x-axis or y-axis.
53
54
  :param xticklabels, yticklabels: bool or sequence of str. Whether to plot
54
55
  ticklabels or what ticklabels to plot for x-axis or y-axis.
55
- :param colorbar: bool, str or Matplotlib.axes.Axes. Bool means plot colorbar
56
- or not. Str means the location of colorbar. Axes means the Axes into
57
- which the colorbar will be drawn.
56
+ :param colorbar: bool, str or Matplotlib.axes.Axes. Bool means plot
57
+ colorbar or not. Str means the location of colorbar. Axes means the
58
+ Axes into which the colorbar will be drawn.
58
59
  :param savefig: str or bool. Figure name to save if needed. If True,
59
60
  it will be set to parameter obj.
60
61
  """
@@ -1,6 +1,6 @@
1
1
  # Purpose: Module for handling Collection objects.
2
2
  # Author: Minzhe Hu
3
- # Date: 2025.9.18
3
+ # Date: 2025.11.26
4
4
  # Email: hmz2018@mail.ustc.edu.cn
5
5
  import os
6
6
  import warnings
@@ -228,6 +228,8 @@ class Collection(object):
228
228
  :param spmin, spmax: int. Sampling point range. Only used when
229
229
  readsec=True.
230
230
  """
231
+ if start is None:
232
+ start = 0
231
233
  if end is None:
232
234
  end = len(self.flist)
233
235
  if 'stime' in kwargs.keys():
@@ -239,10 +241,16 @@ class Collection(object):
239
241
  warnings.warn('In future versions, the parameter \'etime\' will be '
240
242
  'replaced by \'end\'.')
241
243
 
242
- if start is None and 'tmin' in kwargs.keys():
243
- start = kwargs['tmin']
244
- if end is None and 'tmax' in kwargs.keys():
245
- end = kwargs['tmax']
244
+ if start is None:
245
+ if 'tmin' in kwargs.keys():
246
+ start = kwargs['tmin']
247
+ else:
248
+ start = 0
249
+ if end is None:
250
+ if 'tmax' in kwargs.keys():
251
+ end = kwargs['tmax']
252
+ else:
253
+ end = len(self)
246
254
 
247
255
  if isinstance(start, datetime):
248
256
  for i, ftime in enumerate(self.ftime):
@@ -288,6 +296,18 @@ class Collection(object):
288
296
  self.ftime = self.ftime[s:e]
289
297
  return self
290
298
 
299
+ def read(self, **kwargs):
300
+ return self.select(readsec=True, **kwargs)
301
+
302
+ def continuous_acquisition(self):
303
+ index = self.file_interruption()
304
+ index = [-1] + index.tolist() + [len(self)-1]
305
+ coll_list = []
306
+ for i in range(len(index) - 1):
307
+ coll = self.copy().select(start=index[i]+1, end=index[i+1]+1)
308
+ coll_list.append(coll)
309
+ return coll_list
310
+
291
311
  def _optimize_for_continuity(self, operations):
292
312
  method_list = []
293
313
  kwargs_list = []
@@ -296,8 +316,10 @@ class Collection(object):
296
316
  for opera in operations:
297
317
  method, kwargs = opera
298
318
  if method == 'downsampling':
299
- if ('lowpass_filter' in kwargs.keys() and not\
300
- kwargs['lowpass_filter']) or 'tint' not in kwargs.keys():
319
+ if_filter = ('tint' not in kwargs.keys() and 'fs' not in
320
+ kwargs.keys()) or ('lowpass_filter' in kwargs.keys() and
321
+ not kwargs['lowpass_filter'])
322
+ if if_filter:
301
323
  method_list.append('downsampling')
302
324
  kwargs_list.append(kwargs)
303
325
  else:
@@ -356,9 +378,9 @@ class Collection(object):
356
378
  (not os.path.exists(kwargs_file)):
357
379
  raise ValueError('No operations input and no method_list.pkl '
358
380
  'and kwargs_list.pkl found in savepath.')
359
- with open(os.path.join(savepath, 'method_list.pkl'), 'wb') as f:
381
+ with open(os.path.join(savepath, 'method_list.pkl'), 'rb') as f:
360
382
  method_list = pickle.load(f)
361
- with open(os.path.join(savepath, 'kwargs_list.pkl'), 'wb') as f:
383
+ with open(os.path.join(savepath, 'kwargs_list.pkl'), 'rb') as f:
362
384
  kwargs_list = pickle.load(f)
363
385
  else:
364
386
  method_list, kwargs_list = self._optimize_for_continuity(operations)
@@ -9,7 +9,7 @@ from datetime import datetime, timedelta, timezone
9
9
 
10
10
 
11
11
  utc = timezone.utc
12
- local_tz = timezone(timedelta(seconds=-time.altzone))
12
+ local_tz = timezone(timedelta(seconds=-time.timezone))
13
13
 
14
14
 
15
15
  class DASDateTime(datetime):
@@ -69,7 +69,7 @@ class DASDateTime(datetime):
69
69
 
70
70
  @classmethod
71
71
  def from_obspy_UTCDateTime(cls, dt):
72
- return cls.from_datetime(dt.datetime)
72
+ return cls.from_datetime(dt.datetime).replace(tzinfo=utc)
73
73
 
74
74
  def to_datetime(self):
75
75
  return datetime.fromtimestamp(self.timestamp(), tz=self.tzinfo)
@@ -1,15 +1,17 @@
1
1
  # Purpose: Module for reading DAS data.
2
- # Author: Minzhe Hu
3
- # Date: 2025.10.30
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
- try:
77
- data, metadata = ftype(fname, headonly=headonly, **kwargs)
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,60 @@ 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
+
134
+ class DummyObject:
135
+ def __init__(self, *args, **kwargs):
136
+ pass
137
+
138
+
139
+ class SafeUnpickler(pickle.Unpickler):
140
+ def find_class(self, module, name):
141
+ try:
142
+ return super().find_class(module, name)
143
+ except ModuleNotFoundError:
144
+ print(f"Skip missing module: {module}.{name}")
145
+ return DummyObject
146
+ except AttributeError:
147
+ print(f"Skip missing class: {module}.{name}")
148
+ return DummyObject
149
+
150
+
100
151
  def _read_pkl(fname, headonly=False, file_format='auto', chmin=None, chmax=None,
101
152
  dch=1, xmin=None, xmax=None, tmin=None, tmax=None, spmin=None,
102
153
  spmax=None):
@@ -104,7 +155,8 @@ def _read_pkl(fname, headonly=False, file_format='auto', chmin=None, chmax=None,
104
155
  Read data and metadata from a pickle file.
105
156
  """
106
157
  with open(fname, 'rb') as f:
107
- pkl_data = pickle.load(f)
158
+ # pkl_data = pickle.load(f)
159
+ pkl_data = SafeUnpickler(f).load()
108
160
  if isinstance(pkl_data, np.ndarray):
109
161
  warnings.warn('This data format doesn\'t include channel interval'
110
162
  'and sampling rate. Please set manually')
@@ -290,6 +342,34 @@ def _read_h5(fname, headonly=False, file_format='auto', chmin=None, chmax=None,
290
342
  'start_time': stime,
291
343
  'gauge_length': attrs['GaugeLength']}
292
344
 
345
+ elif file_format == 'Puniu Tech HiFi-DAS':
346
+ dataset = h5_file['default']
347
+ if 'time,channel' in attrs.get('row_major_order', 'time, channel')\
348
+ .replace(' ', '').lower():
349
+ transpose = True
350
+
351
+ attrs = {k: (v.decode() if isinstance(v, bytes) else v) for k, v
352
+ in dataset.attrs.items()}
353
+ step = int(attrs['step'])
354
+ dx = step * attrs.get('spatial_sampling_rate', 1.0)
355
+ start_channel = int(attrs['start_channel'])
356
+ if step != 1:
357
+ if chmin:
358
+ chmin = (chmin - start_channel) / step + start_channel
359
+ if chmax:
360
+ chmax = (chmin - start_channel) / step + start_channel
361
+ t0 = int(attrs.get('epoch', 0)) + int(attrs.get('ns', 0)) * 1e-9
362
+ data_type = 'strain rate' if attrs.get('format', '') == \
363
+ 'differential' else 'strain',
364
+ metadata = {'dx': dx, 'fs': float(attrs['sampling_rate']),
365
+ 'start_channel': start_channel,
366
+ 'start_distance': start_channel * dx,
367
+ 'start_time': DASDateTime.fromtimestamp(t0, tz=utc),
368
+ 'scale': 110.37, 'data_type': data_type,
369
+ 'cid': attrs.get('cid', '')}
370
+ if 'spatial_resolution' in attrs.keys():
371
+ metadata['gauge_length'] = float(attrs['spatial_resolution'])
372
+
293
373
  elif file_format == 'Silixa iDAS':
294
374
  dataset = h5_file['Acquisition/Raw[0]/RawData/']
295
375
  attrs = h5_file['Acquisition/Raw[0]'].attrs
@@ -356,13 +436,36 @@ def _read_h5(fname, headonly=False, file_format='auto', chmin=None, chmax=None,
356
436
  metadata = {'dx': h5_file['Sampling_interval_in_space'][0],
357
437
  'fs': 1 / h5_file['Sampling_interval_in_time'][0]}
358
438
 
439
+ elif file_format == 'NEC':
440
+ dataset = h5_file['data']
441
+ dx = dataset.attrs['Interval of monitor point']
442
+ fs = 1.0 / (dataset.attrs['Interval time of data'] / 1000.0) # Hz
443
+ if dataset.shape[0] != \
444
+ dataset.attrs['Number of requested location points']:
445
+ transpose = True
446
+ try:
447
+ scale = dataset.attrs['Radians peer digital value']
448
+ except KeyError:
449
+ try:
450
+ scale = dataset.attrs['Radians per digital value']
451
+ except KeyError:
452
+ scale = 1
453
+ # start_time = datetime(1970, 1, 1) + \
454
+ # timedelta(milliseconds=start_unix_epoch_in_ms)
455
+ start_time = DASDateTime.fromtimestamp(
456
+ np.float64(dataset.attrs['Time of sending request']) / 1e3
457
+ ).utc()
458
+ metadata = {'fs': fs, 'dx': dx, 'start_time': start_time,
459
+ 'gauge_length': dataset.attrs['Gauge length'],
460
+ 'scale': scale, 'data_type':'strain rate'}
461
+
359
462
  elif file_format == 'FORESEE':
360
463
  dataset = h5_file['raw']
361
464
  fs = round(1 / np.diff(h5_file['timestamp']).mean())
362
465
  start_time = DASDateTime.fromtimestamp(
363
466
  h5_file['timestamp'][0]).astimezone(utc)
364
- warnings.warn('This data format doesn\'t include channel interval. '
365
- 'Please set manually')
467
+ warnings.warn('This data format doesn\'t include channel interval.'
468
+ ' Please set manually')
366
469
  metadata = {'dx': None, 'fs': fs, 'start_time': start_time}
367
470
 
368
471
  elif file_format == 'AI4EPS': # https://ai4eps.github.io/homepage/ml4earth/seismic_event_format_das/
@@ -399,7 +502,13 @@ def _read_h5(fname, headonly=False, file_format='auto', chmin=None, chmax=None,
399
502
  metadata['headers'] = _read_h5_headers(h5_file)
400
503
  shape = dataset.shape
401
504
  if len(shape) == 3:
402
- shape = (shape[0] * shape[1], shape[2])
505
+ if headonly:
506
+ fs = int(metadata['fs'])
507
+ fs_b = attrs.get('BlockRate', [1000])[0] / 1e3
508
+ nsp_b = round(fs/fs_b)
509
+ shape = (shape[0] * nsp_b, shape[2])
510
+ else:
511
+ shape = (shape[0] * shape[1], shape[2])
403
512
  if transpose:
404
513
  shape = shape[::-1]
405
514
  si, sj, metadata = _trimming_slice_metadata(shape, metadata=metadata,
@@ -500,7 +609,6 @@ def _read_tdms(fname, headonly=False, file_format='auto', chmin=None,
500
609
  data = np.asarray([tdms_file[key][str(ch)][sj] for ch in
501
610
  range(si.start, si.stop, si.step)])
502
611
  elif file_format == 'Institute of Semiconductors, CAS':
503
-
504
612
  try:
505
613
  start_channel = int(properties['Initial Channel'])
506
614
  except KeyError:
@@ -1,6 +1,6 @@
1
1
  # Purpose: Module for handling Section objects.
2
2
  # Author: Minzhe Hu
3
- # Date: 2025.10.31
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))
@@ -195,7 +196,7 @@ class Section(object):
195
196
 
196
197
  @property
197
198
  def end_channel(self):
198
- return self.start_channel + self.nch - 1
199
+ return self.start_channel + self.nch
199
200
 
200
201
  @property
201
202
  def distance(self):
@@ -462,8 +463,8 @@ class Section(object):
462
463
  :param ftype: None or str. None for automatic detection), or 'pkl',
463
464
  'pickle', 'tdms', 'h5', 'hdf5', 'segy', 'sgy', 'npy'.
464
465
  :param keep_format: bool. If True, we will make a copy of the
465
- self.source file and make changes to it. This will strictly preserve
466
- the original format, but will cost more IO resources.
466
+ self.source file and make changes to it. This will strictly
467
+ preserve the original format, but will cost more IO resources.
467
468
  :param dtype: str. The data type of the saved data.
468
469
  :param file_format: Format in which the file is saved. Only works when
469
470
  keep_format == False. 'auto' for raw file format or the most common
@@ -646,9 +647,11 @@ class Section(object):
646
647
  if hasattr(self, 'origin_time'):
647
648
  kwargs['t0'] -= self.origin_time
648
649
  if ('transpose' in kwargs.keys()) and kwargs['transpose']:
649
- kwargs.setdefault('xlabel', 'Times(s) after occurance')
650
+ kwargs.setdefault('xlabel',
651
+ 'Times after occurance (s)')
650
652
  else:
651
- kwargs.setdefault('ylabel', 'Times(s) after occurance')
653
+ kwargs.setdefault('ylabel',
654
+ 'Times after occurance (s)')
652
655
  else:
653
656
  tmode == 'start'
654
657
  if tmode == 'start':
@@ -1096,12 +1099,13 @@ class Section(object):
1096
1099
  if ('ch1' in kwargs.keys()) or ('ch2' in kwargs.keys()):
1097
1100
  kwargs['chmin'] = kwargs.pop('ch1', 0)
1098
1101
  kwargs['chmax'] = kwargs.pop('ch2', self.nch)
1099
- warnings.warn("'ch1' and 'ch2' attribute will be renamed to 'chmin'"
1100
- " and 'chmax' in a future release.", FutureWarning)
1102
+ warnings.warn("'ch1' and 'ch2' attribute will be renamed to "
1103
+ "'chmin' and 'chmax' in a future release.",
1104
+ FutureWarning)
1101
1105
  if 'nch' in kwargs.keys():
1102
1106
  kwargs['dch'] = kwargs.pop('nch', 1)
1103
- warnings.warn("'nch' attribute will be renamed to 'dch' in a future"
1104
- " release.", FutureWarning)
1107
+ warnings.warn("'nch' attribute will be renamed to 'dch' in a "
1108
+ "future release.", FutureWarning)
1105
1109
 
1106
1110
  if 'chmin' in kwargs.keys():
1107
1111
  chmin = int(kwargs.pop('chmin') - self.start_channel)
@@ -1132,6 +1136,14 @@ class Section(object):
1132
1136
  """
1133
1137
  return fk_transform(self.data, self.dx, self.fs, **kwargs)
1134
1138
 
1139
+ def power(self):
1140
+ """
1141
+ Calculate the power of each channel.
1142
+
1143
+ :return: numpy.ndarray. Power of each channel.
1144
+ """
1145
+ return power(self.data)
1146
+
1135
1147
  def channel_checking(self, use=False, **kwargs):
1136
1148
  """
1137
1149
  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', 'sintalaonyx',
30
- 'sintela', 'onyxv1.0', 'onyxv1', 'onyx'],
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', 'japanagencyformarineearthscienceandtechnology'],
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.9.18
3
+ # Date: 2025.11.19
4
4
  # Email: hmz2018@mail.ustc.edu.cn
5
5
  import os
6
6
  import warnings
@@ -35,8 +35,10 @@ def write(sec, fname, ftype=None, raw_fname=None, dtype=None,
35
35
 
36
36
 
37
37
  def write_pkl(sec, fname):
38
+ save_dict = sec.__dict__
39
+ save_dict.pop('source', None)
38
40
  with open(fname, 'wb') as f:
39
- pickle.dump(sec.__dict__, f)
41
+ pickle.dump(save_dict, f)
40
42
  return None
41
43
 
42
44
 
@@ -192,9 +194,39 @@ def _write_h5(sec, fname, raw_fname=None, file_format='auto'):
192
194
  h5_file.get('Acquisition/Raw[0]/').\
193
195
  create_dataset('RawDataTime', data=datatime)
194
196
  h5_file['Acquisition/Raw[0]'].attrs['OutputDataRate'] = sec.fs
195
- h5_file['Acquisition'].attrs['SpatialSamplingInterval'] = sec.dx
196
- h5_file['Acquisition'].attrs['GaugeLength'] = sec.gauge_length \
197
- if hasattr(sec, 'gauge_length') else -1
197
+ h5_file['Acquisition'].attrs['SpatialSamplingInterval'] = \
198
+ sec.dx
199
+ h5_file['Acquisition'].attrs['GaugeLength'] = \
200
+ sec.gauge_length if hasattr(sec, 'gauge_length') else -1
201
+
202
+ elif file_format == 'Puniu Tech HiFi-DAS':
203
+ h5_file.create_dataset('default', data=sec.data)
204
+ h5_file['default'].attrs['row_major_order'] = 'channel, time'
205
+ h5_file['default'].attrs['spatial_sampling_rate'] = sec.dx
206
+ h5_file['default'].attrs['step'] = 1
207
+ h5_file['default'].attrs['sampling_rate'] = sec.fs
208
+ h5_file['default'].attrs['start_channel'] = sec.start_channel
209
+ if isinstance(sec.start_time, datetime):
210
+ t0 = sec.start_time.timestamp()
211
+ else:
212
+ t0 = sec.start_time
213
+ epoch = int(t0)
214
+ h5_file['default'].attrs['epoch'] = epoch
215
+ h5_file['default'].attrs['ns'] = int(round((t0 - epoch) * 1e9))
216
+ if hasattr(sec, 'data_type'):
217
+ h5_file['default'].attrs['format'] = 'differential' if \
218
+ sec.data_type == 'strain rate' else sec.data_type
219
+ else:
220
+ h5_file['default'].attrs['format'] = 'unknown'
221
+
222
+ if hasattr(sec, 'gauge_length'):
223
+ h5_file['default'].attrs['spatial_resolution'] = \
224
+ sec.gauge_length
225
+ try:
226
+ h5_file['default'].attrs['cid'] = \
227
+ sec.headers['default']['attrs']['cid']
228
+ except KeyError:
229
+ h5_file['default'].attrs['cid'] = 'unknown'
198
230
 
199
231
  elif file_format == 'Silixa iDAS':
200
232
  h5_file.create_group('Acquisition/Raw[0]')
@@ -273,7 +305,6 @@ def _write_h5(sec, fname, raw_fname=None, file_format='auto'):
273
305
  h5_file.attrs['FilterGain'] = 116e-9 * sec.fs / gauge_length / \
274
306
  8192 / scale
275
307
 
276
-
277
308
  elif file_format in 'JAMSTEC':
278
309
  h5_file.create_dataset('DAS_record', data=sec.data)
279
310
  h5_file.create_dataset('Sampling_interval_in_space',
@@ -281,6 +312,23 @@ def _write_h5(sec, fname, raw_fname=None, file_format='auto'):
281
312
  h5_file.create_dataset('Sampling_interval_in_time',
282
313
  data=[1/sec.fs])
283
314
 
315
+ elif file_format == 'NEC':
316
+ h5_file.create_dataset('data', data=sec.data)
317
+ h5_file['data'].attrs['Interval of monitor point'] = sec.dx
318
+ h5_file['data'].attrs['Number of requested location points'] \
319
+ = sec.nch
320
+ h5_file['data'].attrs['Interval time of data'] = \
321
+ 1 / sec.fs * 1e3
322
+ h5_file['data'].attrs['Radians per digital value'] = \
323
+ sec.scale if hasattr(sec, 'scale') else 1
324
+ if isinstance(sec.start_time, datatime):
325
+ t0 = sec.start_time.timestamp()
326
+ else:
327
+ t0 = sec.start_time
328
+ h5_file['data'].attrs['Time of sending request'] = t0 * 1e3
329
+ h5_file['data'].attrs['Gauge length'] = sec.gauge_length if \
330
+ hasattr(sec, 'gauge_length') else -1
331
+
284
332
  elif file_format == 'FORESEE':
285
333
  h5_file.create_dataset('raw', data=sec.data)
286
334
  start_time = sec.start_time.timestamp() if \
@@ -463,9 +511,42 @@ def _write_h5(sec, fname, raw_fname=None, file_format='auto'):
463
511
  _update_h5_dataset(h5_file, 'Acquisition/Raw[0]/',
464
512
  'RawDataTime', DataTime)
465
513
  h5_file['Acquisition/Raw[0]'].attrs['OutputDataRate'] = sec.fs
466
- h5_file['Acquisition'].attrs['SpatialSamplingInterval'] = sec.dx
467
- h5_file['Acquisition'].attrs['GaugeLength'] = sec.gauge_length \
468
- if hasattr(sec, 'gauge_length') else -1
514
+ h5_file['Acquisition'].attrs['SpatialSamplingInterval'] = \
515
+ sec.dx
516
+ h5_file['Acquisition'].attrs['GaugeLength'] = \
517
+ sec.gauge_length if hasattr(sec, 'gauge_length') else -1
518
+
519
+ elif file_format == 'Puniu Tech HiFi-DAS':
520
+ attrs = {k: (v.decode() if isinstance(v, bytes) else v) for k,
521
+ v in h5_file['default'].attrs.items()}
522
+ if 'time,channel' in attrs.get('row_major_order',
523
+ 'time, channel').replace(' ', '').lower():
524
+ _update_h5_dataset(h5_file, '/', 'default', sec.data.T)
525
+ else:
526
+ _update_h5_dataset(h5_file, '/', 'default', sec.data)
527
+
528
+ h5_file['default'].attrs['spatial_sampling_rate'] = sec.dx / \
529
+ attrs['step']
530
+ h5_file['default'].attrs['sampling_rate'] = sec.fs
531
+ h5_file['default'].attrs['start_channel'] = sec.start_channel
532
+ if isinstance(sec.start_time, datetime):
533
+ t0 = sec.start_time.timestamp()
534
+ else:
535
+ t0 = sec.start_time
536
+ epoch = int(t0)
537
+ h5_file['default'].attrs['epoch'] = epoch
538
+ h5_file['default'].attrs['ns'] = int(round((t0 - epoch) * 1e9))
539
+ if hasattr(sec, 'data_type'):
540
+ h5_file['default'].attrs['format'] = 'differential' if \
541
+ sec.data_type == 'strain rate' else sec.data_type
542
+ if hasattr(sec, 'gauge_length'):
543
+ h5_file['default'].attrs['spatial_resolution'] = \
544
+ sec.gauge_length
545
+ try:
546
+ h5_file['default'].attrs['cid'] = \
547
+ sec.headers['default']['attrs']['cid']
548
+ except KeyError:
549
+ pass
469
550
 
470
551
  elif file_format == 'Silixa iDAS':
471
552
  if h5_file['Acquisition/Raw[0]/RawData/'].shape[0] != \
@@ -538,6 +619,24 @@ def _write_h5(sec, fname, raw_fname=None, file_format='auto'):
538
619
  _update_h5_dataset(h5_file, '/', 'Sampling_interval_in_time',
539
620
  [1/sec.fs])
540
621
 
622
+ elif file_format == 'NEC':
623
+ _update_h5_dataset(h5_file, '/', 'data', sec.data)
624
+ h5_file['data'].attrs['Interval of monitor point'] = sec.dx
625
+ h5_file['data'].attrs['Number of requested location points'] \
626
+ = sec.nch
627
+ h5_file['data'].attrs['Interval time of data'] = \
628
+ 1 / sec.fs * 1e3
629
+ if hasattr(sec, scale):
630
+ h5_file['data'].attrs['Radians per digital value'] = \
631
+ sec.scale
632
+ if isinstance(sec.start_time, datatime):
633
+ t0 = sec.start_time.timestamp()
634
+ else:
635
+ t0 = sec.start_time
636
+ h5_file['data'].attrs['Time of sending request'] = t0 * 1e3
637
+ if hasattr(sec, 'gauge_length'):
638
+ h5_file['data'].attrs['Gauge length'] = sec.gauge_length
639
+
541
640
  elif file_format == 'FORESEE':
542
641
  _update_h5_dataset(h5_file, '/', 'raw', sec.data)
543
642
  DataTime = sec.start_time.timestamp() + \
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
 
4
4
  setup(
5
- name='DASPy-toolbox', version='1.2.1',
5
+ name='DASPy-toolbox', version='1.2.3',
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 '
File without changes
File without changes
File without changes