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/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
- # Write source index, trace index, pick time, pick error
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{trace_indices[j] + 1}\t{pick:.5f}\t{error:.5f}\n")
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.2
4
- Summary: A PyQt5-based GUI for picking seismic traveltimes
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
  [![PyPI Downloads](https://static.pepy.tech/badge/pyckster)](https://pepy.tech/projects/pyckster)
39
39
  ![PyPI - Downloads](https://img.shields.io/pypi/dm/PyCKSTER)
40
40
 
41
- PyCKSTER is an open-source PyQt5-based GUI for picking seismic traveltimes. It reads seismic files in SEG2, SEGY and Seismic Unix (SU) formats. Picked traveltimes are saved in [pyGIMLi](https://www.pygimli.org)'s unified format so they can easily be inverted to reconstruct subsurface velocity models.
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
- PyCKSTER can read SEG2, SEGY and Seismic Unix (SU) files (although SEG2 headers are not read correctly yet). You can import source and geophone elevation from csv files. You can also update headers information (ffid, source and trace coordinates, delay), and save shots with these updated headers in SEGY and SU formats.
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 ECCE TERRA*\
109
+ *UAR 3455 OSU OMA TERRA*\
102
110
  *UMR 7619 METIS*
103
111
 
104
112
 
@@ -1,25 +1,24 @@
1
- pyckster/__init__.py,sha256=F3G3USiEB_Vqn16wt2oifz8ant0asmLootKPJWI3cmE,894
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=GZ0ENrOja_n3KlK_cRz6jEQJr66mAbWMov_-_rkMm4M,991206
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/pac_inversion.py,sha256=9624dJvEsvJmYbgVFFg5FeaAg4yUfiXTTwrrAHRzdcs,30076
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=uwAisERVqjk2LWVTz5qc7ru0M_rHZFoYmqOipZbpiNg,6051
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.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,,
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,,