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.
Files changed (32) hide show
  1. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/DASPy_toolbox.egg-info/PKG-INFO +1 -1
  2. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/DASPy_toolbox.egg-info/SOURCES.txt +1 -0
  3. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/PKG-INFO +1 -1
  4. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/advanced_tools/denoising.py +2 -2
  5. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/advanced_tools/strain2vel.py +19 -17
  6. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/basic_tools/freqattributes.py +11 -1
  7. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/basic_tools/preprocessing.py +28 -11
  8. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/core/collection.py +28 -28
  9. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/core/dasdatetime.py +1 -1
  10. daspy_toolbox-1.2.2/daspy/core/make_example.py +32 -0
  11. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/core/read.py +113 -32
  12. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/core/section.py +41 -19
  13. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/core/util.py +16 -5
  14. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/core/write.py +113 -11
  15. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/setup.py +3 -3
  16. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/DASPy_toolbox.egg-info/dependency_links.txt +0 -0
  17. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/DASPy_toolbox.egg-info/entry_points.txt +0 -0
  18. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/DASPy_toolbox.egg-info/requires.txt +0 -0
  19. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/DASPy_toolbox.egg-info/top_level.txt +0 -0
  20. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/LICENSE +0 -0
  21. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/README.md +0 -0
  22. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/__init__.py +0 -0
  23. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/advanced_tools/__init__.py +0 -0
  24. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/advanced_tools/channel.py +0 -0
  25. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/advanced_tools/decomposition.py +0 -0
  26. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/advanced_tools/fdct.py +0 -0
  27. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/basic_tools/__init__.py +0 -0
  28. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/basic_tools/filter.py +0 -0
  29. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/basic_tools/visualization.py +0 -0
  30. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/core/__init__.py +0 -0
  31. {daspy_toolbox-1.2.0 → daspy_toolbox-1.2.2}/daspy/core/example.pkl +0 -0
  32. {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.0
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
@@ -23,6 +23,7 @@ daspy/core/__init__.py
23
23
  daspy/core/collection.py
24
24
  daspy/core/dasdatetime.py
25
25
  daspy/core/example.pkl
26
+ daspy/core/make_example.py
26
27
  daspy/core/read.py
27
28
  daspy/core/section.py
28
29
  daspy/core/util.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DASPy-toolbox
3
- Version: 1.2.0
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: 2024.5.13
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 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))
@@ -1,9 +1,10 @@
1
1
  # Purpose: Some preprocess methods
2
2
  # Author: Minzhe Hu
3
- # Date: 2025.6.25
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 gl: float. Gauge length.
22
+ :paran guage_length: float. Gauge length.
22
23
  :return: Strain data.
23
24
  """
24
- return data * (lam * 1e-9) / (e * 4 * np.pi * n * gl)
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
- return np.cumsum(data, axis=1) / fs + c
333
-
334
-
335
- def time_differential(data, fs, prepend=0):
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 prepend == 'mean':
346
- prepend = np.mean(data, axis=1).reshape((-1, 1))
347
- return np.diff(data, axis=1, prepend=prepend) * fs
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.15
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, save_operations=False,
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, dtype=dtype)
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, dtype=dtype)
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, dtype=dtype)
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
- 'left')):
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', 'highpass',
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, dtype=dtype)
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, dtype=dtype)
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, dtype=dtype)
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, **kwargs):
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, save_operations=save_operations)
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, tz_prefix, offset, dt2 = match.groups()
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.9.15
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,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])).utc()
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['PulseRateFreq'][0] /
220
- attrs['SamplingRes'][0]) / 1000
254
+ fs = float(1000 / attrs['Spacing'][1])
221
255
  except KeyError:
222
- try:
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
- try:
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
- 'Please set manually')
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
- j0, k0 = divmod(sj.start, fs)
419
- j1, k1 = divmod(sj.stop, fs)
420
- k1 = (j1 - j0) * fs + k1
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, :, si]
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.6:
530
+ elif 2.3 <= version < 2.7:
448
531
  file_format = 'Silixa iDAS-v2'
449
- elif version >= 2.6:
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
- else:
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.9.15
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 len(data) != self.nch:
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
- kwargs.setdefault('ylabel', 'Times(s) after occurance')
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 to
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, prepend=prepend)
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, **kwargs)
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', '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.15
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'] = sec.dx
196
- h5_file['Acquisition'].attrs['GaugeLength'] = sec.gauge_length \
197
- if hasattr(sec, 'gauge_length') else -1
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'] = sec.dx
462
- h5_file['Acquisition'].attrs['GaugeLength'] = sec.gauge_length \
463
- if hasattr(sec, 'gauge_length') else -1
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.0',
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