DASPy-toolbox 1.1.1__tar.gz → 1.1.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 (56) hide show
  1. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/DASPy_toolbox.egg-info/PKG-INFO +17 -6
  2. daspy_toolbox-1.1.3/DASPy_toolbox.egg-info/SOURCES.txt +28 -0
  3. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/PKG-INFO +17 -6
  4. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/README.md +2 -3
  5. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/daspy/advanced_tools/channel.py +89 -106
  6. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/daspy/advanced_tools/strain2vel.py +1 -2
  7. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/daspy/basic_tools/preprocessing.py +11 -11
  8. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/daspy/basic_tools/visualization.py +17 -11
  9. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/daspy/core/collection.py +42 -18
  10. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/daspy/core/dasdatetime.py +27 -5
  11. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/daspy/core/section.py +22 -8
  12. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/setup.py +1 -1
  13. DASPy-toolbox-1.1.1/DASPy_toolbox.egg-info/SOURCES.txt +0 -53
  14. DASPy-toolbox-1.1.1/daspy/core/make_example.py +0 -32
  15. DASPy-toolbox-1.1.1/daspy/seismic_detection/__init__.py +0 -1
  16. DASPy-toolbox-1.1.1/daspy/seismic_detection/calc_travel_time.py +0 -23
  17. DASPy-toolbox-1.1.1/daspy/seismic_detection/core.py +0 -186
  18. DASPy-toolbox-1.1.1/daspy/seismic_detection/detection.py +0 -12
  19. DASPy-toolbox-1.1.1/daspy/seismic_detection/gamma/__init__.py +0 -13
  20. DASPy-toolbox-1.1.1/daspy/seismic_detection/gamma/_base.py +0 -549
  21. DASPy-toolbox-1.1.1/daspy/seismic_detection/gamma/_bayesian_mixture.py +0 -875
  22. DASPy-toolbox-1.1.1/daspy/seismic_detection/gamma/_gaussian_mixture.py +0 -866
  23. DASPy-toolbox-1.1.1/daspy/seismic_detection/gamma/app.py +0 -192
  24. DASPy-toolbox-1.1.1/daspy/seismic_detection/gamma/seismic_ops.py +0 -478
  25. DASPy-toolbox-1.1.1/daspy/seismic_detection/gamma/utils.py +0 -512
  26. DASPy-toolbox-1.1.1/daspy/seismic_detection/location.py +0 -266
  27. DASPy-toolbox-1.1.1/daspy/seismic_detection/magnitude.py +0 -43
  28. DASPy-toolbox-1.1.1/daspy/seismic_detection/phase_picking.py +0 -87
  29. DASPy-toolbox-1.1.1/daspy/structure_imaging/__init__.py +0 -0
  30. DASPy-toolbox-1.1.1/daspy/structure_imaging/ambient_noise.py +0 -4
  31. DASPy-toolbox-1.1.1/daspy/structure_imaging/core.py +0 -39
  32. DASPy-toolbox-1.1.1/daspy/structure_imaging/dispersion.py +0 -27
  33. DASPy-toolbox-1.1.1/daspy/structure_imaging/fault_zone.py +0 -90
  34. DASPy-toolbox-1.1.1/daspy/structure_imaging/inversion.py +0 -6
  35. DASPy-toolbox-1.1.1/daspy/traffic_monitoring/JamDetection.py +0 -6
  36. DASPy-toolbox-1.1.1/daspy/traffic_monitoring/SpeedMeasurement.py +0 -6
  37. DASPy-toolbox-1.1.1/daspy/traffic_monitoring/VehicleDetection.py +0 -6
  38. DASPy-toolbox-1.1.1/daspy/traffic_monitoring/__init__.py +0 -0
  39. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/DASPy_toolbox.egg-info/dependency_links.txt +0 -0
  40. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/DASPy_toolbox.egg-info/entry_points.txt +0 -0
  41. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/DASPy_toolbox.egg-info/requires.txt +0 -0
  42. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/DASPy_toolbox.egg-info/top_level.txt +0 -0
  43. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/LICENSE +0 -0
  44. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/daspy/__init__.py +0 -0
  45. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/daspy/advanced_tools/__init__.py +0 -0
  46. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/daspy/advanced_tools/decomposition.py +0 -0
  47. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/daspy/advanced_tools/denoising.py +0 -0
  48. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/daspy/advanced_tools/fdct.py +0 -0
  49. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/daspy/basic_tools/__init__.py +0 -0
  50. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/daspy/basic_tools/filter.py +0 -0
  51. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/daspy/basic_tools/freqattributes.py +0 -0
  52. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/daspy/core/__init__.py +0 -0
  53. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/daspy/core/example.pkl +0 -0
  54. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/daspy/core/read.py +0 -0
  55. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/daspy/core/write.py +0 -0
  56. {DASPy-toolbox-1.1.1 → daspy_toolbox-1.1.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: DASPy-toolbox
3
- Version: 1.1.1
3
+ Version: 1.1.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
@@ -22,14 +22,25 @@ Requires-Dist: h5py
22
22
  Requires-Dist: segyio
23
23
  Requires-Dist: nptdms
24
24
  Requires-Dist: tqdm
25
-
26
- <img src="./website/USTC.svg" height="170" />&emsp;<img src="./website/DAMS.png" height="150" />
27
-
28
- # DASPy
25
+ Dynamic: author
26
+ Dynamic: author-email
27
+ Dynamic: classifier
28
+ Dynamic: description
29
+ Dynamic: home-page
30
+ Dynamic: license
31
+ Dynamic: license-file
32
+ Dynamic: maintainer
33
+ Dynamic: maintainer-email
34
+ Dynamic: requires-dist
35
+ Dynamic: requires-python
36
+ Dynamic: summary
37
+
38
+ <img src="https://raw.github.com/hmz-03/daspy/main/website/logo.png" height="200" />
29
39
 
30
40
  [![Supported Python versions](https://img.shields.io/badge/python-3.9%20|%203.10%20|%203.11%20|%203.12-blue)](https://pypi.org/project/DASPy-toolbox/)
31
41
  [![License](https://img.shields.io/pypi/l/daspy-toolbox.svg)](https://opensource.org/license/mit)
32
42
  [![PyPI Version](https://img.shields.io/pypi/v/daspy-toolbox.svg)](https://pypi.org/project/DASPy-toolbox/)
43
+
33
44
  [![DOI](https://img.shields.io/badge/DOI-10.1785/0220240124-blue.svg)](https://doi.org/10.1785/0220240124)
34
45
  [![PyPI Downloads](https://img.shields.io/pypi/dm/daspy-toolbox.svg?label=pypi)](https://pypi.org/project/DASPy-toolbox/)
35
46
  [![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/daspy-toolbox?label=conda)](https://anaconda.org/conda-forge/daspy-toolbox)
@@ -0,0 +1,28 @@
1
+ LICENSE
2
+ README.md
3
+ setup.py
4
+ DASPy_toolbox.egg-info/PKG-INFO
5
+ DASPy_toolbox.egg-info/SOURCES.txt
6
+ DASPy_toolbox.egg-info/dependency_links.txt
7
+ DASPy_toolbox.egg-info/entry_points.txt
8
+ DASPy_toolbox.egg-info/requires.txt
9
+ DASPy_toolbox.egg-info/top_level.txt
10
+ daspy/__init__.py
11
+ daspy/advanced_tools/__init__.py
12
+ daspy/advanced_tools/channel.py
13
+ daspy/advanced_tools/decomposition.py
14
+ daspy/advanced_tools/denoising.py
15
+ daspy/advanced_tools/fdct.py
16
+ daspy/advanced_tools/strain2vel.py
17
+ daspy/basic_tools/__init__.py
18
+ daspy/basic_tools/filter.py
19
+ daspy/basic_tools/freqattributes.py
20
+ daspy/basic_tools/preprocessing.py
21
+ daspy/basic_tools/visualization.py
22
+ daspy/core/__init__.py
23
+ daspy/core/collection.py
24
+ daspy/core/dasdatetime.py
25
+ daspy/core/example.pkl
26
+ daspy/core/read.py
27
+ daspy/core/section.py
28
+ daspy/core/write.py
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: DASPy-toolbox
3
- Version: 1.1.1
3
+ Version: 1.1.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
@@ -22,14 +22,25 @@ Requires-Dist: h5py
22
22
  Requires-Dist: segyio
23
23
  Requires-Dist: nptdms
24
24
  Requires-Dist: tqdm
25
-
26
- <img src="./website/USTC.svg" height="170" />&emsp;<img src="./website/DAMS.png" height="150" />
27
-
28
- # DASPy
25
+ Dynamic: author
26
+ Dynamic: author-email
27
+ Dynamic: classifier
28
+ Dynamic: description
29
+ Dynamic: home-page
30
+ Dynamic: license
31
+ Dynamic: license-file
32
+ Dynamic: maintainer
33
+ Dynamic: maintainer-email
34
+ Dynamic: requires-dist
35
+ Dynamic: requires-python
36
+ Dynamic: summary
37
+
38
+ <img src="https://raw.github.com/hmz-03/daspy/main/website/logo.png" height="200" />
29
39
 
30
40
  [![Supported Python versions](https://img.shields.io/badge/python-3.9%20|%203.10%20|%203.11%20|%203.12-blue)](https://pypi.org/project/DASPy-toolbox/)
31
41
  [![License](https://img.shields.io/pypi/l/daspy-toolbox.svg)](https://opensource.org/license/mit)
32
42
  [![PyPI Version](https://img.shields.io/pypi/v/daspy-toolbox.svg)](https://pypi.org/project/DASPy-toolbox/)
43
+
33
44
  [![DOI](https://img.shields.io/badge/DOI-10.1785/0220240124-blue.svg)](https://doi.org/10.1785/0220240124)
34
45
  [![PyPI Downloads](https://img.shields.io/pypi/dm/daspy-toolbox.svg?label=pypi)](https://pypi.org/project/DASPy-toolbox/)
35
46
  [![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/daspy-toolbox?label=conda)](https://anaconda.org/conda-forge/daspy-toolbox)
@@ -1,10 +1,9 @@
1
- <img src="./website/USTC.svg" height="170" />&emsp;<img src="./website/DAMS.png" height="150" />
2
-
3
- # DASPy
1
+ <img src="https://raw.github.com/hmz-03/daspy/main/website/logo.png" height="200" />
4
2
 
5
3
  [![Supported Python versions](https://img.shields.io/badge/python-3.9%20|%203.10%20|%203.11%20|%203.12-blue)](https://pypi.org/project/DASPy-toolbox/)
6
4
  [![License](https://img.shields.io/pypi/l/daspy-toolbox.svg)](https://opensource.org/license/mit)
7
5
  [![PyPI Version](https://img.shields.io/pypi/v/daspy-toolbox.svg)](https://pypi.org/project/DASPy-toolbox/)
6
+
8
7
  [![DOI](https://img.shields.io/badge/DOI-10.1785/0220240124-blue.svg)](https://doi.org/10.1785/0220240124)
9
8
  [![PyPI Downloads](https://img.shields.io/pypi/dm/daspy-toolbox.svg?label=pypi)](https://pypi.org/project/DASPy-toolbox/)
10
9
  [![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/daspy-toolbox?label=conda)](https://anaconda.org/conda-forge/daspy-toolbox)
@@ -1,6 +1,6 @@
1
1
  # Purpose: Several functions for analysis data quality and geometry of channels
2
2
  # Author: Minzhe Hu, Zefeng Li
3
- # Date: 2024.11.18
3
+ # Date: 2025.3.31
4
4
  # Email: hmz2018@mail.ustc.edu.cn
5
5
  import numpy as np
6
6
  from copy import deepcopy
@@ -84,7 +84,7 @@ def channel_checking(data, deg=10, thresh=5, continuity=True, adjacent=2,
84
84
  bad_chn = np.argwhere(deviation < -thresh * mad).ravel().tolist()
85
85
  elif mode == 'high':
86
86
  bad_chn = np.argwhere(deviation > thresh * mad).ravel().tolist()
87
- elif mode == 'high':
87
+ elif mode == 'both':
88
88
  bad_chn = np.argwhere(deviation < -thresh * mad).ravel().tolist() + \
89
89
  np.argwhere(deviation > thresh * mad).ravel().tolist()
90
90
  good_chn = list(set(range(nch)) - set(bad_chn))
@@ -365,62 +365,62 @@ def turning_points(data, data_type='coordinate', thresh=5, depth_info=False,
365
365
  raise ValueError('Data_type should be \'coordinate\' or \'waveform\'.')
366
366
 
367
367
 
368
- def _equally_spacing(channels, dist, dx):
369
- if len(channels) > 20:
370
- return _equally_spacing_2(channels, dist, dx)
371
- else:
372
- return _equally_spacing_1(channels, dist, dx)
373
-
374
-
375
- def _equally_spacing_1(channels, dist, dx):
376
- nch = len(channels)
377
- residual = np.inf
378
- for i in range(2 ** (nch - 2)):
379
- state = bin(i)[2:].rjust(nch - 2, '0')
380
- dist_new = [dist[0]]
381
- channels_new = [channels[0]]
382
- idx = 0
383
- for j, s in enumerate(state):
384
- if s == '0':
385
- dist_new[idx] += dist[j+1]
386
- else:
387
- dist_new.append(dist[j+1])
388
- channels_new.append(channels[j+1])
389
- idx += 1
390
- res = sum([abs(d - dx) for d in dist_new])
391
- if res < residual:
392
- residual = res
393
- dist_equal = dist_new
394
- channels_equal = channels_new
395
- channels_equal.append(channels[-1])
396
- return channels_equal, dist_equal
397
-
398
-
399
- def _equally_spacing_2(channels, dist, dx):
400
- channels_equal = [channels[0]]
401
- dist_equal = []
402
- i = 0
403
- while i < len(dist):
404
- d = dist[i]
405
- if d < dx and i < len(dist) - 1:
406
- d1 = d + dist[i + 1]
407
- while d1 < dx and i < len(dist) - 2:
408
- d = d1
409
- i += 1
410
- d1 += dist[i + 1]
411
- if abs(d - dx) <= abs(d1 - dx):
412
- channels_equal.append(channels[i + 1])
413
- dist_equal.append(d)
414
- else:
415
- i += 1
416
- channels_equal.append(channels[i + 1])
417
- dist_equal.append(d1)
368
+ def _equally_spacing(dist, dx):
369
+ index = [[], []]
370
+ residual = [0, abs(dist[0]-dx)]
371
+ for i in range(2, len(dist)+1):
372
+ res = []
373
+ for j in range(i):
374
+ res.append(residual[j] + abs(dx - sum(dist[j:i])))
375
+ residual.append(min(res))
376
+ k = np.argmin(res)
377
+ if k > 0:
378
+ index.append(index[k] + [k])
418
379
  else:
419
- channels_equal.append(channels[i + 1])
420
- dist_equal.append(d)
421
- i += 1
380
+ index.append(index[k])
381
+ # print(index, residual)
382
+
383
+ return index[-1]
384
+
385
+
386
+ def channel_spacing(geometry, depth_info=False):
387
+ nch = len(geometry)
388
+ dist = np.zeros(nch - 1)
389
+ for i in range(nch - 1):
390
+ lon0, lat0 = geometry[i, :2]
391
+ lon1, lat1 = geometry[i+1, :2]
392
+ d = Geodesic.WGS84.Inverse(lat0, lon0, lat1, lon1)['s12']
393
+ if depth_info:
394
+ dist[i] = np.sqrt(d**2 + (geometry[i+1, 2] - geometry[i, 2]) ** 2)
395
+ else:
396
+ dist[i] = d
397
+
398
+ return dist
399
+
400
+
401
+ def closest_channel_to_point(geometry, point):
402
+ """
403
+ Find the channel number closest to a given point.
404
+
405
+ :param geometry: numpy.ndarray. It needs to consist of longitude, latitude
406
+ or channel number, longitude, latitude.
407
+ :param point: tuple or numpy.ndarray. A tuple consisting of latitude and longitude.
408
+ :return: int. The channel number closest to the given point.
409
+ """
410
+ if geometry.shape[1] == 2:
411
+ channels = np.arange(len(geometry)).astype(int)
412
+ else:
413
+ geometry = geometry[geometry[:, 0].argsort()]
414
+ channels = geometry[:, 0].astype(int)
415
+ geometry = geometry[:, 1:]
422
416
 
423
- return channels_equal, dist_equal
417
+ lat, lon = point
418
+ distances = np.array([
419
+ Geodesic.WGS84.Inverse(lat, lon, geometry[i, 0], geometry[i, 1])['s12']
420
+ for i in range(len(geometry))
421
+ ])
422
+ closest_index = np.argmin(distances)
423
+ return int(channels[closest_index])
424
424
 
425
425
 
426
426
  def equally_spaced_channels(geometry, dx, depth_info=False, verbose=False):
@@ -446,53 +446,36 @@ def equally_spaced_channels(geometry, dx, depth_info=False, verbose=False):
446
446
  channels = geometry[:, 0].astype(int)
447
447
  geometry = geometry[:, 1:]
448
448
 
449
- dist = np.zeros(nch - 1)
450
- for i in range(nch - 1):
451
- lon0, lat0 = geometry[i, :2]
452
- lon1, lat1 = geometry[i+1, :2]
453
- d = Geodesic.WGS84.Inverse(lat0, lon0, lat1, lon1)['s12']
454
- if depth_info:
455
- dist[i] = np.sqrt(d**2 + (geometry[i+1, 2] - geometry[i, 2]) ** 2)
456
- else:
457
- dist[i] = d
458
-
459
- channels_equal = [channels[0]]
460
- dist_equal = []
461
- channels_seg = []
462
- dist_seg = []
463
- flag = False
464
- for i in range(1, nch-1):
465
- if dist[i-1] + dist[i] <= dx * 1.5:
466
- channels_seg.append(channels[i-1])
467
- dist_seg.append(dist[i-1])
468
- else:
469
- if len(channels_seg):
470
- channels_seg.extend(channels[i-1:i+1])
471
- dist_seg.append(dist[i-1])
472
- channels_seg, dist_seg = _equally_spacing(channels_seg,
473
- dist_seg, dx)
474
- dist_equal.extend(dist_seg)
475
- channels_equal.extend(channels_seg[1:])
476
- channels_seg = []
477
- dist_seg = []
478
- flag = False
479
- else:
480
- if flag:
481
- channels_equal.append(channels[i-1])
482
- dist_equal.append(dist[i-1])
483
- else:
484
- flag = True
485
-
486
- if len(channels_seg):
487
- channels_seg.extend(channels[i:i+2])
488
- dist_seg.append(dist[i])
489
- channels_seg, dist_seg = _equally_spacing(channels_seg, dist_seg, dx)
490
- dist_equal.extend(dist_seg)
491
- channels_equal.extend(channels_seg[1:])
492
- else:
493
- channels_equal.extend(channels[-2:])
494
- dist_equal.extend(dist[-2:])
495
-
496
- if verbose:
497
- return channels_equal, dist_equal
498
- return channels_equal
449
+ dist = channel_spacing(geometry, depth_info=False)
450
+
451
+ s = 0
452
+ idx_equal = [0]
453
+ for i in range(nch-2):
454
+ if dist[i] > dx * 2:
455
+ e = i
456
+ if e == s + 1:
457
+ idx_equal.append(e)
458
+ elif e >= s + 2:
459
+ idx_equal.extend([idx + s for idx in
460
+ _equally_spacing(dist[s:e], dx)])
461
+ idx_equal.append(e)
462
+ s = e + 1
463
+ idx_equal.append(s)
464
+ elif dist[i] + dist[i+1] > dx * 1.5:
465
+ e = i + 1
466
+ if e == s + 1:
467
+ idx_equal.append(e)
468
+ elif e >= s + 2:
469
+ idx_equal.extend([idx + s for idx in
470
+ _equally_spacing(dist[s:e], dx)])
471
+ idx_equal.append(e)
472
+ s = e
473
+ e = nch - 1
474
+ if e == s + 1:
475
+ idx_equal.append(e)
476
+ elif e >= s + 2:
477
+ idx_equal.extend([idx + s for idx in
478
+ _equally_spacing(dist[s:e], dx)])
479
+ idx_equal.append(e)
480
+
481
+ return channels[idx_equal]
@@ -1,6 +1,6 @@
1
1
  # Purpose: Convert strain rate data to velocity
2
2
  # Author: Minzhe Hu
3
- # Date: 2024.6.8
3
+ # Date: 2024.3.10
4
4
  # Email: hmz2018@mail.ustc.edu.cn
5
5
  import numpy as np
6
6
  from numpy.fft import irfft2, ifftshift
@@ -103,7 +103,6 @@ def curvelet_conversion(data, dx, fs, pad=0.3, scale_begin=2, nbscales=None,
103
103
  :return: numpy.ndarray. Converted data.
104
104
  """
105
105
  if turning is not None:
106
- print(1)
107
106
  data_vel = np.zeros_like(data)
108
107
  start_ch = [0, *turning]
109
108
  end_ch = [*turning, len(data)]
@@ -1,6 +1,6 @@
1
1
  # Purpose: Some preprocess methods
2
2
  # Author: Minzhe Hu
3
- # Date: 2024.10.25
3
+ # Date: 2025.3.10
4
4
  # Email: hmz2018@mail.ustc.edu.cn
5
5
  import numpy as np
6
6
  from scipy.signal import detrend
@@ -23,13 +23,13 @@ def phase2strain(data, lam, e, n, gl):
23
23
  return data * (lam * 1e-9) / (e * 4 * np.pi * n * gl)
24
24
 
25
25
 
26
- def normalization(data, method='z-score', **kwargs):
26
+ def normalization(data, method='z-score'):
27
27
  """
28
28
  Normalize for each individual channel using Z-score method.
29
29
 
30
30
  :param data: numpy.ndarray. Data to normalize.
31
31
  :param method: str. Method for normalization, should be one of 'max',
32
- 'z-score', or 'one-bit'.
32
+ 'z-score', 'MAD' or 'one-bit'.
33
33
  :return: Normalized data.
34
34
  """
35
35
  if data.ndim == 1:
@@ -37,21 +37,21 @@ def normalization(data, method='z-score', **kwargs):
37
37
  elif data.ndim != 2:
38
38
  raise ValueError("Data should be 1-D or 2-D array")
39
39
 
40
- if method == 'max':
40
+ if method.lower() == 'max':
41
41
  amp = np.max(abs(data), 1, keepdims=True)
42
42
  amp[amp == 0] = amp[amp > 0].min()
43
43
  return data / amp
44
-
45
- if method == 'z-score':
44
+ elif method.lower() == 'z-score':
46
45
  mean = np.mean(data, axis=1, keepdims=True)
47
46
  std = np.std(data, axis=1, keepdims=True)
48
47
  std[std == 0] = std[std > 0].min()
49
- if 'p' in kwargs.keys():
50
- thresh = np.percentile(std, kwargs['p'])
51
- std[std > thresh] = thresh
52
48
  return (data - mean) / std
53
-
54
- if method == 'one-bit':
49
+ elif method.lower() == 'mad':
50
+ median = np.median(data, axis=1, keepdims=True)
51
+ mad = np.median(abs(data - median), axis=1, keepdims=True)
52
+ mad[mad == 0] = mad[mad > 0].min()
53
+ return (data - median) / mad
54
+ elif method.lower() == 'one-bit':
55
55
  return np.sign(data)
56
56
 
57
57
 
@@ -1,13 +1,13 @@
1
1
  # Purpose: Plot data
2
2
  # Author: Minzhe Hu
3
- # Date: 2024.11.8
3
+ # Date: 2025.1.6
4
4
  # Email: hmz2018@mail.ustc.edu.cn
5
5
  import numpy as np
6
6
  import matplotlib.pyplot as plt
7
7
  from collections.abc import Sequence
8
8
 
9
9
 
10
- def plot(data: np.ndarray, dx=None, fs=None, ax=None, obj='waveform', dpi=150,
10
+ 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, vmax=None, dB=False,
13
13
  xmode='distance', tmode='time', xlim=None, ylim=None, xlog=False,
@@ -29,7 +29,9 @@ def plot(data: np.ndarray, dx=None, fs=None, ax=None, obj='waveform', dpi=150,
29
29
  :param title: str. The title of this axes.
30
30
  :param transpose: bool. Transpose the figure or not.
31
31
  :param t0, x0: The beginning of time and space.
32
- :param pick: Sequence of picked phases. Required if obj=='phasepick'.
32
+ :param pick: dictionary of sequence of picked phases. Key should be 'P' for
33
+ P phase, 'S' for S phase and 'N' for unknown phase type. Required if
34
+ obj=='phasepick'.
33
35
  :param f: Sequence of frequency. Required if obj is one of 'spectrum',
34
36
  'spectrogram', 'fk' or 'dispersion'.
35
37
  :param k: Wavenumber sequence. Required if obj=='fk'.
@@ -82,14 +84,18 @@ def plot(data: np.ndarray, dx=None, fs=None, ax=None, obj='waveform', dpi=150,
82
84
  extent = [x0 * 1e-3, (x0 + nch * dx) * 1e-3, t0 + nt / fs, t0]
83
85
 
84
86
  if obj == 'phasepick' and len(pick):
85
- pck = np.array(pick).astype(float)
86
- if xmode.lower() == 'distance':
87
- pck[:, 0] = (x0 + pck[:, 0] * dx) * 1e-3
88
- elif xmode.lower() == 'channel':
89
- pck[:, 0] = x0 + pck[:, 0]
90
- if tmode.lower() == 'sampling':
91
- pck[:, 1] = pck[:, 1] / fs
92
- ax.scatter(pck[:,0], t0 + pck[:,1], marker=',', s=0.1, c='black')
87
+ pick_color = {'P': 'r', 'S': 'b', 'N': 'k'}
88
+ for phase, pck in pick.items():
89
+ if len(pck):
90
+ pck = np.array(pck).astype(float)
91
+ if xmode.lower() == 'distance':
92
+ pck[:, 0] = (x0 + pck[:, 0] * dx) * 1e-3
93
+ elif xmode.lower() == 'channel':
94
+ pck[:, 0] = x0 + pck[:, 0]
95
+ if tmode.lower() == 'sampling':
96
+ pck[:, 1] = pck[:, 1] / fs
97
+ ax.scatter(pck[:,0], t0 + pck[:,1], marker=',', s=0.1,
98
+ c=pick_color[phase])
93
99
 
94
100
  elif obj in ['spectrum', 'spectrogram', 'fk', 'dispersion']:
95
101
  if isinstance(data[0,0], (complex, np.complex64)):
@@ -1,6 +1,6 @@
1
1
  # Purpose: Module for handling Collection objects.
2
2
  # Author: Minzhe Hu
3
- # Date: 2025.1.1
3
+ # Date: 2025.3.30
4
4
  # Email: hmz2018@mail.ustc.edu.cn
5
5
  import os
6
6
  import warnings
@@ -18,7 +18,8 @@ cascade_method = ['time_integration', 'time_differential', 'downsampling',
18
18
 
19
19
  class Collection(object):
20
20
  def __init__(self, fpath, ftype=None, flength=None, meta_from_file=True,
21
- timeinfo_format=None, timeinfo_from_basename=True, **kwargs):
21
+ timeinfo_slice=slice(None), timeinfo_format=None,
22
+ timeinfo_tz=None, timeinfo_from_basename=True, **kwargs):
22
23
  """
23
24
  :param fpath: str or Sequence of str. File path(s) containing data.
24
25
  :param ftype: None or str. None for automatic detection, or 'pkl',
@@ -28,7 +29,11 @@ class Collection(object):
28
29
  and gauge_length. True for extracting dt, dx, fs and gauge_length
29
30
  from first 2 file. 'all' for exracting and checking these metadata
30
31
  from all file.
31
- :param timeinfo_format: str or (slice, str). Format for extracting start
32
+ :param timeinfo_slice: slice. Slice for extracting start time from file
33
+ name.
34
+ :param timeinfo_format: str. Format for extracting start time from file
35
+ name.
36
+ :param timeinfo_tz: datetime.timezone. Time zone for extracting start
32
37
  time from file name.
33
38
  :param timeinfo_from_basename: bool. If True, timeinfo_format will use
34
39
  DASDateTime.strptime to basename of fpath.
@@ -63,7 +68,7 @@ class Collection(object):
63
68
  sec.gauge_length = None
64
69
  ftime.append(sec.start_time)
65
70
  metadata_list.append((sec.nch, sec.nt, sec.dx, sec.fs,
66
- sec.gauge_length))
71
+ sec.gauge_length, sec.duration))
67
72
 
68
73
  if len(set(metadata_list)) > 1:
69
74
  warnings.warn('More than one kind of setting detected.')
@@ -71,6 +76,8 @@ class Collection(object):
71
76
  for i, key in enumerate(['nch', 'nt', 'dx', 'fs', 'gauge_length']):
72
77
  if not hasattr(self, key):
73
78
  setattr(self, key, metadata[i])
79
+ if flength is None:
80
+ flength = metadata[-1]
74
81
  self.ftime = ftime
75
82
  elif meta_from_file:
76
83
  i = int(len(self.flist) > 1)
@@ -88,17 +95,22 @@ class Collection(object):
88
95
  setattr(self, key, metadata[i])
89
96
 
90
97
  if not hasattr(self, 'ftime'):
91
- if isinstance(timeinfo_format, tuple):
92
- timeinfo_slice, timeinfo_format = timeinfo_format
93
- else:
94
- timeinfo_slice = slice(None)
95
98
  if timeinfo_from_basename:
96
- self.ftime = [DASDateTime.strptime(
97
- os.path.basename(f)[timeinfo_slice], timeinfo_format)
98
- for f in self.flist]
99
+ flist_use = [os.path.basename(f) for f in self.flist]
99
100
  else:
101
+ flist_use = self.flist
102
+ if timeinfo_tz is None:
100
103
  self.ftime = [DASDateTime.strptime(f[timeinfo_slice],
101
- timeinfo_format) for f in self.flist]
104
+ timeinfo_format) for f in flist_use]
105
+ else:
106
+ if '%z' in timeinfo_format.lower():
107
+ self.ftime = [DASDateTime.strptime(f[timeinfo_slice],
108
+ timeinfo_format).astimezone(timeinfo_tz) for f in
109
+ flist_use]
110
+ else:
111
+ self.ftime = [DASDateTime.strptime(f[timeinfo_slice],
112
+ timeinfo_format).replace(tzinfo=timeinfo_tz) for f in
113
+ flist_use]
102
114
 
103
115
  self._sort()
104
116
  if flength is None:
@@ -211,6 +223,9 @@ class Collection(object):
211
223
  flist.append(self.flist[i])
212
224
  ftime.append(self.ftime[i])
213
225
 
226
+ if len(flist) == 0:
227
+ warnings.warn('Out of collection time range.')
228
+ return None
214
229
  if readsec:
215
230
  sec = read(flist[0], **kwargs)
216
231
  for f in flist[1:]:
@@ -218,10 +233,9 @@ class Collection(object):
218
233
  sec.trimming(tmin=stime, tmax=etime)
219
234
  return sec
220
235
  else:
221
- coll = self.copy()
222
- coll.flist = flist
223
- coll.ftime = ftime
224
- return coll
236
+ self.flist = flist
237
+ self.ftime = ftime
238
+ return self
225
239
 
226
240
  def _optimize_for_continuity(self, operations):
227
241
  method_list = []
@@ -231,8 +245,8 @@ class Collection(object):
231
245
  for opera in operations:
232
246
  method, kwargs = opera
233
247
  if method == 'downsampling':
234
- if hasattr(kwargs, 'lowpass_filter') and not\
235
- kwargs['lowpass_filter']:
248
+ if (hasattr(kwargs, 'lowpass_filter') and not\
249
+ kwargs['lowpass_filter']) or not hasattr(kwargs, 'tint'):
236
250
  method_list.append('downsampling')
237
251
  kwargs_list.append(kwargs)
238
252
  else:
@@ -271,6 +285,16 @@ class Collection(object):
271
285
  merge = len(self)
272
286
  for i in tqdm(range(0, len(self))):
273
287
  f = self[i]
288
+ if os.path.getsize(f) == 0:
289
+ for j, method in enumerate(method_list):
290
+ if method == 'time_integration':
291
+ kwargs_list[j]['c'] = 0
292
+ elif method == 'time_differential':
293
+ kwargs_list[j]['prepend'] = 0
294
+ elif method in ['bandpass', 'bandstop', 'lowpass',
295
+ 'highpass', 'lowpass_cheby_2']:
296
+ kwargs_list[j]['zi'] = 0
297
+ continue
274
298
  sec = read(f, ftype=self.ftype, **read_kwargs)
275
299
  for j, method in enumerate(method_list):
276
300
  if method in ['taper', 'cosine_taper']:
@@ -1,6 +1,6 @@
1
1
  # Purpose: Module for handling DASDateTime objects.
2
2
  # Author: Minzhe Hu
3
- # Date: 2024.11.18
3
+ # Date: 2025.3.29
4
4
  # Email: hmz2018@mail.ustc.edu.cn
5
5
  import time
6
6
  from typing import Iterable
@@ -48,10 +48,10 @@ class DASDateTime(datetime):
48
48
  def __gt__(self, other):
49
49
  return datetime.__gt__(*self._unify_tz(other))
50
50
 
51
- def _unify_tz(self, other):
52
- if self.tzinfo and not other.tzinfo:
51
+ def _unify_tz(self, other: datetime):
52
+ if self.tzinfo and (not other.tzinfo):
53
53
  return self, other.replace(tzinfo=self.tzinfo)
54
- elif not self.tzinfo and other.tzinfo:
54
+ elif (not self.tzinfo) and other.tzinfo:
55
55
  return self.replace(tzinfo=other.tzinfo), other
56
56
  return self, other
57
57
 
@@ -77,4 +77,26 @@ class DASDateTime(datetime):
77
77
 
78
78
  def to_obspy_UTCDateTime(self):
79
79
  from obspy import UTCDateTime
80
- return UTCDateTime(UTCDateTime(self.to_datetime()))
80
+ return UTCDateTime(UTCDateTime(self.to_datetime()))
81
+
82
+ @classmethod
83
+ def strptime(cls, date_string, format):
84
+ """
85
+ string, format -> new datetime parsed from a string
86
+ (like time.strptime()).
87
+ """
88
+ from _strptime import _strptime
89
+ tt, fraction, gmtoff_fraction = _strptime(date_string, format)
90
+ tzname, gmtoff = tt[-2:]
91
+ args = tt[:6] + (fraction,)
92
+ if gmtoff is not None:
93
+ tzdelta = timedelta(seconds=gmtoff, microseconds=gmtoff_fraction)
94
+ if tzname:
95
+ tz = timezone(tzdelta, tzname)
96
+ else:
97
+ tz = timezone(tzdelta)
98
+ args += (tz,)
99
+ elif tt[-3] == 0:
100
+ args += (utc,)
101
+
102
+ return cls(*args)