pyseiskit 0.1.0__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.
- pyseiskit-0.1.0/.gitignore +34 -0
- pyseiskit-0.1.0/LICENSE +21 -0
- pyseiskit-0.1.0/PKG-INFO +43 -0
- pyseiskit-0.1.0/README.md +18 -0
- pyseiskit-0.1.0/demos/gain.ipynb +169 -0
- pyseiskit-0.1.0/demos/seismic_reader.py +76 -0
- pyseiskit-0.1.0/demos/wiggle.ipynb +209 -0
- pyseiskit-0.1.0/legacy/boto/BaseVisualization.py +104 -0
- pyseiskit-0.1.0/legacy/boto/WiggleDisplay.py +92 -0
- pyseiskit-0.1.0/legacy/boto/sufile.py +36 -0
- pyseiskit-0.1.0/legacy/boto/transforms.py +153 -0
- pyseiskit-0.1.0/legacy/display.sh +50 -0
- pyseiskit-0.1.0/legacy/landwave/DatasetFile.py +221 -0
- pyseiskit-0.1.0/legacy/landwave/wigglePatchesRenderFactory.py +128 -0
- pyseiskit-0.1.0/pyproject.toml +51 -0
- pyseiskit-0.1.0/pyseiskit/__init__.py +7 -0
- pyseiskit-0.1.0/pyseiskit/modules/__init__.py +0 -0
- pyseiskit-0.1.0/pyseiskit/modules/applyGains/__init__.py +0 -0
- pyseiskit-0.1.0/pyseiskit/modules/applyGains/adapters/__init__.py +1 -0
- pyseiskit-0.1.0/pyseiskit/modules/applyGains/adapters/clipAdapter.py +16 -0
- pyseiskit-0.1.0/pyseiskit/modules/applyGains/applyGains.py +42 -0
- pyseiskit-0.1.0/pyseiskit/modules/applyGains/gainStrategies.py +11 -0
- pyseiskit-0.1.0/pyseiskit/modules/clip/__init__.py +1 -0
- pyseiskit-0.1.0/pyseiskit/modules/clip/contracts/PercentileClip.py +19 -0
- pyseiskit-0.1.0/pyseiskit/modules/clip/contracts/__init__.py +1 -0
- pyseiskit-0.1.0/pyseiskit/modules/clip/percentileClip.py +31 -0
- pyseiskit-0.1.0/pyseiskit/modules/gain/__init__.py +2 -0
- pyseiskit-0.1.0/pyseiskit/modules/gain/agc.py +52 -0
- pyseiskit-0.1.0/pyseiskit/modules/gain/contracts/AutomaticGain.py +21 -0
- pyseiskit-0.1.0/pyseiskit/modules/gain/contracts/GenericGain.py +19 -0
- pyseiskit-0.1.0/pyseiskit/modules/gain/contracts/__init__.py +2 -0
- pyseiskit-0.1.0/pyseiskit/modules/gain/gagc.py +57 -0
- pyseiskit-0.1.0/pyseiskit/modules/palettes/PALETTES.py +12 -0
- pyseiskit-0.1.0/pyseiskit/modules/palettes/__init__.py +1 -0
- pyseiskit-0.1.0/pyseiskit/modules/palettes/getFadingPalette.py +19 -0
- pyseiskit-0.1.0/pyseiskit/modules/sourceData/__init__.py +3 -0
- pyseiskit-0.1.0/pyseiskit/modules/sourceData/rescaleDataForWiggle.py +52 -0
- pyseiskit-0.1.0/pyseiskit/modules/sourceData/wiggleLinesDataFactory.py +19 -0
- pyseiskit-0.1.0/pyseiskit/modules/sourceData/wigglePatchesDataFactory.py +126 -0
- pyseiskit-0.1.0/pyseiskit/py.typed +0 -0
- pyseiskit-0.1.0/uv.lock +556 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution / packaging
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
share/python-wheels/
|
|
20
|
+
*.egg-info/
|
|
21
|
+
.installed.cfg
|
|
22
|
+
*.egg
|
|
23
|
+
|
|
24
|
+
# Virtual environments
|
|
25
|
+
venv/
|
|
26
|
+
env/
|
|
27
|
+
ENV/
|
|
28
|
+
env.bak/
|
|
29
|
+
venv.bak/
|
|
30
|
+
|
|
31
|
+
# mock data
|
|
32
|
+
*.su
|
|
33
|
+
*.segy
|
|
34
|
+
*.sgy
|
pyseiskit-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PETROGEO
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pyseiskit-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pyseiskit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python library for seismic data processing and pre-visualization.
|
|
5
|
+
Project-URL: Homepage, https://github.com/PETROGEO-UFRN/pyseiskit
|
|
6
|
+
Project-URL: Repository, https://github.com/PETROGEO-UFRN/pyseiskit
|
|
7
|
+
Author-email: JorbFreire <jorbfreire@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: SU,data processing,geophysics,seismic,seismic unix,wiggle
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Requires-Dist: numpy
|
|
23
|
+
Requires-Dist: scipy
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# pyseiskit
|
|
27
|
+
|
|
28
|
+
A Python library for seismic data processing.
|
|
29
|
+
|
|
30
|
+
Includes *wiggle* helpers, wich can be used together with common plot libraries such as:
|
|
31
|
+
|
|
32
|
+
- Matplotlib
|
|
33
|
+
- Bokeh
|
|
34
|
+
- Plotly
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
```bash
|
|
38
|
+
pip install pyseiskit
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## [Available Demos](/demos)
|
|
42
|
+
- [Wiggle plotting with common libraries](/demos/wiggle.ipynb)
|
|
43
|
+
- [Gain methods](/demos/gain.ipynb)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# pyseiskit
|
|
2
|
+
|
|
3
|
+
A Python library for seismic data processing.
|
|
4
|
+
|
|
5
|
+
Includes *wiggle* helpers, wich can be used together with common plot libraries such as:
|
|
6
|
+
|
|
7
|
+
- Matplotlib
|
|
8
|
+
- Bokeh
|
|
9
|
+
- Plotly
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
```bash
|
|
13
|
+
pip install pyseiskit
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## [Available Demos](/demos)
|
|
17
|
+
- [Wiggle plotting with common libraries](/demos/wiggle.ipynb)
|
|
18
|
+
- [Gain methods](/demos/gain.ipynb)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "markdown",
|
|
5
|
+
"metadata": {},
|
|
6
|
+
"source": [
|
|
7
|
+
"# PySeis Gain & AGC Demo\n",
|
|
8
|
+
"This notebook demonstrates how to replicate Seismic Unix processing pipelines (like `sugain` and `suxwigb` clipping) using PySeis."
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"cell_type": "code",
|
|
13
|
+
"execution_count": null,
|
|
14
|
+
"metadata": {},
|
|
15
|
+
"outputs": [],
|
|
16
|
+
"source": [
|
|
17
|
+
"%pip install bokeh segyio"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"cell_type": "code",
|
|
22
|
+
"execution_count": null,
|
|
23
|
+
"metadata": {},
|
|
24
|
+
"outputs": [],
|
|
25
|
+
"source": [
|
|
26
|
+
"# Install pyseiskit from PyPI\n",
|
|
27
|
+
"%pip install pyseiskit\n",
|
|
28
|
+
"# Install for local development\n",
|
|
29
|
+
"# %pip install -e .."
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"cell_type": "markdown",
|
|
34
|
+
"metadata": {},
|
|
35
|
+
"source": [
|
|
36
|
+
"## 1A. Option 1: Create Simple Synthetic Data\n",
|
|
37
|
+
"Run this cell to generate a small 2D array representing 5 seismic traces, each with 100 time samples."
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"cell_type": "code",
|
|
42
|
+
"execution_count": null,
|
|
43
|
+
"metadata": {},
|
|
44
|
+
"outputs": [],
|
|
45
|
+
"source": [
|
|
46
|
+
"import numpy as np\n",
|
|
47
|
+
"\n",
|
|
48
|
+
"# 100 samples, 5 traces\n",
|
|
49
|
+
"data = np.random.randn(100, 5) \n",
|
|
50
|
+
"times = np.arange(100)\n",
|
|
51
|
+
"offsets = np.arange(5, dtype=float)\n"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"cell_type": "markdown",
|
|
56
|
+
"metadata": {},
|
|
57
|
+
"source": [
|
|
58
|
+
"## 1B. Option 2: Load Real `.su` or `.sgy` Data\n",
|
|
59
|
+
"Run this cell instead if you have the file `tac-204RL239.su` in the same folder as this notebook."
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"cell_type": "code",
|
|
64
|
+
"execution_count": null,
|
|
65
|
+
"metadata": {},
|
|
66
|
+
"outputs": [],
|
|
67
|
+
"source": [
|
|
68
|
+
"import segyio\n",
|
|
69
|
+
"from seismic_reader import read_seismic_file\n",
|
|
70
|
+
"\n",
|
|
71
|
+
"filename = 'tac-204RL239.su'\n",
|
|
72
|
+
"\n",
|
|
73
|
+
"# We only want to plot a single gather to avoid rendering thousands of traces.\n",
|
|
74
|
+
"# FieldRecord corresponds to the 'fldr' header in SU/SEGY.\n",
|
|
75
|
+
"gather_key = segyio.TraceField.FieldRecord\n",
|
|
76
|
+
"gather_index = 50 # Change this to a valid fldr number from your dataset!\n",
|
|
77
|
+
"\n",
|
|
78
|
+
"try:\n",
|
|
79
|
+
" data, offsets, times = read_seismic_file(filename, gather_key=gather_key, gather_index=gather_index)\n",
|
|
80
|
+
" print(f\"Loaded {data.shape[1]} traces for fldr {gather_index} with {data.shape[0]} samples each.\")\n",
|
|
81
|
+
"except FileNotFoundError:\n",
|
|
82
|
+
" print(f\"File '{filename}' not found. Please place it in the 'demos' folder or stick to Option 1A.\")\n",
|
|
83
|
+
"except ValueError as e:\n",
|
|
84
|
+
" print(f\"Error: {e}\")"
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"cell_type": "markdown",
|
|
89
|
+
"metadata": {},
|
|
90
|
+
"source": [
|
|
91
|
+
"## 2. Process Data for Wiggles\n",
|
|
92
|
+
"Regardless of how you loaded the data above, we now pass it through PySeis."
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"cell_type": "code",
|
|
97
|
+
"execution_count": null,
|
|
98
|
+
"metadata": {},
|
|
99
|
+
"outputs": [],
|
|
100
|
+
"source": [
|
|
101
|
+
"from pyseiskit import sourceData\n",
|
|
102
|
+
"from pyseiskit import gain\n",
|
|
103
|
+
"from pyseiskit import clip\n",
|
|
104
|
+
"\n",
|
|
105
|
+
"# Replicating SU pipeline: suwind | sugain agc=1 wagc=1.0 | suxwigb perc=99\n",
|
|
106
|
+
"dt = times[1] - times[0]\n",
|
|
107
|
+
"\n",
|
|
108
|
+
"# 1. Apply AGC (Optional, just like sugain agc=1 wagc=1.0)\n",
|
|
109
|
+
"data_gained = gain.applyAGC(data, wagc=1.0, intervalTimeSamples=dt)\n",
|
|
110
|
+
"# data_gained = data # uncomment above to apply AGC\n",
|
|
111
|
+
"\n",
|
|
112
|
+
"# 2. Apply Percentile Clipping (Like suxwigb perc=99 flat-topping)\n",
|
|
113
|
+
"data_clipped = clip.applyPercentileClip(data_gained, percentile=99.0)\n",
|
|
114
|
+
"\n",
|
|
115
|
+
"# 3. Scale physical dimensions (overlap=1.0 is SU default xcur/overlap)\n",
|
|
116
|
+
"scaled_data = sourceData.rescaleDataForWiggle(data_clipped, offsets, overlap=1.0)\n",
|
|
117
|
+
"\n",
|
|
118
|
+
"# 4. Generate geometry\n",
|
|
119
|
+
"line_data = sourceData.wiggleLinesDataFactory(scaled_data, offsets, times)\n",
|
|
120
|
+
"patch_data = sourceData.wigglePatchesDataFactory(scaled_data, offsets, times, fill_mode='positive')"
|
|
121
|
+
]
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"cell_type": "markdown",
|
|
125
|
+
"metadata": {},
|
|
126
|
+
"source": [
|
|
127
|
+
"## 3. Visualize Results\n",
|
|
128
|
+
"Here we use Bokeh to visualize the gained results interactively."
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"cell_type": "code",
|
|
133
|
+
"execution_count": null,
|
|
134
|
+
"metadata": {},
|
|
135
|
+
"outputs": [],
|
|
136
|
+
"source": [
|
|
137
|
+
"from bokeh.plotting import figure, show, output_notebook\n",
|
|
138
|
+
"output_notebook()\n",
|
|
139
|
+
"\n",
|
|
140
|
+
"bokeh_plot = figure(width=600, height=400, y_range=(times[-1], times[0]))\n",
|
|
141
|
+
"bokeh_plot.multi_line(**line_data, color='black', line_width=0.5)\n",
|
|
142
|
+
"bokeh_plot.patches(**patch_data, color='black', line_width=0)\n",
|
|
143
|
+
"\n",
|
|
144
|
+
"show(bokeh_plot)"
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
],
|
|
148
|
+
"metadata": {
|
|
149
|
+
"kernelspec": {
|
|
150
|
+
"display_name": "pyseis (3.12.2)",
|
|
151
|
+
"language": "python",
|
|
152
|
+
"name": "python3"
|
|
153
|
+
},
|
|
154
|
+
"language_info": {
|
|
155
|
+
"codemirror_mode": {
|
|
156
|
+
"name": "ipython",
|
|
157
|
+
"version": 3
|
|
158
|
+
},
|
|
159
|
+
"file_extension": ".py",
|
|
160
|
+
"mimetype": "text/x-python",
|
|
161
|
+
"name": "python",
|
|
162
|
+
"nbconvert_exporter": "python",
|
|
163
|
+
"pygments_lexer": "ipython3",
|
|
164
|
+
"version": "3.12.2"
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
"nbformat": 4,
|
|
168
|
+
"nbformat_minor": 4
|
|
169
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import segyio
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
def read_seismic_file(filepath, gather_key=None, gather_index=None):
|
|
6
|
+
"""
|
|
7
|
+
Reads a .su or .sgy file and returns the data, times, and offsets.
|
|
8
|
+
If gather_key and gather_index are provided, it only loads traces
|
|
9
|
+
matching that specific gather (e.g. a specific fldr or cdp).
|
|
10
|
+
"""
|
|
11
|
+
path = Path(filepath)
|
|
12
|
+
if not path.is_file():
|
|
13
|
+
raise FileNotFoundError(f"File not found: {path}")
|
|
14
|
+
|
|
15
|
+
is_su = path.suffix.lower() == '.su'
|
|
16
|
+
|
|
17
|
+
# Open the file depending on its extension
|
|
18
|
+
if is_su:
|
|
19
|
+
dataset = segyio.su.open(str(path), "r", ignore_geometry=True, endian='little')
|
|
20
|
+
else:
|
|
21
|
+
dataset = segyio.open(str(path), "r", ignore_geometry=True)
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
if gather_key is not None and gather_index is not None:
|
|
25
|
+
# Group traces by their gather key (e.g. all traces sharing the same fldr)
|
|
26
|
+
key_to_trace_indices = {}
|
|
27
|
+
for i in range(dataset.tracecount):
|
|
28
|
+
val = int(dataset.header[i][gather_key])
|
|
29
|
+
if val not in key_to_trace_indices:
|
|
30
|
+
key_to_trace_indices[val] = []
|
|
31
|
+
key_to_trace_indices[val].append(i)
|
|
32
|
+
|
|
33
|
+
available_gathers = sorted(key_to_trace_indices.keys())
|
|
34
|
+
if not available_gathers:
|
|
35
|
+
raise ValueError(f"No valid values found for header key {gather_key}")
|
|
36
|
+
|
|
37
|
+
# Safely select the requested gather index
|
|
38
|
+
safe_index = max(0, min(gather_index, len(available_gathers) - 1))
|
|
39
|
+
selected_gather_val = available_gathers[safe_index]
|
|
40
|
+
trace_indices = key_to_trace_indices[selected_gather_val]
|
|
41
|
+
|
|
42
|
+
print(f"Selected gather {safe_index} (Header value: {selected_gather_val}) containing {len(trace_indices)} traces.")
|
|
43
|
+
|
|
44
|
+
raw_traces = np.stack([dataset.trace[i] for i in trace_indices])
|
|
45
|
+
|
|
46
|
+
offsets = np.array([
|
|
47
|
+
dataset.header[i][segyio.TraceField.offset]
|
|
48
|
+
for i in trace_indices
|
|
49
|
+
], dtype=float)
|
|
50
|
+
|
|
51
|
+
# If the offset header is missing or all zero, fallback to trace indices
|
|
52
|
+
if len(offsets) > 1 and np.all(offsets == offsets[0]):
|
|
53
|
+
print("Warning: Trace offsets are all identical (likely missing header). Falling back to trace indices.")
|
|
54
|
+
offsets = np.arange(len(trace_indices), dtype=float)
|
|
55
|
+
else:
|
|
56
|
+
# Load all traces into memory
|
|
57
|
+
raw_traces = dataset.trace.raw[:]
|
|
58
|
+
offsets = np.arange(raw_traces.shape[0], dtype=float)
|
|
59
|
+
|
|
60
|
+
# We transpose so that data is (samples x traces) for wiggle plotting
|
|
61
|
+
data = raw_traces.T
|
|
62
|
+
|
|
63
|
+
# Calculate times based on sample interval
|
|
64
|
+
if is_su:
|
|
65
|
+
dt_micros = dataset.header[0][segyio.TraceField.TRACE_SAMPLE_INTERVAL]
|
|
66
|
+
else:
|
|
67
|
+
dt_micros = dataset.bin[segyio.BinField.Interval]
|
|
68
|
+
|
|
69
|
+
dt_seconds = dt_micros / 1e6
|
|
70
|
+
num_samples = len(dataset.samples)
|
|
71
|
+
times = np.arange(num_samples) * dt_seconds
|
|
72
|
+
|
|
73
|
+
return data, offsets, times
|
|
74
|
+
|
|
75
|
+
finally:
|
|
76
|
+
dataset.close()
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "markdown",
|
|
5
|
+
"metadata": {},
|
|
6
|
+
"source": [
|
|
7
|
+
"# PySeis Wiggle Plots Demo\n",
|
|
8
|
+
"This notebook demonstrates how to plot wiggle traces using PySeis with Matplotlib, Bokeh, and Plotly."
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"cell_type": "code",
|
|
13
|
+
"execution_count": null,
|
|
14
|
+
"metadata": {},
|
|
15
|
+
"outputs": [],
|
|
16
|
+
"source": [
|
|
17
|
+
"%pip install matplotlib bokeh plotly segyio"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"cell_type": "code",
|
|
22
|
+
"execution_count": null,
|
|
23
|
+
"metadata": {},
|
|
24
|
+
"outputs": [],
|
|
25
|
+
"source": [
|
|
26
|
+
"# Install pyseiskit from PyPI\n",
|
|
27
|
+
"%pip install pyseiskit\n",
|
|
28
|
+
"# Install for local development\n",
|
|
29
|
+
"# %pip install -e .."
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"cell_type": "markdown",
|
|
34
|
+
"metadata": {},
|
|
35
|
+
"source": [
|
|
36
|
+
"## 1A. Option 1: Create Simple Synthetic Data\n",
|
|
37
|
+
"Run this cell to generate a small 2D array representing 5 seismic traces, each with 100 time samples."
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"cell_type": "code",
|
|
42
|
+
"execution_count": null,
|
|
43
|
+
"metadata": {},
|
|
44
|
+
"outputs": [],
|
|
45
|
+
"source": [
|
|
46
|
+
"import numpy as np\n",
|
|
47
|
+
"\n",
|
|
48
|
+
"# 100 samples, 5 traces\n",
|
|
49
|
+
"data = np.random.randn(100, 5) \n",
|
|
50
|
+
"times = np.arange(100)\n",
|
|
51
|
+
"offsets = np.arange(5, dtype=float)\n"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"cell_type": "markdown",
|
|
56
|
+
"metadata": {},
|
|
57
|
+
"source": [
|
|
58
|
+
"## 1B. Option 2: Load Real `.su` or `.sgy` Data\n",
|
|
59
|
+
"Run this cell instead if you have the file `tac-204RL239.su` in the same folder as this notebook."
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"cell_type": "code",
|
|
64
|
+
"execution_count": null,
|
|
65
|
+
"metadata": {},
|
|
66
|
+
"outputs": [],
|
|
67
|
+
"source": [
|
|
68
|
+
"import segyio\n",
|
|
69
|
+
"from seismic_reader import read_seismic_file\n",
|
|
70
|
+
"\n",
|
|
71
|
+
"filename = 'tac-204RL239.su'\n",
|
|
72
|
+
"\n",
|
|
73
|
+
"# We only want to plot a single gather to avoid rendering thousands of traces.\n",
|
|
74
|
+
"# FieldRecord corresponds to the 'fldr' header in SU/SEGY.\n",
|
|
75
|
+
"gather_key = segyio.TraceField.FieldRecord\n",
|
|
76
|
+
"gather_index = 50 # Change this to a valid fldr number from your dataset!\n",
|
|
77
|
+
"\n",
|
|
78
|
+
"try:\n",
|
|
79
|
+
" data, offsets, times = read_seismic_file(filename, gather_key=gather_key, gather_index=gather_index)\n",
|
|
80
|
+
" print(f\"Loaded {data.shape[1]} traces for fldr {gather_index} with {data.shape[0]} samples each.\")\n",
|
|
81
|
+
"except FileNotFoundError:\n",
|
|
82
|
+
" print(f\"File '{filename}' not found. Please place it in the 'demos' folder or stick to Option 1A.\")\n",
|
|
83
|
+
"except ValueError as e:\n",
|
|
84
|
+
" print(f\"Error: {e}\")"
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"cell_type": "markdown",
|
|
89
|
+
"metadata": {},
|
|
90
|
+
"source": [
|
|
91
|
+
"## 2. Process Data for Wiggles\n",
|
|
92
|
+
"Regardless of how you loaded the data above, we now pass it through PySeis."
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"cell_type": "code",
|
|
97
|
+
"execution_count": null,
|
|
98
|
+
"metadata": {},
|
|
99
|
+
"outputs": [],
|
|
100
|
+
"source": [
|
|
101
|
+
"from pyseiskit import sourceData\n",
|
|
102
|
+
"\n",
|
|
103
|
+
"scaled_data = sourceData.rescaleDataForWiggle(data, offsets, overlap=1.0)\n",
|
|
104
|
+
"line_data = sourceData.wiggleLinesDataFactory(scaled_data, offsets, times)\n",
|
|
105
|
+
"patch_data = sourceData.wigglePatchesDataFactory(scaled_data, offsets, times, fill_mode='positive')"
|
|
106
|
+
]
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"cell_type": "markdown",
|
|
110
|
+
"metadata": {},
|
|
111
|
+
"source": [
|
|
112
|
+
"## 3. Plotting with Matplotlib"
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"cell_type": "code",
|
|
117
|
+
"execution_count": null,
|
|
118
|
+
"metadata": {},
|
|
119
|
+
"outputs": [],
|
|
120
|
+
"source": [
|
|
121
|
+
"import matplotlib.pyplot as plt\n",
|
|
122
|
+
"\n",
|
|
123
|
+
"matplotlib_figure, matplotlib_axes = plt.subplots(figsize=(6, 4))\n",
|
|
124
|
+
"\n",
|
|
125
|
+
"# Plot filled areas\n",
|
|
126
|
+
"for x, y in zip(patch_data['xs'], patch_data['ys']):\n",
|
|
127
|
+
" matplotlib_axes.fill(x, y, color='black')\n",
|
|
128
|
+
"\n",
|
|
129
|
+
"# Plot trace lines\n",
|
|
130
|
+
"for x, y in zip(line_data['xs'], line_data['ys']):\n",
|
|
131
|
+
" matplotlib_axes.plot(x, y, color='black', linewidth=0.5)\n",
|
|
132
|
+
"\n",
|
|
133
|
+
"matplotlib_axes.invert_yaxis() # Time goes down\n",
|
|
134
|
+
"plt.show()"
|
|
135
|
+
]
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"cell_type": "markdown",
|
|
139
|
+
"metadata": {},
|
|
140
|
+
"source": [
|
|
141
|
+
"## 4. Plotting with Bokeh"
|
|
142
|
+
]
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"cell_type": "code",
|
|
146
|
+
"execution_count": null,
|
|
147
|
+
"metadata": {},
|
|
148
|
+
"outputs": [],
|
|
149
|
+
"source": [
|
|
150
|
+
"from bokeh.plotting import figure, show, output_notebook\n",
|
|
151
|
+
"output_notebook()\n",
|
|
152
|
+
"\n",
|
|
153
|
+
"bokeh_plot = figure(width=600, height=400, y_range=(times[-1], times[0]))\n",
|
|
154
|
+
"bokeh_plot.multi_line(**line_data, color='black', line_width=0.5)\n",
|
|
155
|
+
"bokeh_plot.patches(**patch_data, color='black', line_width=0)\n",
|
|
156
|
+
"\n",
|
|
157
|
+
"show(bokeh_plot)"
|
|
158
|
+
]
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"cell_type": "markdown",
|
|
162
|
+
"metadata": {},
|
|
163
|
+
"source": [
|
|
164
|
+
"## 5. Plotting with Plotly"
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"cell_type": "code",
|
|
169
|
+
"execution_count": null,
|
|
170
|
+
"metadata": {},
|
|
171
|
+
"outputs": [],
|
|
172
|
+
"source": [
|
|
173
|
+
"import plotly.graph_objects as go\n",
|
|
174
|
+
"\n",
|
|
175
|
+
"plotly_figure = go.Figure()\n",
|
|
176
|
+
"\n",
|
|
177
|
+
"for x, y in zip(line_data['xs'], line_data['ys']):\n",
|
|
178
|
+
" plotly_figure.add_trace(go.Scatter(x=x, y=y, mode='lines', line=dict(color='black', width=0.5), showlegend=False))\n",
|
|
179
|
+
"\n",
|
|
180
|
+
"for x, y in zip(patch_data['xs'], patch_data['ys']):\n",
|
|
181
|
+
" plotly_figure.add_trace(go.Scatter(x=x, y=y, fill='toself', fillcolor='black', line=dict(width=0), showlegend=False))\n",
|
|
182
|
+
"\n",
|
|
183
|
+
"plotly_figure.update_layout(yaxis=dict(autorange='reversed'), height=500)\n",
|
|
184
|
+
"plotly_figure.show()"
|
|
185
|
+
]
|
|
186
|
+
}
|
|
187
|
+
],
|
|
188
|
+
"metadata": {
|
|
189
|
+
"kernelspec": {
|
|
190
|
+
"display_name": "pyseis (3.12.2)",
|
|
191
|
+
"language": "python",
|
|
192
|
+
"name": "python3"
|
|
193
|
+
},
|
|
194
|
+
"language_info": {
|
|
195
|
+
"codemirror_mode": {
|
|
196
|
+
"name": "ipython",
|
|
197
|
+
"version": 3
|
|
198
|
+
},
|
|
199
|
+
"file_extension": ".py",
|
|
200
|
+
"mimetype": "text/x-python",
|
|
201
|
+
"name": "python",
|
|
202
|
+
"nbconvert_exporter": "python",
|
|
203
|
+
"pygments_lexer": "ipython3",
|
|
204
|
+
"version": "3.12.2"
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
"nbformat": 4,
|
|
208
|
+
"nbformat_minor": 4
|
|
209
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import numpy.typing as np_types
|
|
3
|
+
from seismicio.Models.SuDataModel import SuFile
|
|
4
|
+
|
|
5
|
+
from ...constants.VISUALIZATION import FIRST_TIME_SAMPLE
|
|
6
|
+
from ...services.sufile import get_stack_sufile, get_multi_gather_sufile
|
|
7
|
+
from .BasePlotOptionsState import BasePlotOptionsState
|
|
8
|
+
|
|
9
|
+
def debug(message: str):
|
|
10
|
+
print(f"DEBUG: {message}", flush=True)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BaseVisualization:
|
|
14
|
+
sufile: SuFile
|
|
15
|
+
plot_options_state: BasePlotOptionsState
|
|
16
|
+
gather_offsets: np_types.NDArray | None
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
filename: str,
|
|
21
|
+
plot_options_state: BasePlotOptionsState,
|
|
22
|
+
gather_key: str | None = None,
|
|
23
|
+
) -> None:
|
|
24
|
+
self.plot_options_state = plot_options_state
|
|
25
|
+
if gather_key:
|
|
26
|
+
self.sufile: SuFile = get_multi_gather_sufile(
|
|
27
|
+
plot_options=self.plot_options_state.__dict__,
|
|
28
|
+
filename=filename,
|
|
29
|
+
gather_key=gather_key,
|
|
30
|
+
)
|
|
31
|
+
else:
|
|
32
|
+
self.sufile = get_stack_sufile(
|
|
33
|
+
plot_options=self.plot_options_state.__dict__,
|
|
34
|
+
filename=filename,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def getShotGathersData(
|
|
38
|
+
self,
|
|
39
|
+
index_start: int | None = None
|
|
40
|
+
):
|
|
41
|
+
if index_start is None:
|
|
42
|
+
index_start = self.plot_options_state.gather_index_start
|
|
43
|
+
index_stop = index_start + self.plot_options_state.num_loadedgathers
|
|
44
|
+
# ! igather VS gather
|
|
45
|
+
selected_gathers = self.sufile.igather[
|
|
46
|
+
index_start:index_stop
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
last_time_sample = \
|
|
50
|
+
FIRST_TIME_SAMPLE + \
|
|
51
|
+
(self.plot_options_state.num_time_samples - 1) * \
|
|
52
|
+
self.plot_options_state.interval_time_samples
|
|
53
|
+
|
|
54
|
+
width_time_samples = np.abs(
|
|
55
|
+
last_time_sample - FIRST_TIME_SAMPLE
|
|
56
|
+
)
|
|
57
|
+
self.plot_options_state.updatePlotOptionsState(
|
|
58
|
+
width_time_samples=width_time_samples,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if (index_stop - 1 == index_start):
|
|
62
|
+
# *** Single gather
|
|
63
|
+
self.gather_offsets = selected_gathers.headers["offset"]
|
|
64
|
+
else:
|
|
65
|
+
# *** Multiple gathers
|
|
66
|
+
self.gather_offsets = None
|
|
67
|
+
|
|
68
|
+
# --- DEBUG LOGS START ---
|
|
69
|
+
debug(f"DEBUG: index_start = {index_start}, index_stop = {index_stop}")
|
|
70
|
+
debug(f"DEBUG: num_loadedgathers = {self.plot_options_state.num_loadedgathers}")
|
|
71
|
+
debug(f"DEBUG: Data type (dtype) = {selected_gathers.data.dtype}")
|
|
72
|
+
debug(f"DEBUG: Data shape = {selected_gathers.data.shape}")
|
|
73
|
+
|
|
74
|
+
# Check for non-standard values that might break VTK color mapping
|
|
75
|
+
debug(f"DEBUG: Contains NaNs? = {np.isnan(selected_gathers.data).any()}")
|
|
76
|
+
debug(f"DEBUG: Min Amplitude = {np.nanmin(selected_gathers.data)}")
|
|
77
|
+
debug(f"DEBUG: Max Amplitude = {np.nanmax(selected_gathers.data)}")
|
|
78
|
+
|
|
79
|
+
if self.gather_offsets is not None:
|
|
80
|
+
debug(f"DEBUG: gather_offsets shape = {self.gather_offsets.shape}")
|
|
81
|
+
# --- DEBUG LOGS END ---
|
|
82
|
+
return selected_gathers.data
|
|
83
|
+
|
|
84
|
+
def getBaseData(self) -> np_types.NDArray:
|
|
85
|
+
"""
|
|
86
|
+
Get Numpy NDArray for data section on display
|
|
87
|
+
|
|
88
|
+
Handles stack or sectioned data (shot gathers)
|
|
89
|
+
"""
|
|
90
|
+
# *** "gather_keyword" must exist for shot gathers
|
|
91
|
+
if self.sufile.gather_keyword:
|
|
92
|
+
data = self.getShotGathersData()
|
|
93
|
+
else:
|
|
94
|
+
data = self.sufile.traces
|
|
95
|
+
self.gather_offsets = None
|
|
96
|
+
return data
|
|
97
|
+
|
|
98
|
+
def handle_state_change(self):
|
|
99
|
+
raise NotImplementedError(
|
|
100
|
+
"""
|
|
101
|
+
Missing custom handle_state_change() for this Visualization
|
|
102
|
+
handle_state_change() method must be re-implmented when extending BaseVisualization
|
|
103
|
+
"""
|
|
104
|
+
)
|