pyseiskit 0.1.0__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.
- pyseiskit/__init__.py +7 -0
- pyseiskit/modules/__init__.py +0 -0
- pyseiskit/modules/applyGains/__init__.py +0 -0
- pyseiskit/modules/applyGains/adapters/__init__.py +1 -0
- pyseiskit/modules/applyGains/adapters/clipAdapter.py +16 -0
- pyseiskit/modules/applyGains/applyGains.py +42 -0
- pyseiskit/modules/applyGains/gainStrategies.py +11 -0
- pyseiskit/modules/clip/__init__.py +1 -0
- pyseiskit/modules/clip/contracts/PercentileClip.py +19 -0
- pyseiskit/modules/clip/contracts/__init__.py +1 -0
- pyseiskit/modules/clip/percentileClip.py +31 -0
- pyseiskit/modules/gain/__init__.py +2 -0
- pyseiskit/modules/gain/agc.py +52 -0
- pyseiskit/modules/gain/contracts/AutomaticGain.py +21 -0
- pyseiskit/modules/gain/contracts/GenericGain.py +19 -0
- pyseiskit/modules/gain/contracts/__init__.py +2 -0
- pyseiskit/modules/gain/gagc.py +57 -0
- pyseiskit/modules/palettes/PALETTES.py +12 -0
- pyseiskit/modules/palettes/__init__.py +1 -0
- pyseiskit/modules/palettes/getFadingPalette.py +19 -0
- pyseiskit/modules/sourceData/__init__.py +3 -0
- pyseiskit/modules/sourceData/rescaleDataForWiggle.py +52 -0
- pyseiskit/modules/sourceData/wiggleLinesDataFactory.py +19 -0
- pyseiskit/modules/sourceData/wigglePatchesDataFactory.py +126 -0
- pyseiskit/py.typed +0 -0
- pyseiskit-0.1.0.dist-info/METADATA +43 -0
- pyseiskit-0.1.0.dist-info/RECORD +29 -0
- pyseiskit-0.1.0.dist-info/WHEEL +4 -0
- pyseiskit-0.1.0.dist-info/licenses/LICENSE +21 -0
pyseiskit/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .clipAdapter import createClipAdapter
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
import numpy.typing as np_types
|
|
3
|
+
|
|
4
|
+
def createClipAdapter(func: Callable) -> Callable:
|
|
5
|
+
"""
|
|
6
|
+
Wraps a 2-parameter clipping function to safely accept
|
|
7
|
+
the 3-parameter strategy caller signature.
|
|
8
|
+
"""
|
|
9
|
+
def clipAdapter(
|
|
10
|
+
gatherAmplitudes: np_types.NDArray,
|
|
11
|
+
gainValue: float,
|
|
12
|
+
intervalTimeSamples: float
|
|
13
|
+
) -> np_types.NDArray:
|
|
14
|
+
return func(gatherAmplitudes, gainValue)
|
|
15
|
+
|
|
16
|
+
return clipAdapter
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import TypedDict
|
|
2
|
+
from numpy import typing as np_types
|
|
3
|
+
|
|
4
|
+
from .gainStrategies import gainStrategies
|
|
5
|
+
|
|
6
|
+
class GainType(TypedDict):
|
|
7
|
+
"""
|
|
8
|
+
A dictionary defining the supported types of gain that can
|
|
9
|
+
be applied to seismic data.
|
|
10
|
+
"""
|
|
11
|
+
AGC: float
|
|
12
|
+
GAUSSIAN_AGC: float
|
|
13
|
+
PERCENTILE_CLIPPING: float
|
|
14
|
+
|
|
15
|
+
def applyGains(
|
|
16
|
+
data: np_types.NDArray,
|
|
17
|
+
gains: GainType,
|
|
18
|
+
intervalTimeSamples: float,
|
|
19
|
+
) -> np_types.NDArray:
|
|
20
|
+
"""
|
|
21
|
+
Applies the specified gain to the input data.
|
|
22
|
+
\nChecks for gain values, not keys.
|
|
23
|
+
\nIn case of None or 0 values, the gain will be skipped.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
data: np_types.NDArray
|
|
28
|
+
Input data array [2D array]
|
|
29
|
+
gains: GainType [dict]
|
|
30
|
+
Dictionary containing the gain values.
|
|
31
|
+
intervalTimeSamples: float
|
|
32
|
+
Time step between samples in seconds.
|
|
33
|
+
|
|
34
|
+
Returns: np_types.NDArray
|
|
35
|
+
The gained data array.
|
|
36
|
+
"""
|
|
37
|
+
newData = data.copy()
|
|
38
|
+
|
|
39
|
+
for gainType, gainValue in gains.items():
|
|
40
|
+
newData = gainStrategies[gainType](newData, gainValue, intervalTimeSamples)
|
|
41
|
+
|
|
42
|
+
return newData
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .adapters import createClipAdapter
|
|
2
|
+
|
|
3
|
+
from ..gain.contracts import GenericGainProtocol
|
|
4
|
+
from ..gain import applyAGC, applyGAGC
|
|
5
|
+
from ..clip import applyPercentileClip
|
|
6
|
+
|
|
7
|
+
gainStrategies: dict[str, GenericGainProtocol] = {
|
|
8
|
+
'AGC': applyAGC,
|
|
9
|
+
'GAUSSIAN_AGC': applyGAGC,
|
|
10
|
+
'PERCENTILE_CLIPPING': createClipAdapter(applyPercentileClip)
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .percentileClip import applyPercentileClip
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import numpy.typing as np_types
|
|
2
|
+
from typing import Protocol
|
|
3
|
+
|
|
4
|
+
class PercentileClipProtocol(Protocol):
|
|
5
|
+
def __call__(
|
|
6
|
+
self,
|
|
7
|
+
gatherAmplitudes: np_types.NDArray,
|
|
8
|
+
percentile: float,
|
|
9
|
+
) -> np_types.NDArray:
|
|
10
|
+
"""
|
|
11
|
+
Clip functions must exactly match these parameter names, order, and types.
|
|
12
|
+
"""
|
|
13
|
+
...
|
|
14
|
+
|
|
15
|
+
def PercentileClipContract(function: PercentileClipProtocol) -> PercentileClipProtocol:
|
|
16
|
+
"""
|
|
17
|
+
Decorator that forces type checkers to verify the function matches ClipProtocol.
|
|
18
|
+
"""
|
|
19
|
+
return function
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .PercentileClip import PercentileClipContract
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numpy import typing as np_types
|
|
3
|
+
|
|
4
|
+
from .contracts.PercentileClip import PercentileClipContract
|
|
5
|
+
|
|
6
|
+
@PercentileClipContract
|
|
7
|
+
def applyPercentileClip(
|
|
8
|
+
gatherAmplitudes: np_types.NDArray,
|
|
9
|
+
percentile: float
|
|
10
|
+
) -> np_types.NDArray:
|
|
11
|
+
"""
|
|
12
|
+
Apply a percentile-based clipping to input data.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
gatherAmplitudes : np_types.NDArray
|
|
17
|
+
Input data array.
|
|
18
|
+
percentile : float
|
|
19
|
+
Percentile value for clipping.
|
|
20
|
+
|
|
21
|
+
Returns
|
|
22
|
+
-------
|
|
23
|
+
np_types.NDArray
|
|
24
|
+
Clipped data array.
|
|
25
|
+
"""
|
|
26
|
+
maxTraceAmplitude = np.percentile(
|
|
27
|
+
np.absolute(gatherAmplitudes),
|
|
28
|
+
percentile
|
|
29
|
+
)
|
|
30
|
+
clippedTraces = np.clip(gatherAmplitudes, a_min=-maxTraceAmplitude, a_max=maxTraceAmplitude)
|
|
31
|
+
return clippedTraces
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import numpy.typing as np_types
|
|
3
|
+
from scipy import ndimage
|
|
4
|
+
|
|
5
|
+
from .contracts import AutomaticGainContract
|
|
6
|
+
|
|
7
|
+
@AutomaticGainContract
|
|
8
|
+
def applyAGC(
|
|
9
|
+
gatherAmplitudes: np_types.NDArray,
|
|
10
|
+
wagc: float,
|
|
11
|
+
intervalTimeSamples: float
|
|
12
|
+
) -> np_types.NDArray:
|
|
13
|
+
"""
|
|
14
|
+
Calculates Simple Automatic Gain Control (AGC) of a seismic gather.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
gatherAmplitudes : np_types.NDArray
|
|
19
|
+
Array of gather amplitudes [2D array]
|
|
20
|
+
wagc : float
|
|
21
|
+
Window length for AGC in seconds.
|
|
22
|
+
intervalTimeSamples : float
|
|
23
|
+
Time step between samples in seconds.
|
|
24
|
+
"""
|
|
25
|
+
half_window_samples = round(wagc / intervalTimeSamples)
|
|
26
|
+
|
|
27
|
+
# *** Define the physical window dimensions
|
|
28
|
+
fullWindowSamples = 2 * half_window_samples
|
|
29
|
+
|
|
30
|
+
# *** Rectangular filter (weights sum to 1.0 to compute a true mean)
|
|
31
|
+
averagingWeights = np.ones(fullWindowSamples) / fullWindowSamples
|
|
32
|
+
|
|
33
|
+
# *** Moving energy average
|
|
34
|
+
# *** Apply the 1D filter down the time axis (axis=0)
|
|
35
|
+
# *** mode='constant' with cval=0.0 safely pads the top and bottom with zeros
|
|
36
|
+
localMeanSquaredEnergy = ndimage.convolve1d(
|
|
37
|
+
gatherAmplitudes**2,
|
|
38
|
+
averagingWeights,
|
|
39
|
+
mode='constant',
|
|
40
|
+
cval=0.0,
|
|
41
|
+
axis=0
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# *** Extract RMS and apply gain
|
|
45
|
+
rootMeanSquareEnergy = np.sqrt(localMeanSquaredEnergy)
|
|
46
|
+
|
|
47
|
+
return np.divide(
|
|
48
|
+
gatherAmplitudes,
|
|
49
|
+
rootMeanSquareEnergy,
|
|
50
|
+
out=np.zeros_like(gatherAmplitudes),
|
|
51
|
+
where=rootMeanSquareEnergy!=0
|
|
52
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import numpy.typing as np_types
|
|
2
|
+
|
|
3
|
+
from .GenericGain import GenericGainProtocol
|
|
4
|
+
|
|
5
|
+
class AutomaticGainProtocol(GenericGainProtocol):
|
|
6
|
+
def __call__(
|
|
7
|
+
self,
|
|
8
|
+
gatherAmplitudes: np_types.NDArray,
|
|
9
|
+
wagc: float,
|
|
10
|
+
intervalTimeSamples: float
|
|
11
|
+
) -> np_types.NDArray:
|
|
12
|
+
"""
|
|
13
|
+
Automatic gain functions must exactly match these parameter names, order, and types.
|
|
14
|
+
"""
|
|
15
|
+
...
|
|
16
|
+
|
|
17
|
+
def AutomaticGainContract(function: AutomaticGainProtocol) -> AutomaticGainProtocol:
|
|
18
|
+
"""
|
|
19
|
+
Decorator that forces type checkers to verify the function matches AutomaticGainProtocol.
|
|
20
|
+
"""
|
|
21
|
+
return function
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import numpy.typing as np_types
|
|
2
|
+
from typing import Protocol
|
|
3
|
+
|
|
4
|
+
class GenericGainProtocol(Protocol):
|
|
5
|
+
def __call__(
|
|
6
|
+
self,
|
|
7
|
+
data: np_types.NDArray,
|
|
8
|
+
value: float,
|
|
9
|
+
# *** The '= ...' makes the third parameter optional.
|
|
10
|
+
timeSamples: float = ...,
|
|
11
|
+
# *** '/' means this protocol is positional-only.
|
|
12
|
+
/
|
|
13
|
+
) -> np_types.NDArray:
|
|
14
|
+
"""
|
|
15
|
+
Functions can name their variables wagc, percentile, threshold, etc.,
|
|
16
|
+
and it will still be valid.
|
|
17
|
+
The third parameter is optional.
|
|
18
|
+
"""
|
|
19
|
+
...
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import numpy.typing as np_types
|
|
3
|
+
from scipy import ndimage
|
|
4
|
+
|
|
5
|
+
from .contracts import AutomaticGainContract
|
|
6
|
+
|
|
7
|
+
EPS: float = 3.8090232
|
|
8
|
+
|
|
9
|
+
@AutomaticGainContract
|
|
10
|
+
def applyGAGC(
|
|
11
|
+
gatherAmplitudes: np_types.NDArray,
|
|
12
|
+
wagc: float,
|
|
13
|
+
intervalTimeSamples: float
|
|
14
|
+
) -> np_types.NDArray:
|
|
15
|
+
"""
|
|
16
|
+
Calculates Gaussian Automatic Gain Control (GAGC) of a seismic gather.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
gatherAmplitudes : np_types.NDArray
|
|
21
|
+
Array of gather amplitudes [2D array]
|
|
22
|
+
wagc : float
|
|
23
|
+
Window length for AGC in seconds.
|
|
24
|
+
intervalTimeSamples : float
|
|
25
|
+
Time step between samples in seconds.
|
|
26
|
+
"""
|
|
27
|
+
halfWindowSamples = round(wagc / intervalTimeSamples)
|
|
28
|
+
|
|
29
|
+
# *** Define the variance/decay of the Gaussian curve based on the window size
|
|
30
|
+
decayScalar = EPS / halfWindowSamples
|
|
31
|
+
|
|
32
|
+
# *** Array of relative distances from the center of the window
|
|
33
|
+
sampleOffsets = np.arange(-halfWindowSamples + 1, halfWindowSamples)
|
|
34
|
+
|
|
35
|
+
# *** Bell curve weights using the decay scalar and distances
|
|
36
|
+
gaussianWeights = np.exp(
|
|
37
|
+
-((decayScalar**2) * sampleOffsets * sampleOffsets)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# *** Moving energy average weighted by the Gaussian curve
|
|
41
|
+
localWeightedEnergy = ndimage.convolve1d(
|
|
42
|
+
gatherAmplitudes**2,
|
|
43
|
+
gaussianWeights,
|
|
44
|
+
mode='constant',
|
|
45
|
+
cval=0.0,
|
|
46
|
+
axis=0
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# *** Extract Root Weighted Energy and apply gain
|
|
50
|
+
rootWeightedEnergy = np.sqrt(localWeightedEnergy)
|
|
51
|
+
|
|
52
|
+
return np.divide(
|
|
53
|
+
gatherAmplitudes,
|
|
54
|
+
rootWeightedEnergy,
|
|
55
|
+
out=np.zeros_like(gatherAmplitudes),
|
|
56
|
+
where=rootWeightedEnergy!=0
|
|
57
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from bokeh.palettes import Greys256, viridis
|
|
2
|
+
from colorcet import rainbow4
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from .getFadingPalette import getFadingPalette
|
|
6
|
+
|
|
7
|
+
PALETTES = {
|
|
8
|
+
"Greys256": Greys256,
|
|
9
|
+
"FadingGreys": getFadingPalette(Greys256),
|
|
10
|
+
"viridis": viridis(256),
|
|
11
|
+
"rainbow": rainbow4,
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .PALETTES import PALETTES
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
def getFadingPalette(base_palette):
|
|
2
|
+
"""
|
|
3
|
+
Takes a palette (list of hex strings) and applies an alpha gradient.
|
|
4
|
+
The first color becomes 100% opaque, fading to 100% transparent at the last color.
|
|
5
|
+
"""
|
|
6
|
+
palette_length = len(base_palette)
|
|
7
|
+
fading_palette = []
|
|
8
|
+
|
|
9
|
+
for index, color in enumerate(base_palette):
|
|
10
|
+
# *** index=0 -> alpha=255 (opaque)
|
|
11
|
+
# *** index=(length-1) -> alpha=0 (transparent)
|
|
12
|
+
alpha = int(255 * (1 - (index / (palette_length - 1))))
|
|
13
|
+
# *** Grab just the #RRGGBB part
|
|
14
|
+
# *** in case the original palette already had alpha
|
|
15
|
+
base_hex = color[:7]
|
|
16
|
+
# *** Append the calculated alpha as a 2-character hex string
|
|
17
|
+
fading_palette.append(f"{base_hex}{alpha:02x}")
|
|
18
|
+
|
|
19
|
+
return fading_palette
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numpy import typing as np_types
|
|
3
|
+
|
|
4
|
+
def rescaleDataForWiggle(
|
|
5
|
+
data: np_types.NDArray,
|
|
6
|
+
offsetPosition: np_types.NDArray,
|
|
7
|
+
overlap: float = 1.0,
|
|
8
|
+
percentile: float = 100.0
|
|
9
|
+
) -> np_types.NDArray:
|
|
10
|
+
"""
|
|
11
|
+
Rescales seismic data amplitudes to physical horizontal offsets for wiggle trace plotting.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
data: 2D array of amplitudes (samples x traces).
|
|
15
|
+
offsetPosition: 1D array of horizontal trace locations.
|
|
16
|
+
overlap: Controls the maximum horizontal swing. A value of 1.0 (Seismic Unix 'xcur' equivalent)
|
|
17
|
+
means a maximum amplitude peak will swing exactly 1.0 trace spacing, touching the baseline
|
|
18
|
+
of the adjacent trace.
|
|
19
|
+
percentile: Percentile of absolute amplitudes used as the maximum for scaling.
|
|
20
|
+
Values < 100.0 (e.g., 99.0) ignore extreme outlier spikes when determining the
|
|
21
|
+
normalization reference, mirroring the behavior of Seismic Unix's 'perc' parameter.
|
|
22
|
+
"""
|
|
23
|
+
# Determine maximum scale reference using the specified percentile
|
|
24
|
+
if percentile < 100.0:
|
|
25
|
+
dataMax = np.percentile(np.abs(data), percentile)
|
|
26
|
+
else:
|
|
27
|
+
dataMax = np.max(np.abs(data))
|
|
28
|
+
|
|
29
|
+
if dataMax == 0:
|
|
30
|
+
return data.copy()
|
|
31
|
+
|
|
32
|
+
dataNormalized = data / dataMax
|
|
33
|
+
|
|
34
|
+
# For a single trace there is no inter-trace spacing to reference,
|
|
35
|
+
# so the normalized data is already the final result.
|
|
36
|
+
if data.shape[1] == 1:
|
|
37
|
+
return dataNormalized
|
|
38
|
+
|
|
39
|
+
# Calculate trace spacing robustly (median of non-zero absolute differences)
|
|
40
|
+
abs_diffs = np.abs(np.diff(offsetPosition))
|
|
41
|
+
valid_diffs = abs_diffs[abs_diffs > 0]
|
|
42
|
+
|
|
43
|
+
if len(valid_diffs) == 0:
|
|
44
|
+
# Fallback if all traces are literally on the exact same physical offset
|
|
45
|
+
minTraceSpacing = 1.0
|
|
46
|
+
else:
|
|
47
|
+
minTraceSpacing = np.min(valid_diffs)
|
|
48
|
+
|
|
49
|
+
# Scale normalized data so the maximum deflection equals overlap * trace spacing
|
|
50
|
+
dataRescaled = dataNormalized * (minTraceSpacing * overlap)
|
|
51
|
+
|
|
52
|
+
return dataRescaled
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numpy import typing as np_types
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def wiggleLinesDataFactory(
|
|
6
|
+
data: np_types.NDArray,
|
|
7
|
+
offsetPosition: np_types.NDArray,
|
|
8
|
+
timeSampleInstants: np_types.NDArray,
|
|
9
|
+
):
|
|
10
|
+
tracesAmount = data.shape[1]
|
|
11
|
+
|
|
12
|
+
# *** Positioning each trace at its corresponding X-axis offset
|
|
13
|
+
broadcastOffsetPosition = np.broadcast_to(offsetPosition, data.shape)
|
|
14
|
+
dataRepositioned = data + broadcastOffsetPosition
|
|
15
|
+
|
|
16
|
+
dataPositioned = dataRepositioned.T.tolist()
|
|
17
|
+
timesPositioned = [timeSampleInstants for _ in range(tracesAmount)]
|
|
18
|
+
|
|
19
|
+
return {"xs": dataPositioned, "ys": timesPositioned}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numpy import typing as np_types
|
|
3
|
+
from typing import Tuple, List
|
|
4
|
+
|
|
5
|
+
MIDPOINT_INDEX_OFFSET = 0.5
|
|
6
|
+
|
|
7
|
+
def _interpolateLobeCoordinates(
|
|
8
|
+
timeSampleInstants: np_types.NDArray,
|
|
9
|
+
traceAmplitudes: np_types.NDArray,
|
|
10
|
+
traceOffset: float
|
|
11
|
+
) -> Tuple[List[float], List[float]]:
|
|
12
|
+
"""
|
|
13
|
+
Finds all active amplitudes and calculates the exact zero-crossing interpolation
|
|
14
|
+
points, returning the sorted boundary coordinates for the filled lobes of a single trace.
|
|
15
|
+
Expects data to be pre-flipped if positive lobes are desired.
|
|
16
|
+
"""
|
|
17
|
+
currentAmplitudes = traceAmplitudes[:-1]
|
|
18
|
+
nextAmplitudes = traceAmplitudes[1:]
|
|
19
|
+
currentTime = timeSampleInstants[:-1]
|
|
20
|
+
nextTime = timeSampleInstants[1:]
|
|
21
|
+
|
|
22
|
+
# Capture strictly negative values (since we flip the data if filling positive)
|
|
23
|
+
negativeMask = currentAmplitudes <= 0
|
|
24
|
+
negativeIndices = np.where(negativeMask)[0]
|
|
25
|
+
|
|
26
|
+
xs_neg = traceOffset + currentAmplitudes[negativeMask]
|
|
27
|
+
ys_neg = currentTime[negativeMask]
|
|
28
|
+
|
|
29
|
+
# Capture exact zero crossings
|
|
30
|
+
crossingMask = (
|
|
31
|
+
(currentAmplitudes < 0) & (nextAmplitudes > 0)
|
|
32
|
+
) | (
|
|
33
|
+
(currentAmplitudes > 0) & (nextAmplitudes < 0)
|
|
34
|
+
)
|
|
35
|
+
crossingIndices = np.where(crossingMask)[0]
|
|
36
|
+
|
|
37
|
+
# Linear interpolation to find the exact time at zero
|
|
38
|
+
# Handle potential divide by zero just in case
|
|
39
|
+
diffAmp = nextAmplitudes[crossingMask] - currentAmplitudes[crossingMask]
|
|
40
|
+
safeDiffAmp = np.where(diffAmp == 0, 1e-10, diffAmp)
|
|
41
|
+
|
|
42
|
+
crossingTimes = (
|
|
43
|
+
currentTime[crossingMask] + (
|
|
44
|
+
nextTime[crossingMask] - currentTime[crossingMask]
|
|
45
|
+
) * (0 - currentAmplitudes[crossingMask]) / safeDiffAmp
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
xs_cross = np.full(len(crossingIndices), traceOffset)
|
|
49
|
+
ys_cross = crossingTimes
|
|
50
|
+
|
|
51
|
+
# Combine and sort to preserve the chronological order
|
|
52
|
+
sortOrder = np.argsort(np.concatenate(
|
|
53
|
+
[negativeIndices, crossingIndices + MIDPOINT_INDEX_OFFSET]
|
|
54
|
+
))
|
|
55
|
+
|
|
56
|
+
new_xs = np.concatenate([xs_neg, xs_cross])[sortOrder].tolist()
|
|
57
|
+
new_ys = np.concatenate([ys_neg, ys_cross])[sortOrder].tolist()
|
|
58
|
+
|
|
59
|
+
# Add the last point if it is part of the lobe
|
|
60
|
+
if traceAmplitudes[-1] <= 0:
|
|
61
|
+
new_xs.append(float(traceOffset + traceAmplitudes[-1]))
|
|
62
|
+
new_ys.append(float(timeSampleInstants[-1]))
|
|
63
|
+
|
|
64
|
+
return new_xs, new_ys
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def wigglePatchesDataFactory(
|
|
68
|
+
data: np_types.NDArray,
|
|
69
|
+
offsetPosition: np_types.NDArray,
|
|
70
|
+
timeSampleInstants: np_types.NDArray,
|
|
71
|
+
fill_mode: str = "positive"
|
|
72
|
+
) -> Tuple[List[List[float]], List[List[float]]]:
|
|
73
|
+
"""
|
|
74
|
+
Generates polygon coordinates for rendering filled wiggle lobes.
|
|
75
|
+
|
|
76
|
+
This function calculates sub-sample exact zero-crossings using linear interpolation
|
|
77
|
+
to create mathematically smooth filled areas. It is plotting-library agnostic.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
data: 2D array of rescaled amplitudes (samples x traces).
|
|
81
|
+
offsetPosition: 1D array of horizontal trace locations.
|
|
82
|
+
timeSampleInstants: 1D array of time/depth values for the vertical axis.
|
|
83
|
+
fill_mode: "positive" to fill right-swinging lobes, "negative" for left-swinging.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
A dictionary `{"xs": [...], "ys": [...]}` where:
|
|
87
|
+
xs: List of lists containing the X coordinates for each trace's filled polygon.
|
|
88
|
+
ys: List of lists containing the Y coordinates for each trace's filled polygon.
|
|
89
|
+
"""
|
|
90
|
+
if fill_mode not in ["positive", "negative"]:
|
|
91
|
+
raise ValueError("fill_mode must be 'positive' or 'negative'")
|
|
92
|
+
|
|
93
|
+
# The internal algorithm extracts negative values.
|
|
94
|
+
# If the user wants to fill positive lobes, we simply flip the polarity of the data.
|
|
95
|
+
working_data = -data if fill_mode == "positive" else data
|
|
96
|
+
|
|
97
|
+
xs_patches = []
|
|
98
|
+
ys_patches = []
|
|
99
|
+
|
|
100
|
+
for traceOffset, traceAmplitude in zip(offsetPosition, working_data.T):
|
|
101
|
+
lobeAmplitudes, lobeTimes = _interpolateLobeCoordinates(
|
|
102
|
+
timeSampleInstants,
|
|
103
|
+
traceAmplitude,
|
|
104
|
+
traceOffset
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Skip if the trace has no lobes of the requested polarity
|
|
108
|
+
if not lobeAmplitudes:
|
|
109
|
+
xs_patches.append([])
|
|
110
|
+
ys_patches.append([])
|
|
111
|
+
continue
|
|
112
|
+
|
|
113
|
+
# Build the single continuous polygon: Lobe path + return path along the baseline
|
|
114
|
+
# Note: We must invert the amplitude back to normal if we flipped it earlier
|
|
115
|
+
if fill_mode == "positive":
|
|
116
|
+
actualAmplitudes = [traceOffset - (amp - traceOffset) for amp in lobeAmplitudes]
|
|
117
|
+
else:
|
|
118
|
+
actualAmplitudes = lobeAmplitudes
|
|
119
|
+
|
|
120
|
+
polygonOffsetCoordinates = [traceOffset] * len(actualAmplitudes) + actualAmplitudes[::-1]
|
|
121
|
+
polygonTimeCoordinates = lobeTimes + lobeTimes[::-1]
|
|
122
|
+
|
|
123
|
+
xs_patches.append(polygonOffsetCoordinates)
|
|
124
|
+
ys_patches.append(polygonTimeCoordinates)
|
|
125
|
+
|
|
126
|
+
return {"xs": xs_patches, "ys": ys_patches}
|
pyseiskit/py.typed
ADDED
|
File without changes
|
|
@@ -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,29 @@
|
|
|
1
|
+
pyseiskit/__init__.py,sha256=AwTaxxl5N9qCrb6ZyjOYBkmwv8v1NWV-3biR0m4mVVU,233
|
|
2
|
+
pyseiskit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
pyseiskit/modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
pyseiskit/modules/applyGains/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
pyseiskit/modules/applyGains/applyGains.py,sha256=U4ZiK86gb84xYl6oqDaodP8crj-AX6DvjrTRHgbHDZg,1092
|
|
6
|
+
pyseiskit/modules/applyGains/gainStrategies.py,sha256=ezZRmy7jU8D83MCPKO-nHCvn-q174uEmVVl3bAVuSw4,340
|
|
7
|
+
pyseiskit/modules/applyGains/adapters/__init__.py,sha256=A1-rqVJZR6deGC0DbYlhJqMp7ov2_cPMFAJPUSV0HsY,43
|
|
8
|
+
pyseiskit/modules/applyGains/adapters/clipAdapter.py,sha256=7CxVhsR_n7xCnil_AIYDdzJI1oLEAWn0jSDr0mVps8s,465
|
|
9
|
+
pyseiskit/modules/clip/__init__.py,sha256=AJT90JbIk-bdYLygqI84S2EbshJ8mUIsMVH0tbH62Vc,48
|
|
10
|
+
pyseiskit/modules/clip/percentileClip.py,sha256=s2G7VU8CrkODOXbEoFZrckleY0YaKirge-zeRdRQi2o,777
|
|
11
|
+
pyseiskit/modules/clip/contracts/PercentileClip.py,sha256=HgFvARg8KLN9QE6viNiGZyqIY3iQVlmHOzeaD7yzIQ8,560
|
|
12
|
+
pyseiskit/modules/clip/contracts/__init__.py,sha256=yN1CT2zDL-_cu0m9S6z8N2TE3RzZK_VrPTexUBQQBJg,51
|
|
13
|
+
pyseiskit/modules/gain/__init__.py,sha256=Qks9VshxyA_OH8BpUQerxeg_7IGWf01BPOnBcvlBvDo,54
|
|
14
|
+
pyseiskit/modules/gain/agc.py,sha256=RrWQXd0d2O1DoLe6MugoYZY6DG0_RoWZ3G3OWX9ilqY,1529
|
|
15
|
+
pyseiskit/modules/gain/gagc.py,sha256=EgxiwFSUuxeuDOaGq98g_h7CQpNdtjMeelGuD8MFMTk,1635
|
|
16
|
+
pyseiskit/modules/gain/contracts/AutomaticGain.py,sha256=ngE_djLcV0sokG7lEhFKWwPKXQLJoHDJxLRMRu4oeS0,633
|
|
17
|
+
pyseiskit/modules/gain/contracts/GenericGain.py,sha256=fQqOgOql16QJi_AgWL_V0-JoJGoEndXBgcs7q4qFvHQ,568
|
|
18
|
+
pyseiskit/modules/gain/contracts/__init__.py,sha256=5d5y6DdJ98S70_wRtp6ql_1t5bsM0hHy6UgKP0gI7D4,94
|
|
19
|
+
pyseiskit/modules/palettes/PALETTES.py,sha256=6TM7ZYqGNzc97l1dLUOMFyUaQUvDgO0y9gQj7KDfETw,255
|
|
20
|
+
pyseiskit/modules/palettes/__init__.py,sha256=Mv_bF0y13eguj98wI9rz_LcL0jEeCjBPlpfvYL5oCk8,31
|
|
21
|
+
pyseiskit/modules/palettes/getFadingPalette.py,sha256=Rcmiece8xxAECJKg9KN3-xKBd4Tgnj9I_blsfyFNrsk,787
|
|
22
|
+
pyseiskit/modules/sourceData/__init__.py,sha256=upeayOuOXNzJpEg-DY8ex4gY6C-RVdr8f9RHUlXK9_E,177
|
|
23
|
+
pyseiskit/modules/sourceData/rescaleDataForWiggle.py,sha256=Z8ZLz5VVjOIoOlTtnoPGkHBlM6GBYmIktNVgiu8eA74,2023
|
|
24
|
+
pyseiskit/modules/sourceData/wiggleLinesDataFactory.py,sha256=JAP0LQuol0dop-O0D7ueoksrwhpkCwpW1ddP2EtijSs,607
|
|
25
|
+
pyseiskit/modules/sourceData/wigglePatchesDataFactory.py,sha256=rKMGpumCXIZBk0o8qSgTNi8NGM4ifTYkVQxKd_b3k3s,4856
|
|
26
|
+
pyseiskit-0.1.0.dist-info/METADATA,sha256=dalBtDtyHOvQLsuUoiZu8GJ2xpeHRm1ynz48aIbzO4U,1388
|
|
27
|
+
pyseiskit-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
28
|
+
pyseiskit-0.1.0.dist-info/licenses/LICENSE,sha256=Nb2j7KkrzjaGNwtyEBAOIZHOqlVlyxML27s62sVL-hA,1065
|
|
29
|
+
pyseiskit-0.1.0.dist-info/RECORD,,
|
|
@@ -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.
|