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/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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyckster
3
- Version: 26.1.2
3
+ Version: 26.1.3
4
4
  Summary: A PyQt5-based GUI for picking seismic traveltimes
5
5
  Home-page: https://gitlab.in2p3.fr/metis-geophysics/pyckster
6
6
  Author: Sylvain Pasquet
@@ -1,8 +1,8 @@
1
- pyckster/__init__.py,sha256=F3G3USiEB_Vqn16wt2oifz8ant0asmLootKPJWI3cmE,894
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=GZ0ENrOja_n3KlK_cRz6jEQJr66mAbWMov_-_rkMm4M,991206
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=uwAisERVqjk2LWVTz5qc7ru0M_rHZFoYmqOipZbpiNg,6051
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.2.dist-info/licenses/LICENCE,sha256=-uaAIm20JrJKoMdCdn2GlFQfNU4fbsHWK3eh4kIQ_Ec,35143
21
- pyckster-26.1.2.dist-info/METADATA,sha256=qYNt21jEX3swf7hVe9T3haQhGIkzAMaId3V5YWMFP7o,4067
22
- pyckster-26.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- pyckster-26.1.2.dist-info/entry_points.txt,sha256=yrOQx1wHi84rbxX_ZYtYaVcK3EeuRhHRQDZRc8mB0NI,100
24
- pyckster-26.1.2.dist-info/top_level.txt,sha256=eaihhwhEmlysgdZE4HmELFdSUwlXcMv90YorkjOXujQ,9
25
- pyckster-26.1.2.dist-info/RECORD,,
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,,