DASPy-toolbox 1.2.1__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.1 → daspy_toolbox-1.2.2}/DASPy_toolbox.egg-info/PKG-INFO +1 -1
  2. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/PKG-INFO +1 -1
  3. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/advanced_tools/strain2vel.py +19 -17
  4. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/basic_tools/freqattributes.py +11 -1
  5. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/basic_tools/preprocessing.py +2 -2
  6. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/core/read.py +96 -12
  7. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/core/section.py +11 -2
  8. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/core/util.py +16 -5
  9. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/core/write.py +105 -8
  10. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/setup.py +1 -1
  11. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/DASPy_toolbox.egg-info/SOURCES.txt +0 -0
  12. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/DASPy_toolbox.egg-info/dependency_links.txt +0 -0
  13. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/DASPy_toolbox.egg-info/entry_points.txt +0 -0
  14. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/DASPy_toolbox.egg-info/requires.txt +0 -0
  15. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/DASPy_toolbox.egg-info/top_level.txt +0 -0
  16. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/LICENSE +0 -0
  17. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/README.md +0 -0
  18. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/__init__.py +0 -0
  19. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/advanced_tools/__init__.py +0 -0
  20. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/advanced_tools/channel.py +0 -0
  21. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/advanced_tools/decomposition.py +0 -0
  22. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/advanced_tools/denoising.py +0 -0
  23. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/advanced_tools/fdct.py +0 -0
  24. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/basic_tools/__init__.py +0 -0
  25. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/basic_tools/filter.py +0 -0
  26. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/basic_tools/visualization.py +0 -0
  27. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/core/__init__.py +0 -0
  28. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/core/collection.py +0 -0
  29. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/core/dasdatetime.py +0 -0
  30. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/core/example.pkl +0 -0
  31. {daspy_toolbox-1.2.1 → daspy_toolbox-1.2.2}/daspy/core/make_example.py +0 -0
  32. {daspy_toolbox-1.2.1 → 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.1
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.1
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,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'):
@@ -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,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):
@@ -290,6 +324,34 @@ def _read_h5(fname, headonly=False, file_format='auto', chmin=None, chmax=None,
290
324
  'start_time': stime,
291
325
  'gauge_length': attrs['GaugeLength']}
292
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
+
293
355
  elif file_format == 'Silixa iDAS':
294
356
  dataset = h5_file['Acquisition/Raw[0]/RawData/']
295
357
  attrs = h5_file['Acquisition/Raw[0]'].attrs
@@ -356,13 +418,36 @@ def _read_h5(fname, headonly=False, file_format='auto', chmin=None, chmax=None,
356
418
  metadata = {'dx': h5_file['Sampling_interval_in_space'][0],
357
419
  'fs': 1 / h5_file['Sampling_interval_in_time'][0]}
358
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
+
359
444
  elif file_format == 'FORESEE':
360
445
  dataset = h5_file['raw']
361
446
  fs = round(1 / np.diff(h5_file['timestamp']).mean())
362
447
  start_time = DASDateTime.fromtimestamp(
363
448
  h5_file['timestamp'][0]).astimezone(utc)
364
- warnings.warn('This data format doesn\'t include channel interval. '
365
- 'Please set manually')
449
+ warnings.warn('This data format doesn\'t include channel interval.'
450
+ ' Please set manually')
366
451
  metadata = {'dx': None, 'fs': fs, 'start_time': start_time}
367
452
 
368
453
  elif file_format == 'AI4EPS': # https://ai4eps.github.io/homepage/ml4earth/seismic_event_format_das/
@@ -500,7 +585,6 @@ def _read_tdms(fname, headonly=False, file_format='auto', chmin=None,
500
585
  data = np.asarray([tdms_file[key][str(ch)][sj] for ch in
501
586
  range(si.start, si.stop, si.step)])
502
587
  elif file_format == 'Institute of Semiconductors, CAS':
503
-
504
588
  try:
505
589
  start_channel = int(properties['Initial Channel'])
506
590
  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))
@@ -1132,6 +1133,14 @@ class Section(object):
1132
1133
  """
1133
1134
  return fk_transform(self.data, self.dx, self.fs, **kwargs)
1134
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
+
1135
1144
  def channel_checking(self, use=False, **kwargs):
1136
1145
  """
1137
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.18
3
+ # Date: 2025.11.19
4
4
  # Email: hmz2018@mail.ustc.edu.cn
5
5
  import os
6
6
  import warnings
@@ -192,9 +192,39 @@ def _write_h5(sec, fname, raw_fname=None, file_format='auto'):
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 \
@@ -463,9 +509,42 @@ def _write_h5(sec, fname, raw_fname=None, file_format='auto'):
463
509
  _update_h5_dataset(h5_file, 'Acquisition/Raw[0]/',
464
510
  'RawDataTime', DataTime)
465
511
  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
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
469
548
 
470
549
  elif file_format == 'Silixa iDAS':
471
550
  if h5_file['Acquisition/Raw[0]/RawData/'].shape[0] != \
@@ -538,6 +617,24 @@ def _write_h5(sec, fname, raw_fname=None, file_format='auto'):
538
617
  _update_h5_dataset(h5_file, '/', 'Sampling_interval_in_time',
539
618
  [1/sec.fs])
540
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
+
541
638
  elif file_format == 'FORESEE':
542
639
  _update_h5_dataset(h5_file, '/', 'raw', sec.data)
543
640
  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.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 '
File without changes
File without changes
File without changes