pyckster 26.1.2__py3-none-any.whl → 26.1.4__py3-none-any.whl
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.
- pyckster/__init__.py +2 -2
- pyckster/core.py +3041 -607
- pyckster/pick_io.py +13 -3
- pyckster/sw_utils.py +199 -0
- {pyckster-26.1.2.dist-info → pyckster-26.1.4.dist-info}/METADATA +13 -5
- {pyckster-26.1.2.dist-info → pyckster-26.1.4.dist-info}/RECORD +10 -11
- pyckster/pac_inversion.py +0 -785
- {pyckster-26.1.2.dist-info → pyckster-26.1.4.dist-info}/WHEEL +0 -0
- {pyckster-26.1.2.dist-info → pyckster-26.1.4.dist-info}/entry_points.txt +0 -0
- {pyckster-26.1.2.dist-info → pyckster-26.1.4.dist-info}/licenses/LICENCE +0 -0
- {pyckster-26.1.2.dist-info → pyckster-26.1.4.dist-info}/top_level.txt +0 -0
pyckster/pick_io.py
CHANGED
|
@@ -19,7 +19,7 @@ import numpy as np
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def save_picks_to_sgt(output_file, trace_positions, trace_elevations,
|
|
22
|
-
source_positions, source_elevations, picks, errors):
|
|
22
|
+
source_positions, source_elevations, picks, errors, geophone_mapping=None):
|
|
23
23
|
"""
|
|
24
24
|
Save picks to PyGimLi .sgt file format.
|
|
25
25
|
|
|
@@ -43,6 +43,9 @@ def save_picks_to_sgt(output_file, trace_positions, trace_elevations,
|
|
|
43
43
|
Nested list of pick times [source][trace]
|
|
44
44
|
errors : list of lists
|
|
45
45
|
Nested list of pick errors [source][trace]
|
|
46
|
+
geophone_mapping : dict, optional
|
|
47
|
+
Maps (shot_idx, trace_idx) -> sgt_geophone_number for order-matched picks.
|
|
48
|
+
If provided, uses these geophone numbers instead of deriving from positions.
|
|
46
49
|
|
|
47
50
|
Returns
|
|
48
51
|
-------
|
|
@@ -115,9 +118,16 @@ def save_picks_to_sgt(output_file, trace_positions, trace_elevations,
|
|
|
115
118
|
for j, pick in enumerate(pick_list):
|
|
116
119
|
if not np.isnan(pick):
|
|
117
120
|
error = errors[i][j]
|
|
118
|
-
#
|
|
121
|
+
# Determine geophone number to use
|
|
122
|
+
if geophone_mapping is not None and (i, j) in geophone_mapping:
|
|
123
|
+
# Use the mapped geophone number from order matching
|
|
124
|
+
geophone_num = geophone_mapping[(i, j)]
|
|
125
|
+
else:
|
|
126
|
+
# Use the trace index-based geophone number
|
|
127
|
+
geophone_num = trace_indices[j] + 1
|
|
128
|
+
# Write source index, geophone number, pick time, pick error
|
|
119
129
|
# Indices are 1-based in .sgt format
|
|
120
|
-
f.write(f"{source_indices[i] + 1}\t{
|
|
130
|
+
f.write(f"{source_indices[i] + 1}\t{geophone_num}\t{pick:.5f}\t{error:.5f}\n")
|
|
121
131
|
|
|
122
132
|
return n_picks
|
|
123
133
|
|
pyckster/sw_utils.py
CHANGED
|
@@ -1,10 +1,207 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
2
4
|
from scipy.fft import rfft, rfftfreq
|
|
3
5
|
from scipy.interpolate import interp1d
|
|
4
6
|
|
|
7
|
+
#%% ============================ Dispersion Curve I/O =============================================
|
|
8
|
+
|
|
9
|
+
### -----------------------------------------------------------------------------------------------
|
|
10
|
+
def read_pvc_file(file_path):
|
|
11
|
+
"""
|
|
12
|
+
Read a .pvc file containing dispersion curve data.
|
|
13
|
+
|
|
14
|
+
.pvc files are named following the pattern: {xmid}.M{mode}.pvc
|
|
15
|
+
Example: "12.5.M0.pvc" where 12.5 is the Xmid position and 0 is the mode number
|
|
16
|
+
|
|
17
|
+
File format:
|
|
18
|
+
- Column 1: Frequency (Hz)
|
|
19
|
+
- Column 2: Phase velocity (m/s)
|
|
20
|
+
- Column 3: Error (m/s) - optional
|
|
21
|
+
|
|
22
|
+
Parameters:
|
|
23
|
+
-----------
|
|
24
|
+
file_path : str
|
|
25
|
+
Path to the .pvc file
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
--------
|
|
29
|
+
dict
|
|
30
|
+
Dictionary containing:
|
|
31
|
+
- 'frequencies': numpy array of frequencies (Hz)
|
|
32
|
+
- 'velocities': numpy array of phase velocities (m/s)
|
|
33
|
+
- 'errors': numpy array of errors (m/s)
|
|
34
|
+
- 'xmid': float, extracted position from filename
|
|
35
|
+
- 'mode': str, mode number (e.g., 'M0', 'M1')
|
|
36
|
+
- 'filename': str, original filename
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
-------
|
|
40
|
+
ValueError
|
|
41
|
+
If file format is invalid or insufficient data points
|
|
42
|
+
FileNotFoundError
|
|
43
|
+
If file does not exist
|
|
44
|
+
"""
|
|
45
|
+
if not os.path.exists(file_path):
|
|
46
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
47
|
+
|
|
48
|
+
# Parse filename to get xmid and mode
|
|
49
|
+
filename = os.path.basename(file_path)
|
|
50
|
+
filename_no_ext = os.path.splitext(filename)[0]
|
|
51
|
+
|
|
52
|
+
# Extract mode information (M0, M1, etc.)
|
|
53
|
+
mode = "M0" # Default mode
|
|
54
|
+
mode_match = re.search(r'M(\d+)', filename_no_ext)
|
|
55
|
+
if mode_match:
|
|
56
|
+
mode = f"M{mode_match.group(1)}"
|
|
57
|
+
|
|
58
|
+
# Try different patterns for extracting xmid
|
|
59
|
+
xmid = None
|
|
60
|
+
|
|
61
|
+
# Pattern 1: just a number (e.g., "12.5.M0.pvc" -> xmid = 12.5)
|
|
62
|
+
try:
|
|
63
|
+
if '.' in filename_no_ext:
|
|
64
|
+
parts = filename_no_ext.split('.')
|
|
65
|
+
if len(parts) >= 2:
|
|
66
|
+
# Try to get xmid from first two parts, ignoring mode part
|
|
67
|
+
for i in range(len(parts)-1):
|
|
68
|
+
try:
|
|
69
|
+
xmid = float(f"{parts[i]}.{parts[i+1]}")
|
|
70
|
+
break
|
|
71
|
+
except ValueError:
|
|
72
|
+
continue
|
|
73
|
+
else:
|
|
74
|
+
xmid = float(filename_no_ext.replace(mode, ''))
|
|
75
|
+
except ValueError:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
# Pattern 2: look for "xmid_X.X" or "X.X_" patterns
|
|
79
|
+
if xmid is None:
|
|
80
|
+
patterns = [
|
|
81
|
+
r'xmid[_\-]?(\d+\.?\d*)',
|
|
82
|
+
r'(\d+\.?\d*)[_\-]?xmid',
|
|
83
|
+
r'curve[_\-]?(\d+\.?\d*)',
|
|
84
|
+
r'(\d+\.?\d*)[_\-]?curve',
|
|
85
|
+
r'(\d+\.?\d*)', # any number in the filename
|
|
86
|
+
r'^(\d+\.?\d*)' # just starts with a number
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
for pattern in patterns:
|
|
90
|
+
match = re.search(pattern, filename_no_ext, re.IGNORECASE)
|
|
91
|
+
if match:
|
|
92
|
+
try:
|
|
93
|
+
xmid = float(match.group(1))
|
|
94
|
+
break
|
|
95
|
+
except ValueError:
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
# If still no xmid, use None
|
|
99
|
+
if xmid is None:
|
|
100
|
+
print(f"Warning: Could not parse xmid from {filename}, using None")
|
|
101
|
+
|
|
102
|
+
# Read file - handle different formats (legacy vs modern)
|
|
103
|
+
# Legacy format: optional header with number of picks, then 2 columns (freq, vel)
|
|
104
|
+
# Modern format: 3 columns (freq, vel, error)
|
|
105
|
+
|
|
106
|
+
data = None
|
|
107
|
+
skip_rows = 0
|
|
108
|
+
|
|
109
|
+
# First, check for header with number of picks
|
|
110
|
+
with open(file_path, 'r') as f:
|
|
111
|
+
first_line = f.readline().strip()
|
|
112
|
+
# Check if first line is a single integer (legacy header)
|
|
113
|
+
try:
|
|
114
|
+
num_picks = int(first_line)
|
|
115
|
+
skip_rows = 1 # Skip header line
|
|
116
|
+
except ValueError:
|
|
117
|
+
# Not a header, start from beginning
|
|
118
|
+
skip_rows = 0
|
|
119
|
+
|
|
120
|
+
# Read file - handle both comma and space separated
|
|
121
|
+
try:
|
|
122
|
+
# Try comma separated first
|
|
123
|
+
data = np.loadtxt(file_path, delimiter=',', skiprows=skip_rows)
|
|
124
|
+
except:
|
|
125
|
+
try:
|
|
126
|
+
# Fall back to space separated
|
|
127
|
+
data = np.loadtxt(file_path, skiprows=skip_rows)
|
|
128
|
+
except Exception as e:
|
|
129
|
+
raise ValueError(f"Could not read {filename}: {str(e)}")
|
|
130
|
+
|
|
131
|
+
if len(data.shape) == 1:
|
|
132
|
+
data = data.reshape(1, -1) # Single row case
|
|
133
|
+
|
|
134
|
+
if data.shape[1] >= 3: # frequency, velocity, error
|
|
135
|
+
frequencies = data[:, 0]
|
|
136
|
+
velocities = data[:, 1]
|
|
137
|
+
errors = data[:, 2]
|
|
138
|
+
elif data.shape[1] >= 2: # frequency, velocity only (legacy format)
|
|
139
|
+
frequencies = data[:, 0]
|
|
140
|
+
velocities = data[:, 1]
|
|
141
|
+
# Calculate errors based on velocity-dependent uncertainty
|
|
142
|
+
# Use simple 5% rule as we don't have dx/Nx information from .pvc file
|
|
143
|
+
errors = 0.05 * velocities
|
|
144
|
+
# Apply minimum and maximum error constraints similar to lorentzian_error
|
|
145
|
+
errors = np.maximum(errors, 5.0) # Minimum 5 m/s
|
|
146
|
+
errors = np.minimum(errors, 0.4 * velocities) # Maximum 40% of velocity
|
|
147
|
+
else:
|
|
148
|
+
raise ValueError(f"{filename} has insufficient columns (need at least 2)")
|
|
149
|
+
|
|
150
|
+
# Validate data
|
|
151
|
+
if len(frequencies) < 3:
|
|
152
|
+
raise ValueError(f"{filename} has too few data points ({len(frequencies)}, need at least 3)")
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
'frequencies': np.array(frequencies),
|
|
156
|
+
'velocities': np.array(velocities),
|
|
157
|
+
'errors': np.array(errors),
|
|
158
|
+
'xmid': xmid,
|
|
159
|
+
'mode': mode,
|
|
160
|
+
'filename': filename
|
|
161
|
+
}
|
|
162
|
+
### -----------------------------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
### -----------------------------------------------------------------------------------------------
|
|
165
|
+
def write_pvc_file(file_path, frequencies, velocities, errors=None):
|
|
166
|
+
"""
|
|
167
|
+
Write a .pvc file containing dispersion curve data.
|
|
168
|
+
|
|
169
|
+
Parameters:
|
|
170
|
+
-----------
|
|
171
|
+
file_path : str
|
|
172
|
+
Path where the .pvc file will be saved
|
|
173
|
+
frequencies : array-like
|
|
174
|
+
Frequency values (Hz)
|
|
175
|
+
velocities : array-like
|
|
176
|
+
Phase velocity values (m/s)
|
|
177
|
+
errors : array-like, optional
|
|
178
|
+
Error values (m/s). If None, uses 5% of velocity as default
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
--------
|
|
182
|
+
None
|
|
183
|
+
"""
|
|
184
|
+
frequencies = np.array(frequencies)
|
|
185
|
+
velocities = np.array(velocities)
|
|
186
|
+
|
|
187
|
+
if errors is None:
|
|
188
|
+
errors = 0.05 * velocities
|
|
189
|
+
else:
|
|
190
|
+
errors = np.array(errors)
|
|
191
|
+
|
|
192
|
+
# Write the file
|
|
193
|
+
with open(file_path, 'w') as f:
|
|
194
|
+
for freq, vel, err in zip(frequencies, velocities, errors):
|
|
195
|
+
f.write(f"{freq:.6f} {vel:.6f} {err:.6f}\n")
|
|
196
|
+
#### -----------------------------------------------------------------------------------------------
|
|
197
|
+
|
|
5
198
|
#%% ============================ MASW functions =============================================
|
|
6
199
|
|
|
7
200
|
# From https://github.com/JoseCunhaTeixeira/PAC
|
|
201
|
+
# Licensed under the Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0) License.
|
|
202
|
+
# Copyright (c) 2020 José Cunha Teixeira
|
|
203
|
+
|
|
204
|
+
### -----------------------------------------------------------------------------------------------
|
|
8
205
|
def phase_shift(XT, si, offsets, vmin, vmax, dv, fmax, fmin=0):
|
|
9
206
|
"""
|
|
10
207
|
Constructs a FV dispersion diagram with the phase-shift method from Park et al. (1999)
|
|
@@ -90,6 +287,7 @@ def phase_shift(XT, si, offsets, vmin, vmax, dv, fmax, fmin=0):
|
|
|
90
287
|
|
|
91
288
|
|
|
92
289
|
return fs, vs, FV
|
|
290
|
+
### -----------------------------------------------------------------------------------------------
|
|
93
291
|
|
|
94
292
|
### -----------------------------------------------------------------------------------------------
|
|
95
293
|
def resamp_wavelength(f, v):
|
|
@@ -167,3 +365,4 @@ def arange(start, stop, step):
|
|
|
167
365
|
"""
|
|
168
366
|
num_steps = int(round((stop - start) / step)) + 1 # Compute exact number of steps
|
|
169
367
|
return np.linspace(start, stop, num_steps)
|
|
368
|
+
### -----------------------------------------------------------------------------------------------
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyckster
|
|
3
|
-
Version: 26.1.
|
|
4
|
-
Summary: A PyQt5-based GUI for
|
|
3
|
+
Version: 26.1.4
|
|
4
|
+
Summary: A PyQt5-based GUI for the processing and analysis of active near-surface seismic data
|
|
5
5
|
Home-page: https://gitlab.in2p3.fr/metis-geophysics/pyckster
|
|
6
6
|
Author: Sylvain Pasquet
|
|
7
7
|
Author-email: sylvain.pasquet@sorbonne-universite.fr
|
|
@@ -38,9 +38,17 @@ Dynamic: summary
|
|
|
38
38
|
[](https://pepy.tech/projects/pyckster)
|
|
39
39
|

|
|
40
40
|
|
|
41
|
-
PyCKSTER is an open-source PyQt5-based GUI for
|
|
41
|
+
PyCKSTER is an open-source PyQt5-based GUI for processing and analysis of active near-surface seismic data, with a focus on traveltime picking and surface wave dispersion analysis.
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
**Core Features:**
|
|
44
|
+
- **File I/O**: Read and write seismic files in SEG2, SEGY, and Seismic Unix (SU) formats
|
|
45
|
+
- **Data Editing**: Edit headers information (source and trace coordinates, topography integration, delay, ffid) and traces (move, swap, mute, delete)
|
|
46
|
+
- **Traveltime Analysis**: Interactive picking with multiple visualization options (source/geophone diagrams, hodochrones) and direct inversion using [pyGIMLi](https://www.pygimli.org)
|
|
47
|
+
- **Surface Wave Analysis**: Compute dispersion images using phase-shift transform, simple windowing with interactive picking capabilities, and import dispersion curves picked with MATLAB package [SWIP](https://github.com/spasquet/SWIP)
|
|
48
|
+
|
|
49
|
+
**Coming Soon**: Advanced dispersion windowing and stacking, surface wave dispersion inversion
|
|
50
|
+
|
|
51
|
+
Picked traveltimes are saved in pyGIMLi's unified format for seamless subsurface velocity model reconstruction.
|
|
44
52
|
|
|
45
53
|
## Installation
|
|
46
54
|
|
|
@@ -98,7 +106,7 @@ PyCKSTER is currently developped by [Sylvain Pasquet](https://orcid.org/0000-000
|
|
|
98
106
|
|
|
99
107
|
|
|
100
108
|
*CNRS, Sorbonne Université*\
|
|
101
|
-
*UAR 3455 OSU
|
|
109
|
+
*UAR 3455 OSU OMA TERRA*\
|
|
102
110
|
*UMR 7619 METIS*
|
|
103
111
|
|
|
104
112
|
|
|
@@ -1,25 +1,24 @@
|
|
|
1
|
-
pyckster/__init__.py,sha256=
|
|
1
|
+
pyckster/__init__.py,sha256=qjcLpo1XCunhdQGCNfwOOsbraTpOFsQjkDRcYsdbZE4,905
|
|
2
2
|
pyckster/__main__.py,sha256=zv3AGVKorKo2tgWOEIcVnkDbp15eepSqka3IoWH_adU,406
|
|
3
3
|
pyckster/auto_picking.py,sha256=fyZiOj0Ib-SB_oxsKnUszECHbOjo4JE23JVQILGYZco,12754
|
|
4
4
|
pyckster/bayesian_inversion.py,sha256=kdnKOlAZ0JlYLipuFDHlwS7dU8LtI-0aMb90bYpEHhE,163523
|
|
5
|
-
pyckster/core.py,sha256=
|
|
5
|
+
pyckster/core.py,sha256=Q8d1Bmk-Gu8PABYMRemu8OzWMNKjuDxnreNr_liPrGc,1114480
|
|
6
6
|
pyckster/inversion_app.py,sha256=ovM44oYBFsvfKxO7rjjThUhkJnLDLZZ0R6ZVp-5r66E,60676
|
|
7
7
|
pyckster/inversion_manager.py,sha256=P8i1fqUJKMWkd-9PoDmNtmQuKglGKTeSuptUUA57D-8,15393
|
|
8
8
|
pyckster/inversion_visualizer.py,sha256=vfKZIoJzKawbaEv29NsYYIGnWLDQCGef5bM2vY1aCBo,22135
|
|
9
9
|
pyckster/ipython_console.py,sha256=tZyyoiXCjCl7ozxOj_h-YR4eGjoC4kpKe7nZ48eUAJc,9313
|
|
10
10
|
pyckster/mpl_export.py,sha256=_WqPo9l9ABiSoU0ukLfm4caGV1-FKKbXjt8SoBHTR30,12346
|
|
11
11
|
pyckster/obspy_utils.py,sha256=01fNI9ryIYuiGOl4NR0J9C_xXupcnsBb1mLSz1Qo63A,20569
|
|
12
|
-
pyckster/
|
|
13
|
-
pyckster/pick_io.py,sha256=uCre4o7vUYMOkk0PMAZOqB7Td6UNXWoLlfX1qstQ_Ic,17340
|
|
12
|
+
pyckster/pick_io.py,sha256=1svAzh1g73zEsjHnIy1ruEOrCC-vVMQnicXim3oWFa0,18027
|
|
14
13
|
pyckster/pyqtgraph_utils.py,sha256=PAeE3n_wz7skHOC5eLnkFczbie7diVH1xvuL8jtJ4T8,6049
|
|
15
14
|
pyckster/surface_wave_analysis.py,sha256=97BrDA-n5AZp89NdxQ2ekZPaCErMc7v8C6GmD5KTi-4,102695
|
|
16
15
|
pyckster/surface_wave_profiling.py,sha256=L9KidhKmfGvVoPZjf6us3c49VB7VPB_VcsDqRx45OYI,315401
|
|
17
|
-
pyckster/sw_utils.py,sha256=
|
|
16
|
+
pyckster/sw_utils.py,sha256=in7zrLXCMDrNjuDmWecZdYBzHUv4pYm_bTdf3bGJHBk,13184
|
|
18
17
|
pyckster/tab_factory.py,sha256=NlCIC6F8BrEu7a8BYOJJdWy5ftpX_zKDLj7SbcwBbh8,14519
|
|
19
18
|
pyckster/visualization_utils.py,sha256=bgODn21NAQx1FOMPj91kdDd0szKOgUyfZ3cQlyu2PF8,47947
|
|
20
|
-
pyckster-26.1.
|
|
21
|
-
pyckster-26.1.
|
|
22
|
-
pyckster-26.1.
|
|
23
|
-
pyckster-26.1.
|
|
24
|
-
pyckster-26.1.
|
|
25
|
-
pyckster-26.1.
|
|
19
|
+
pyckster-26.1.4.dist-info/licenses/LICENCE,sha256=-uaAIm20JrJKoMdCdn2GlFQfNU4fbsHWK3eh4kIQ_Ec,35143
|
|
20
|
+
pyckster-26.1.4.dist-info/METADATA,sha256=sYD2qSzyJfJkXKXhFQl8IGFmPVqFU5EbayFYxqSnSgw,4552
|
|
21
|
+
pyckster-26.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
22
|
+
pyckster-26.1.4.dist-info/entry_points.txt,sha256=yrOQx1wHi84rbxX_ZYtYaVcK3EeuRhHRQDZRc8mB0NI,100
|
|
23
|
+
pyckster-26.1.4.dist-info/top_level.txt,sha256=eaihhwhEmlysgdZE4HmELFdSUwlXcMv90YorkjOXujQ,9
|
|
24
|
+
pyckster-26.1.4.dist-info/RECORD,,
|