pyckster 26.1.2__py3-none-any.whl → 26.1.3__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 +1 -1
- pyckster/core.py +1429 -243
- pyckster/sw_utils.py +191 -0
- {pyckster-26.1.2.dist-info → pyckster-26.1.3.dist-info}/METADATA +1 -1
- {pyckster-26.1.2.dist-info → pyckster-26.1.3.dist-info}/RECORD +9 -9
- {pyckster-26.1.2.dist-info → pyckster-26.1.3.dist-info}/WHEEL +0 -0
- {pyckster-26.1.2.dist-info → pyckster-26.1.3.dist-info}/entry_points.txt +0 -0
- {pyckster-26.1.2.dist-info → pyckster-26.1.3.dist-info}/licenses/LICENCE +0 -0
- {pyckster-26.1.2.dist-info → pyckster-26.1.3.dist-info}/top_level.txt +0 -0
pyckster/sw_utils.py
CHANGED
|
@@ -1,7 +1,198 @@
|
|
|
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
|
+
def read_pvc_file(file_path):
|
|
10
|
+
"""
|
|
11
|
+
Read a .pvc file containing dispersion curve data.
|
|
12
|
+
|
|
13
|
+
.pvc files are named following the pattern: {xmid}.M{mode}.pvc
|
|
14
|
+
Example: "12.5.M0.pvc" where 12.5 is the Xmid position and 0 is the mode number
|
|
15
|
+
|
|
16
|
+
File format:
|
|
17
|
+
- Column 1: Frequency (Hz)
|
|
18
|
+
- Column 2: Phase velocity (m/s)
|
|
19
|
+
- Column 3: Error (m/s) - optional
|
|
20
|
+
|
|
21
|
+
Parameters:
|
|
22
|
+
-----------
|
|
23
|
+
file_path : str
|
|
24
|
+
Path to the .pvc file
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
--------
|
|
28
|
+
dict
|
|
29
|
+
Dictionary containing:
|
|
30
|
+
- 'frequencies': numpy array of frequencies (Hz)
|
|
31
|
+
- 'velocities': numpy array of phase velocities (m/s)
|
|
32
|
+
- 'errors': numpy array of errors (m/s)
|
|
33
|
+
- 'xmid': float, extracted position from filename
|
|
34
|
+
- 'mode': str, mode number (e.g., 'M0', 'M1')
|
|
35
|
+
- 'filename': str, original filename
|
|
36
|
+
|
|
37
|
+
Raises:
|
|
38
|
+
-------
|
|
39
|
+
ValueError
|
|
40
|
+
If file format is invalid or insufficient data points
|
|
41
|
+
FileNotFoundError
|
|
42
|
+
If file does not exist
|
|
43
|
+
"""
|
|
44
|
+
if not os.path.exists(file_path):
|
|
45
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
46
|
+
|
|
47
|
+
# Parse filename to get xmid and mode
|
|
48
|
+
filename = os.path.basename(file_path)
|
|
49
|
+
filename_no_ext = os.path.splitext(filename)[0]
|
|
50
|
+
|
|
51
|
+
# Extract mode information (M0, M1, etc.)
|
|
52
|
+
mode = "M0" # Default mode
|
|
53
|
+
mode_match = re.search(r'M(\d+)', filename_no_ext)
|
|
54
|
+
if mode_match:
|
|
55
|
+
mode = f"M{mode_match.group(1)}"
|
|
56
|
+
|
|
57
|
+
# Try different patterns for extracting xmid
|
|
58
|
+
xmid = None
|
|
59
|
+
|
|
60
|
+
# Pattern 1: just a number (e.g., "12.5.M0.pvc" -> xmid = 12.5)
|
|
61
|
+
try:
|
|
62
|
+
if '.' in filename_no_ext:
|
|
63
|
+
parts = filename_no_ext.split('.')
|
|
64
|
+
if len(parts) >= 2:
|
|
65
|
+
# Try to get xmid from first two parts, ignoring mode part
|
|
66
|
+
for i in range(len(parts)-1):
|
|
67
|
+
try:
|
|
68
|
+
xmid = float(f"{parts[i]}.{parts[i+1]}")
|
|
69
|
+
break
|
|
70
|
+
except ValueError:
|
|
71
|
+
continue
|
|
72
|
+
else:
|
|
73
|
+
xmid = float(filename_no_ext.replace(mode, ''))
|
|
74
|
+
except ValueError:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
# Pattern 2: look for "xmid_X.X" or "X.X_" patterns
|
|
78
|
+
if xmid is None:
|
|
79
|
+
patterns = [
|
|
80
|
+
r'xmid[_\-]?(\d+\.?\d*)',
|
|
81
|
+
r'(\d+\.?\d*)[_\-]?xmid',
|
|
82
|
+
r'curve[_\-]?(\d+\.?\d*)',
|
|
83
|
+
r'(\d+\.?\d*)[_\-]?curve',
|
|
84
|
+
r'(\d+\.?\d*)', # any number in the filename
|
|
85
|
+
r'^(\d+\.?\d*)' # just starts with a number
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
for pattern in patterns:
|
|
89
|
+
match = re.search(pattern, filename_no_ext, re.IGNORECASE)
|
|
90
|
+
if match:
|
|
91
|
+
try:
|
|
92
|
+
xmid = float(match.group(1))
|
|
93
|
+
break
|
|
94
|
+
except ValueError:
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
# If still no xmid, use None
|
|
98
|
+
if xmid is None:
|
|
99
|
+
print(f"Warning: Could not parse xmid from {filename}, using None")
|
|
100
|
+
|
|
101
|
+
# Read file - handle different formats (legacy vs modern)
|
|
102
|
+
# Legacy format: optional header with number of picks, then 2 columns (freq, vel)
|
|
103
|
+
# Modern format: 3 columns (freq, vel, error)
|
|
104
|
+
|
|
105
|
+
data = None
|
|
106
|
+
skip_rows = 0
|
|
107
|
+
|
|
108
|
+
# First, check for header with number of picks
|
|
109
|
+
with open(file_path, 'r') as f:
|
|
110
|
+
first_line = f.readline().strip()
|
|
111
|
+
# Check if first line is a single integer (legacy header)
|
|
112
|
+
try:
|
|
113
|
+
num_picks = int(first_line)
|
|
114
|
+
skip_rows = 1 # Skip header line
|
|
115
|
+
except ValueError:
|
|
116
|
+
# Not a header, start from beginning
|
|
117
|
+
skip_rows = 0
|
|
118
|
+
|
|
119
|
+
# Read file - handle both comma and space separated
|
|
120
|
+
try:
|
|
121
|
+
# Try comma separated first
|
|
122
|
+
data = np.loadtxt(file_path, delimiter=',', skiprows=skip_rows)
|
|
123
|
+
except:
|
|
124
|
+
try:
|
|
125
|
+
# Fall back to space separated
|
|
126
|
+
data = np.loadtxt(file_path, skiprows=skip_rows)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
raise ValueError(f"Could not read {filename}: {str(e)}")
|
|
129
|
+
|
|
130
|
+
if len(data.shape) == 1:
|
|
131
|
+
data = data.reshape(1, -1) # Single row case
|
|
132
|
+
|
|
133
|
+
if data.shape[1] >= 3: # frequency, velocity, error
|
|
134
|
+
frequencies = data[:, 0]
|
|
135
|
+
velocities = data[:, 1]
|
|
136
|
+
errors = data[:, 2]
|
|
137
|
+
elif data.shape[1] >= 2: # frequency, velocity only (legacy format)
|
|
138
|
+
frequencies = data[:, 0]
|
|
139
|
+
velocities = data[:, 1]
|
|
140
|
+
# Calculate errors based on velocity-dependent uncertainty
|
|
141
|
+
# Use simple 5% rule as we don't have dx/Nx information from .pvc file
|
|
142
|
+
errors = 0.05 * velocities
|
|
143
|
+
# Apply minimum and maximum error constraints similar to lorentzian_error
|
|
144
|
+
errors = np.maximum(errors, 5.0) # Minimum 5 m/s
|
|
145
|
+
errors = np.minimum(errors, 0.4 * velocities) # Maximum 40% of velocity
|
|
146
|
+
else:
|
|
147
|
+
raise ValueError(f"{filename} has insufficient columns (need at least 2)")
|
|
148
|
+
|
|
149
|
+
# Validate data
|
|
150
|
+
if len(frequencies) < 3:
|
|
151
|
+
raise ValueError(f"{filename} has too few data points ({len(frequencies)}, need at least 3)")
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
'frequencies': np.array(frequencies),
|
|
155
|
+
'velocities': np.array(velocities),
|
|
156
|
+
'errors': np.array(errors),
|
|
157
|
+
'xmid': xmid,
|
|
158
|
+
'mode': mode,
|
|
159
|
+
'filename': filename
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def write_pvc_file(file_path, frequencies, velocities, errors=None):
|
|
164
|
+
"""
|
|
165
|
+
Write a .pvc file containing dispersion curve data.
|
|
166
|
+
|
|
167
|
+
Parameters:
|
|
168
|
+
-----------
|
|
169
|
+
file_path : str
|
|
170
|
+
Path where the .pvc file will be saved
|
|
171
|
+
frequencies : array-like
|
|
172
|
+
Frequency values (Hz)
|
|
173
|
+
velocities : array-like
|
|
174
|
+
Phase velocity values (m/s)
|
|
175
|
+
errors : array-like, optional
|
|
176
|
+
Error values (m/s). If None, uses 5% of velocity as default
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
--------
|
|
180
|
+
None
|
|
181
|
+
"""
|
|
182
|
+
frequencies = np.array(frequencies)
|
|
183
|
+
velocities = np.array(velocities)
|
|
184
|
+
|
|
185
|
+
if errors is None:
|
|
186
|
+
errors = 0.05 * velocities
|
|
187
|
+
else:
|
|
188
|
+
errors = np.array(errors)
|
|
189
|
+
|
|
190
|
+
# Write the file
|
|
191
|
+
with open(file_path, 'w') as f:
|
|
192
|
+
for freq, vel, err in zip(frequencies, velocities, errors):
|
|
193
|
+
f.write(f"{freq:.6f} {vel:.6f} {err:.6f}\n")
|
|
194
|
+
|
|
195
|
+
|
|
5
196
|
#%% ============================ MASW functions =============================================
|
|
6
197
|
|
|
7
198
|
# From https://github.com/JoseCunhaTeixeira/PAC
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
pyckster/__init__.py,sha256=
|
|
1
|
+
pyckster/__init__.py,sha256=ABqVg3omK7TL4BKILg2OUzEMxd-gorQ3PhJd1upuTkw,894
|
|
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=jhLqcmgmbwrRmeVbvtzVoLUDTL-66Fg0Lchv24jgosw,1049035
|
|
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
|
|
@@ -14,12 +14,12 @@ pyckster/pick_io.py,sha256=uCre4o7vUYMOkk0PMAZOqB7Td6UNXWoLlfX1qstQ_Ic,17340
|
|
|
14
14
|
pyckster/pyqtgraph_utils.py,sha256=PAeE3n_wz7skHOC5eLnkFczbie7diVH1xvuL8jtJ4T8,6049
|
|
15
15
|
pyckster/surface_wave_analysis.py,sha256=97BrDA-n5AZp89NdxQ2ekZPaCErMc7v8C6GmD5KTi-4,102695
|
|
16
16
|
pyckster/surface_wave_profiling.py,sha256=L9KidhKmfGvVoPZjf6us3c49VB7VPB_VcsDqRx45OYI,315401
|
|
17
|
-
pyckster/sw_utils.py,sha256=
|
|
17
|
+
pyckster/sw_utils.py,sha256=37z9AAEN4Oxwj1v-XBlDJMnSFT9AMnwybYCs963Jrn8,12337
|
|
18
18
|
pyckster/tab_factory.py,sha256=NlCIC6F8BrEu7a8BYOJJdWy5ftpX_zKDLj7SbcwBbh8,14519
|
|
19
19
|
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.
|
|
20
|
+
pyckster-26.1.3.dist-info/licenses/LICENCE,sha256=-uaAIm20JrJKoMdCdn2GlFQfNU4fbsHWK3eh4kIQ_Ec,35143
|
|
21
|
+
pyckster-26.1.3.dist-info/METADATA,sha256=LSR-PwdQMJuytEeqO0-FrslYDlubNYoELilOSzUuNKw,4067
|
|
22
|
+
pyckster-26.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
23
|
+
pyckster-26.1.3.dist-info/entry_points.txt,sha256=yrOQx1wHi84rbxX_ZYtYaVcK3EeuRhHRQDZRc8mB0NI,100
|
|
24
|
+
pyckster-26.1.3.dist-info/top_level.txt,sha256=eaihhwhEmlysgdZE4HmELFdSUwlXcMv90YorkjOXujQ,9
|
|
25
|
+
pyckster-26.1.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|