subsurface-terra 2025.1.0rc14__py3-none-any.whl → 2025.1.0rc16__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.
- subsurface/__init__.py +31 -31
- subsurface/_version.py +34 -21
- subsurface/api/__init__.py +13 -13
- subsurface/api/interfaces/__init__.py +3 -3
- subsurface/api/interfaces/stream.py +136 -136
- subsurface/api/reader/read_wells.py +78 -78
- subsurface/core/geological_formats/boreholes/_combine_trajectories.py +117 -117
- subsurface/core/geological_formats/boreholes/_map_attrs_to_survey.py +236 -0
- subsurface/core/geological_formats/boreholes/_survey_to_unstruct.py +163 -0
- subsurface/core/geological_formats/boreholes/boreholes.py +140 -116
- subsurface/core/geological_formats/boreholes/collars.py +26 -26
- subsurface/core/geological_formats/boreholes/survey.py +86 -380
- subsurface/core/geological_formats/fault.py +47 -47
- subsurface/core/reader_helpers/reader_unstruct.py +11 -11
- subsurface/core/reader_helpers/readers_data.py +130 -130
- subsurface/core/reader_helpers/readers_wells.py +13 -13
- subsurface/core/structs/__init__.py +3 -3
- subsurface/core/structs/base_structures/__init__.py +2 -2
- subsurface/core/structs/base_structures/_liquid_earth_mesh.py +121 -121
- subsurface/core/structs/base_structures/_unstructured_data_constructor.py +70 -70
- subsurface/core/structs/base_structures/base_structures_enum.py +6 -6
- subsurface/core/structs/base_structures/structured_data.py +282 -282
- subsurface/core/structs/base_structures/unstructured_data.py +319 -319
- subsurface/core/structs/structured_elements/octree_mesh.py +10 -10
- subsurface/core/structs/structured_elements/structured_grid.py +59 -59
- subsurface/core/structs/structured_elements/structured_mesh.py +9 -9
- subsurface/core/structs/unstructured_elements/__init__.py +3 -3
- subsurface/core/structs/unstructured_elements/line_set.py +72 -72
- subsurface/core/structs/unstructured_elements/point_set.py +43 -43
- subsurface/core/structs/unstructured_elements/tetrahedron_mesh.py +35 -35
- subsurface/core/structs/unstructured_elements/triangular_surface.py +62 -62
- subsurface/core/utils/utils_core.py +38 -38
- subsurface/modules/reader/__init__.py +13 -13
- subsurface/modules/reader/faults/faults.py +80 -80
- subsurface/modules/reader/from_binary.py +46 -46
- subsurface/modules/reader/mesh/_GOCAD_mesh.py +82 -82
- subsurface/modules/reader/mesh/_trimesh_reader.py +447 -447
- subsurface/modules/reader/mesh/csv_mesh_reader.py +53 -53
- subsurface/modules/reader/mesh/dxf_reader.py +177 -177
- subsurface/modules/reader/mesh/glb_reader.py +30 -30
- subsurface/modules/reader/mesh/mx_reader.py +232 -232
- subsurface/modules/reader/mesh/obj_reader.py +53 -53
- subsurface/modules/reader/mesh/omf_mesh_reader.py +43 -43
- subsurface/modules/reader/mesh/surface_reader.py +56 -56
- subsurface/modules/reader/mesh/surfaces_api.py +41 -41
- subsurface/modules/reader/profiles/__init__.py +3 -3
- subsurface/modules/reader/profiles/profiles_core.py +197 -197
- subsurface/modules/reader/read_netcdf.py +38 -38
- subsurface/modules/reader/topography/__init__.py +7 -7
- subsurface/modules/reader/topography/topo_core.py +100 -100
- subsurface/modules/reader/volume/read_grav3d.py +478 -428
- subsurface/modules/reader/volume/read_volume.py +327 -230
- subsurface/modules/reader/volume/segy_reader.py +105 -105
- subsurface/modules/reader/volume/seismic.py +173 -173
- subsurface/modules/reader/volume/volume_utils.py +43 -43
- subsurface/modules/reader/wells/DEP/__init__.py +43 -43
- subsurface/modules/reader/wells/DEP/_well_files_reader.py +167 -167
- subsurface/modules/reader/wells/DEP/_wells_api.py +61 -61
- subsurface/modules/reader/wells/DEP/_welly_reader.py +180 -180
- subsurface/modules/reader/wells/DEP/pandas_to_welly.py +212 -212
- subsurface/modules/reader/wells/_read_to_df.py +57 -57
- subsurface/modules/reader/wells/read_borehole_interface.py +148 -148
- subsurface/modules/reader/wells/wells_utils.py +68 -68
- subsurface/modules/tools/mocking_aux.py +104 -104
- subsurface/modules/visualization/__init__.py +2 -2
- subsurface/modules/visualization/to_pyvista.py +320 -320
- subsurface/modules/writer/to_binary.py +12 -12
- subsurface/modules/writer/to_rex/common.py +78 -78
- subsurface/modules/writer/to_rex/data_struct.py +74 -74
- subsurface/modules/writer/to_rex/gempy_to_rexfile.py +791 -791
- subsurface/modules/writer/to_rex/material_encoder.py +44 -44
- subsurface/modules/writer/to_rex/mesh_encoder.py +152 -152
- subsurface/modules/writer/to_rex/to_rex.py +115 -115
- subsurface/modules/writer/to_rex/utils.py +15 -15
- subsurface/optional_requirements.py +116 -116
- {subsurface_terra-2025.1.0rc14.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/METADATA +194 -194
- subsurface_terra-2025.1.0rc16.dist-info/RECORD +98 -0
- {subsurface_terra-2025.1.0rc14.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/WHEEL +1 -1
- {subsurface_terra-2025.1.0rc14.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/licenses/LICENSE +203 -203
- subsurface_terra-2025.1.0rc14.dist-info/RECORD +0 -96
- {subsurface_terra-2025.1.0rc14.dist-info → subsurface_terra-2025.1.0rc16.dist-info}/top_level.txt +0 -0
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
from typing import Union
|
|
2
|
-
|
|
3
|
-
from subsurface import optional_requirements
|
|
4
|
-
from ....core.structs.base_structures import StructuredData
|
|
5
|
-
import numpy as np
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def read_in_segy(filepath: str, ignore_geometry: bool = True, flip_y_axis: bool = True) -> StructuredData:
|
|
9
|
-
"""
|
|
10
|
-
Reads a SEG-Y file and processes its data into a structured format.
|
|
11
|
-
|
|
12
|
-
This function opens a SEG-Y file using the `segyio` library and extracts
|
|
13
|
-
the trace data. The data is optionally flipped along the y-axis and its
|
|
14
|
-
axes are rearranged before being converted into a `StructuredData` object.
|
|
15
|
-
The `ignore_geometry` option determines whether SEG-Y header geometry
|
|
16
|
-
information is considered during file read.
|
|
17
|
-
|
|
18
|
-
Args:
|
|
19
|
-
filepath (str): The path to the SEG-Y file to be read.
|
|
20
|
-
ignore_geometry (bool): Whether to ignore the geometry information
|
|
21
|
-
from the SEG-Y header. Defaults to True.
|
|
22
|
-
flip_y_axis (bool): Whether to flip the data vertically (on the y-axis).
|
|
23
|
-
Defaults to True.
|
|
24
|
-
|
|
25
|
-
Returns:
|
|
26
|
-
StructuredData: An instance of `StructuredData` containing the
|
|
27
|
-
processed trace data from the SEG-Y file.
|
|
28
|
-
|
|
29
|
-
Raises:
|
|
30
|
-
IOError: If there is an error in opening or reading the SEG-Y file.
|
|
31
|
-
ValueError: If the data cannot be processed into the expected format.
|
|
32
|
-
"""
|
|
33
|
-
segyio = optional_requirements.require_segyio()
|
|
34
|
-
segyfile = segyio.open(filepath, ignore_geometry=ignore_geometry)
|
|
35
|
-
|
|
36
|
-
data = np.asarray([np.copy(tr) for tr in segyfile.trace[:]])
|
|
37
|
-
|
|
38
|
-
if flip_y_axis:
|
|
39
|
-
data = np.flip(data, axis=1)
|
|
40
|
-
|
|
41
|
-
data = np.swapaxes(data, 0, 1)
|
|
42
|
-
|
|
43
|
-
sd = StructuredData.from_numpy(data) # data holds traces * (samples per trace) values
|
|
44
|
-
segyfile.close()
|
|
45
|
-
return sd
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def create_mesh_from_coords(coords: dict, zmin: Union[float, int], zmax: Union[float, int] = 0.0):
|
|
49
|
-
"""Creates a mesh for plotting StructuredData
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
coords (Union[dict, LineString]): the x and y, i.e. latitude and longitude, location of the traces of the seismic profile
|
|
53
|
-
zmax (float): the maximum elevation of the seismic profile, by default 0.0
|
|
54
|
-
zmin (float): the location in z where the lowest sample was taken
|
|
55
|
-
|
|
56
|
-
Returns: vertices and faces for creating an UnstructuredData object
|
|
57
|
-
|
|
58
|
-
"""
|
|
59
|
-
n = len(coords['x'])
|
|
60
|
-
coords = np.array([coords['x'], coords['y']]).T
|
|
61
|
-
# duplicating the line, once with z=lower and another with z=upper values
|
|
62
|
-
vertices = np.zeros((2 * n, 3))
|
|
63
|
-
vertices[:n, :2] = coords
|
|
64
|
-
vertices[:n, 2] = zmin
|
|
65
|
-
vertices[n:, :2] = coords
|
|
66
|
-
vertices[n:, 2] = zmax
|
|
67
|
-
# i+n --- i+n+1
|
|
68
|
-
# |\ |
|
|
69
|
-
# | \ |
|
|
70
|
-
# | \ |
|
|
71
|
-
# | \ |
|
|
72
|
-
# i --- i+1
|
|
73
|
-
|
|
74
|
-
scipy = optional_requirements.require_scipy()
|
|
75
|
-
tri = scipy.spatial.qhull.Delaunay(vertices[:, [0, 2]])
|
|
76
|
-
faces = tri.simplices
|
|
77
|
-
return vertices, faces
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def apply_colormap_to_texture(texture: StructuredData, cmap_name="bwr"):
|
|
81
|
-
"""
|
|
82
|
-
Convert a single-channel seismic texture.data array into RGB
|
|
83
|
-
using a Matplotlib colormap (e.g., 'bwr', 'jet', 'RdBu_r', etc.).
|
|
84
|
-
"""
|
|
85
|
-
# 'texture.data' should be a 2D array of amplitudes: shape = (height, width)
|
|
86
|
-
import matplotlib.colors as colors
|
|
87
|
-
import matplotlib.pyplot as plt
|
|
88
|
-
data = texture.values
|
|
89
|
-
# 1. Normalize data to [0,1] range based on its min/max
|
|
90
|
-
min_val, max_val = data.min(), data.max()
|
|
91
|
-
norm = colors.Normalize(vmin=min_val, vmax=max_val)
|
|
92
|
-
|
|
93
|
-
norm = colors.Normalize(vmin=-6, vmax=6)
|
|
94
|
-
|
|
95
|
-
# 2. Get a Matplotlib colormap
|
|
96
|
-
cmap = plt.get_cmap(cmap_name)
|
|
97
|
-
|
|
98
|
-
# 3. Map normalized data -> RGBA, shape becomes (height, width, 4)
|
|
99
|
-
rgba_data = cmap(norm(data))
|
|
100
|
-
|
|
101
|
-
# 4. Convert from float in [0,1] to uint8 in [0,255], and drop alpha channel
|
|
102
|
-
rgb_data = (rgba_data[..., :3] * 255).astype(np.uint8)
|
|
103
|
-
|
|
104
|
-
texture = StructuredData.from_numpy(rgb_data)
|
|
105
|
-
return texture
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
from subsurface import optional_requirements
|
|
4
|
+
from ....core.structs.base_structures import StructuredData
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def read_in_segy(filepath: str, ignore_geometry: bool = True, flip_y_axis: bool = True) -> StructuredData:
|
|
9
|
+
"""
|
|
10
|
+
Reads a SEG-Y file and processes its data into a structured format.
|
|
11
|
+
|
|
12
|
+
This function opens a SEG-Y file using the `segyio` library and extracts
|
|
13
|
+
the trace data. The data is optionally flipped along the y-axis and its
|
|
14
|
+
axes are rearranged before being converted into a `StructuredData` object.
|
|
15
|
+
The `ignore_geometry` option determines whether SEG-Y header geometry
|
|
16
|
+
information is considered during file read.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
filepath (str): The path to the SEG-Y file to be read.
|
|
20
|
+
ignore_geometry (bool): Whether to ignore the geometry information
|
|
21
|
+
from the SEG-Y header. Defaults to True.
|
|
22
|
+
flip_y_axis (bool): Whether to flip the data vertically (on the y-axis).
|
|
23
|
+
Defaults to True.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
StructuredData: An instance of `StructuredData` containing the
|
|
27
|
+
processed trace data from the SEG-Y file.
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
IOError: If there is an error in opening or reading the SEG-Y file.
|
|
31
|
+
ValueError: If the data cannot be processed into the expected format.
|
|
32
|
+
"""
|
|
33
|
+
segyio = optional_requirements.require_segyio()
|
|
34
|
+
segyfile = segyio.open(filepath, ignore_geometry=ignore_geometry)
|
|
35
|
+
|
|
36
|
+
data = np.asarray([np.copy(tr) for tr in segyfile.trace[:]])
|
|
37
|
+
|
|
38
|
+
if flip_y_axis:
|
|
39
|
+
data = np.flip(data, axis=1)
|
|
40
|
+
|
|
41
|
+
data = np.swapaxes(data, 0, 1)
|
|
42
|
+
|
|
43
|
+
sd = StructuredData.from_numpy(data) # data holds traces * (samples per trace) values
|
|
44
|
+
segyfile.close()
|
|
45
|
+
return sd
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def create_mesh_from_coords(coords: dict, zmin: Union[float, int], zmax: Union[float, int] = 0.0):
|
|
49
|
+
"""Creates a mesh for plotting StructuredData
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
coords (Union[dict, LineString]): the x and y, i.e. latitude and longitude, location of the traces of the seismic profile
|
|
53
|
+
zmax (float): the maximum elevation of the seismic profile, by default 0.0
|
|
54
|
+
zmin (float): the location in z where the lowest sample was taken
|
|
55
|
+
|
|
56
|
+
Returns: vertices and faces for creating an UnstructuredData object
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
n = len(coords['x'])
|
|
60
|
+
coords = np.array([coords['x'], coords['y']]).T
|
|
61
|
+
# duplicating the line, once with z=lower and another with z=upper values
|
|
62
|
+
vertices = np.zeros((2 * n, 3))
|
|
63
|
+
vertices[:n, :2] = coords
|
|
64
|
+
vertices[:n, 2] = zmin
|
|
65
|
+
vertices[n:, :2] = coords
|
|
66
|
+
vertices[n:, 2] = zmax
|
|
67
|
+
# i+n --- i+n+1
|
|
68
|
+
# |\ |
|
|
69
|
+
# | \ |
|
|
70
|
+
# | \ |
|
|
71
|
+
# | \ |
|
|
72
|
+
# i --- i+1
|
|
73
|
+
|
|
74
|
+
scipy = optional_requirements.require_scipy()
|
|
75
|
+
tri = scipy.spatial.qhull.Delaunay(vertices[:, [0, 2]])
|
|
76
|
+
faces = tri.simplices
|
|
77
|
+
return vertices, faces
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def apply_colormap_to_texture(texture: StructuredData, cmap_name="bwr"):
|
|
81
|
+
"""
|
|
82
|
+
Convert a single-channel seismic texture.data array into RGB
|
|
83
|
+
using a Matplotlib colormap (e.g., 'bwr', 'jet', 'RdBu_r', etc.).
|
|
84
|
+
"""
|
|
85
|
+
# 'texture.data' should be a 2D array of amplitudes: shape = (height, width)
|
|
86
|
+
import matplotlib.colors as colors
|
|
87
|
+
import matplotlib.pyplot as plt
|
|
88
|
+
data = texture.values
|
|
89
|
+
# 1. Normalize data to [0,1] range based on its min/max
|
|
90
|
+
min_val, max_val = data.min(), data.max()
|
|
91
|
+
norm = colors.Normalize(vmin=min_val, vmax=max_val)
|
|
92
|
+
|
|
93
|
+
norm = colors.Normalize(vmin=-6, vmax=6)
|
|
94
|
+
|
|
95
|
+
# 2. Get a Matplotlib colormap
|
|
96
|
+
cmap = plt.get_cmap(cmap_name)
|
|
97
|
+
|
|
98
|
+
# 3. Map normalized data -> RGBA, shape becomes (height, width, 4)
|
|
99
|
+
rgba_data = cmap(norm(data))
|
|
100
|
+
|
|
101
|
+
# 4. Convert from float in [0,1] to uint8 in [0,255], and drop alpha channel
|
|
102
|
+
rgb_data = (rgba_data[..., :3] * 255).astype(np.uint8)
|
|
103
|
+
|
|
104
|
+
texture = StructuredData.from_numpy(rgb_data)
|
|
105
|
+
return texture
|
|
@@ -1,173 +1,173 @@
|
|
|
1
|
-
"""
|
|
2
|
-
TODO: This is legacy code waiting to be updated to the new ideas
|
|
3
|
-
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import xarray as xr
|
|
7
|
-
import numpy as np
|
|
8
|
-
|
|
9
|
-
from subsurface import optional_requirements
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class Seismic:
|
|
13
|
-
def __init__(self, data: np.ndarray, *args, **kwargs):
|
|
14
|
-
"""Seismic data object based on xarray.DataArray.
|
|
15
|
-
|
|
16
|
-
Args:
|
|
17
|
-
data (Array): np.ndarray of the seismic cube / section.
|
|
18
|
-
"""
|
|
19
|
-
self.n_shp = len(data.shape)
|
|
20
|
-
self._xarray = xr.DataArray(self._flip(data), *args, **kwargs)
|
|
21
|
-
|
|
22
|
-
def __getattr__(self, attr):
|
|
23
|
-
if attr in self.__dict__:
|
|
24
|
-
return getattr(self, attr)
|
|
25
|
-
return getattr(self._xarray, attr)
|
|
26
|
-
|
|
27
|
-
def __getitem__(self, item):
|
|
28
|
-
if isinstance(item, str):
|
|
29
|
-
return self._xarray._getitem_coord(item)
|
|
30
|
-
|
|
31
|
-
# preserve coordinates
|
|
32
|
-
cp = list(self._xarray.coords.items()) # parent coordinates
|
|
33
|
-
coords = [(cp[i]) for i, it in enumerate(item) if not type(it) == int]
|
|
34
|
-
return Seismic(self._xarray[item].data, coords=coords)
|
|
35
|
-
|
|
36
|
-
def __repr__(self):
|
|
37
|
-
return self._xarray.__repr__()
|
|
38
|
-
|
|
39
|
-
def __str__(self):
|
|
40
|
-
return "Seismic"
|
|
41
|
-
|
|
42
|
-
def _flip(self, data: np.ndarray) -> np.ndarray:
|
|
43
|
-
"""Flip SEGY data to fit with numpy array orientation.
|
|
44
|
-
|
|
45
|
-
Args:
|
|
46
|
-
data (np.ndarray): Seismic data.
|
|
47
|
-
|
|
48
|
-
Returns:
|
|
49
|
-
(np.ndarray) Flipped seismic data.
|
|
50
|
-
"""
|
|
51
|
-
if self.n_shp == 1:
|
|
52
|
-
return data
|
|
53
|
-
if self.n_shp == 2:
|
|
54
|
-
return data
|
|
55
|
-
if self.n_shp == 3:
|
|
56
|
-
return np.flip(data, axis=2)
|
|
57
|
-
|
|
58
|
-
def add_coords(self):
|
|
59
|
-
"""Ability to easily add physical coordinates."""
|
|
60
|
-
raise NotImplementedError
|
|
61
|
-
|
|
62
|
-
def to_segy(self, filepath: str) -> None:
|
|
63
|
-
"""Write given Seismic to SEGY file using segyio.tools.from_array().
|
|
64
|
-
|
|
65
|
-
Args:
|
|
66
|
-
filepath (str): Filepath for SEGY file.
|
|
67
|
-
"""
|
|
68
|
-
segyio = optional_requirements.require_segyio()
|
|
69
|
-
segyio.tools.from_array(filepath, self._xarray.data)
|
|
70
|
-
|
|
71
|
-
def plot_(self):
|
|
72
|
-
return xr.plot.plot._PlotMethods(self)
|
|
73
|
-
|
|
74
|
-
def plot(self):
|
|
75
|
-
if self.n_shp == 1:
|
|
76
|
-
return _plot_1d(self)
|
|
77
|
-
elif self.n_shp == 2:
|
|
78
|
-
pass
|
|
79
|
-
# TODO: plot seismic section using imshow for correct orientation
|
|
80
|
-
elif self.n_shp >= 3:
|
|
81
|
-
_plot_3d(self)
|
|
82
|
-
|
|
83
|
-
def create_pyvista_grid(self) -> 'pyvista.grid.UniformGrid':
|
|
84
|
-
"""Generate UniformGrid object for 3D plotting of the seismic.
|
|
85
|
-
|
|
86
|
-
Args:
|
|
87
|
-
seismic (Seismic): Seismic object.
|
|
88
|
-
|
|
89
|
-
Returns:
|
|
90
|
-
(pv.grid.UniformGrid)
|
|
91
|
-
"""
|
|
92
|
-
pv = optional_requirements.require_pyvista()
|
|
93
|
-
grid = pv.UniformGrid()
|
|
94
|
-
grid.spacing = (1, 1, 1) # TODO: cell sizes? vertical exaggeration etc
|
|
95
|
-
grid.dimensions = np.array(self.data.shape) + 1
|
|
96
|
-
grid.cell_data["values"] = self.data.flatten(order="F")
|
|
97
|
-
# TODO: correct orientation of cube
|
|
98
|
-
return grid
|
|
99
|
-
|
|
100
|
-
def plot_3d_slices(self):
|
|
101
|
-
pv = optional_requirements.require_pyvista()
|
|
102
|
-
if self.n_shp != 3:
|
|
103
|
-
raise AssertionError("Seismic data needs to be a 3-D volume.")
|
|
104
|
-
# TODO: cmap, kwarg passthrough
|
|
105
|
-
pv.OrthogonalSlicer(self.grid)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def _plot_1d(seismic: Seismic, linekwargs={}, fillkwargs={}):
|
|
109
|
-
import matplotlib.pyplot as plt
|
|
110
|
-
fig, ax = plt.subplots(figsize=(2,8))
|
|
111
|
-
|
|
112
|
-
lkwargs = dict(
|
|
113
|
-
color="black",
|
|
114
|
-
linewidth=1
|
|
115
|
-
)
|
|
116
|
-
lkwargs.update(linekwargs)
|
|
117
|
-
|
|
118
|
-
fkwargs = dict(
|
|
119
|
-
color="grey"
|
|
120
|
-
)
|
|
121
|
-
fkwargs.update(fillkwargs)
|
|
122
|
-
|
|
123
|
-
y = np.arange(0, *seismic.data.shape)
|
|
124
|
-
ax.plot(seismic.data, y, **lkwargs)
|
|
125
|
-
x1 = seismic.data.copy()
|
|
126
|
-
x1[x1<=0] = 0
|
|
127
|
-
ax.fill_betweenx(y, x1=x1, **fkwargs)
|
|
128
|
-
return ax
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def _plot_3d(seismic: Seismic):
|
|
132
|
-
if not hasattr(seismic, "grid"):
|
|
133
|
-
seismic.grid = seismic.create_pyvista_grid()
|
|
134
|
-
|
|
135
|
-
seismic.grid.plot(cmap="seismic")
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def _plot_2d(seismic: Seismic):
|
|
139
|
-
pass
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
def _plot_hist(seismic: Seismic):
|
|
143
|
-
pass
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def from_segy(filepath:str, coords=None) -> Seismic:
|
|
147
|
-
"""Create a Seismic data object from a SEGY file.
|
|
148
|
-
|
|
149
|
-
Args:
|
|
150
|
-
filepath (str): Filepath to the SEGY file.
|
|
151
|
-
|
|
152
|
-
Returns:
|
|
153
|
-
Seismic: Seismic data object based on xarray.DataArray.
|
|
154
|
-
"""
|
|
155
|
-
segyio = optional_requirements.require_segyio()
|
|
156
|
-
with segyio.open(filepath) as sf:
|
|
157
|
-
sf.mmap() # memory mapping
|
|
158
|
-
xlines = sf.xlines
|
|
159
|
-
ilines = sf.ilines
|
|
160
|
-
samples = sf.samples
|
|
161
|
-
header = sf.bin
|
|
162
|
-
|
|
163
|
-
if not coords:
|
|
164
|
-
coords = [
|
|
165
|
-
("ilines", ilines),
|
|
166
|
-
("xlines", xlines),
|
|
167
|
-
("samples", samples)
|
|
168
|
-
]
|
|
169
|
-
|
|
170
|
-
cube = segyio.tools.cube(filepath)
|
|
171
|
-
seismic = Seismic(cube, coords=coords)
|
|
172
|
-
seismic.header = header
|
|
173
|
-
return seismic
|
|
1
|
+
"""
|
|
2
|
+
TODO: This is legacy code waiting to be updated to the new ideas
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import xarray as xr
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from subsurface import optional_requirements
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Seismic:
|
|
13
|
+
def __init__(self, data: np.ndarray, *args, **kwargs):
|
|
14
|
+
"""Seismic data object based on xarray.DataArray.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
data (Array): np.ndarray of the seismic cube / section.
|
|
18
|
+
"""
|
|
19
|
+
self.n_shp = len(data.shape)
|
|
20
|
+
self._xarray = xr.DataArray(self._flip(data), *args, **kwargs)
|
|
21
|
+
|
|
22
|
+
def __getattr__(self, attr):
|
|
23
|
+
if attr in self.__dict__:
|
|
24
|
+
return getattr(self, attr)
|
|
25
|
+
return getattr(self._xarray, attr)
|
|
26
|
+
|
|
27
|
+
def __getitem__(self, item):
|
|
28
|
+
if isinstance(item, str):
|
|
29
|
+
return self._xarray._getitem_coord(item)
|
|
30
|
+
|
|
31
|
+
# preserve coordinates
|
|
32
|
+
cp = list(self._xarray.coords.items()) # parent coordinates
|
|
33
|
+
coords = [(cp[i]) for i, it in enumerate(item) if not type(it) == int]
|
|
34
|
+
return Seismic(self._xarray[item].data, coords=coords)
|
|
35
|
+
|
|
36
|
+
def __repr__(self):
|
|
37
|
+
return self._xarray.__repr__()
|
|
38
|
+
|
|
39
|
+
def __str__(self):
|
|
40
|
+
return "Seismic"
|
|
41
|
+
|
|
42
|
+
def _flip(self, data: np.ndarray) -> np.ndarray:
|
|
43
|
+
"""Flip SEGY data to fit with numpy array orientation.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
data (np.ndarray): Seismic data.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
(np.ndarray) Flipped seismic data.
|
|
50
|
+
"""
|
|
51
|
+
if self.n_shp == 1:
|
|
52
|
+
return data
|
|
53
|
+
if self.n_shp == 2:
|
|
54
|
+
return data
|
|
55
|
+
if self.n_shp == 3:
|
|
56
|
+
return np.flip(data, axis=2)
|
|
57
|
+
|
|
58
|
+
def add_coords(self):
|
|
59
|
+
"""Ability to easily add physical coordinates."""
|
|
60
|
+
raise NotImplementedError
|
|
61
|
+
|
|
62
|
+
def to_segy(self, filepath: str) -> None:
|
|
63
|
+
"""Write given Seismic to SEGY file using segyio.tools.from_array().
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
filepath (str): Filepath for SEGY file.
|
|
67
|
+
"""
|
|
68
|
+
segyio = optional_requirements.require_segyio()
|
|
69
|
+
segyio.tools.from_array(filepath, self._xarray.data)
|
|
70
|
+
|
|
71
|
+
def plot_(self):
|
|
72
|
+
return xr.plot.plot._PlotMethods(self)
|
|
73
|
+
|
|
74
|
+
def plot(self):
|
|
75
|
+
if self.n_shp == 1:
|
|
76
|
+
return _plot_1d(self)
|
|
77
|
+
elif self.n_shp == 2:
|
|
78
|
+
pass
|
|
79
|
+
# TODO: plot seismic section using imshow for correct orientation
|
|
80
|
+
elif self.n_shp >= 3:
|
|
81
|
+
_plot_3d(self)
|
|
82
|
+
|
|
83
|
+
def create_pyvista_grid(self) -> 'pyvista.grid.UniformGrid':
|
|
84
|
+
"""Generate UniformGrid object for 3D plotting of the seismic.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
seismic (Seismic): Seismic object.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
(pv.grid.UniformGrid)
|
|
91
|
+
"""
|
|
92
|
+
pv = optional_requirements.require_pyvista()
|
|
93
|
+
grid = pv.UniformGrid()
|
|
94
|
+
grid.spacing = (1, 1, 1) # TODO: cell sizes? vertical exaggeration etc
|
|
95
|
+
grid.dimensions = np.array(self.data.shape) + 1
|
|
96
|
+
grid.cell_data["values"] = self.data.flatten(order="F")
|
|
97
|
+
# TODO: correct orientation of cube
|
|
98
|
+
return grid
|
|
99
|
+
|
|
100
|
+
def plot_3d_slices(self):
|
|
101
|
+
pv = optional_requirements.require_pyvista()
|
|
102
|
+
if self.n_shp != 3:
|
|
103
|
+
raise AssertionError("Seismic data needs to be a 3-D volume.")
|
|
104
|
+
# TODO: cmap, kwarg passthrough
|
|
105
|
+
pv.OrthogonalSlicer(self.grid)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _plot_1d(seismic: Seismic, linekwargs={}, fillkwargs={}):
|
|
109
|
+
import matplotlib.pyplot as plt
|
|
110
|
+
fig, ax = plt.subplots(figsize=(2,8))
|
|
111
|
+
|
|
112
|
+
lkwargs = dict(
|
|
113
|
+
color="black",
|
|
114
|
+
linewidth=1
|
|
115
|
+
)
|
|
116
|
+
lkwargs.update(linekwargs)
|
|
117
|
+
|
|
118
|
+
fkwargs = dict(
|
|
119
|
+
color="grey"
|
|
120
|
+
)
|
|
121
|
+
fkwargs.update(fillkwargs)
|
|
122
|
+
|
|
123
|
+
y = np.arange(0, *seismic.data.shape)
|
|
124
|
+
ax.plot(seismic.data, y, **lkwargs)
|
|
125
|
+
x1 = seismic.data.copy()
|
|
126
|
+
x1[x1<=0] = 0
|
|
127
|
+
ax.fill_betweenx(y, x1=x1, **fkwargs)
|
|
128
|
+
return ax
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _plot_3d(seismic: Seismic):
|
|
132
|
+
if not hasattr(seismic, "grid"):
|
|
133
|
+
seismic.grid = seismic.create_pyvista_grid()
|
|
134
|
+
|
|
135
|
+
seismic.grid.plot(cmap="seismic")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _plot_2d(seismic: Seismic):
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _plot_hist(seismic: Seismic):
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def from_segy(filepath:str, coords=None) -> Seismic:
|
|
147
|
+
"""Create a Seismic data object from a SEGY file.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
filepath (str): Filepath to the SEGY file.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Seismic: Seismic data object based on xarray.DataArray.
|
|
154
|
+
"""
|
|
155
|
+
segyio = optional_requirements.require_segyio()
|
|
156
|
+
with segyio.open(filepath) as sf:
|
|
157
|
+
sf.mmap() # memory mapping
|
|
158
|
+
xlines = sf.xlines
|
|
159
|
+
ilines = sf.ilines
|
|
160
|
+
samples = sf.samples
|
|
161
|
+
header = sf.bin
|
|
162
|
+
|
|
163
|
+
if not coords:
|
|
164
|
+
coords = [
|
|
165
|
+
("ilines", ilines),
|
|
166
|
+
("xlines", xlines),
|
|
167
|
+
("samples", samples)
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
cube = segyio.tools.cube(filepath)
|
|
171
|
+
seismic = Seismic(cube, coords=coords)
|
|
172
|
+
seismic.header = header
|
|
173
|
+
return seismic
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
import enum
|
|
2
|
-
from typing import List
|
|
3
|
-
import numpy as np
|
|
4
|
-
from subsurface.optional_requirements import require_scipy
|
|
5
|
-
|
|
6
|
-
from ....core.structs import UnstructuredData, StructuredData
|
|
7
|
-
|
|
8
|
-
__all__ = ['interpolate_unstructured_data_to_structured_data', ]
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class InterpolationMethod(enum.Enum):
|
|
12
|
-
linear = "linear"
|
|
13
|
-
nearest = "nearest"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def interpolate_unstructured_data_to_structured_data(
|
|
17
|
-
ud: UnstructuredData, attr_name: str,
|
|
18
|
-
resolution: List[int] = None,
|
|
19
|
-
interpolation_method: InterpolationMethod = InterpolationMethod.nearest) -> StructuredData:
|
|
20
|
-
if resolution is None:
|
|
21
|
-
resolution = [50, 50, 50]
|
|
22
|
-
boundaries_max = ud.vertex.max(axis=0)
|
|
23
|
-
boundaries_min = ud.vertex.min(axis=0)
|
|
24
|
-
coords = dict()
|
|
25
|
-
dims = ['x', 'y', 'z']
|
|
26
|
-
for e, i in enumerate(dims):
|
|
27
|
-
coords[i] = np.linspace(boundaries_min[e], boundaries_max[e], resolution[e], endpoint=False)
|
|
28
|
-
|
|
29
|
-
grid = np.meshgrid(*coords.values())
|
|
30
|
-
scipy = require_scipy()
|
|
31
|
-
interpolated_attributes = scipy.interpolate.griddata(
|
|
32
|
-
points=ud.vertex,
|
|
33
|
-
values=ud.attributes.loc[:, attr_name],
|
|
34
|
-
xi=tuple(grid),
|
|
35
|
-
method=interpolation_method.value
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
sd = StructuredData.from_numpy(
|
|
39
|
-
array=interpolated_attributes,
|
|
40
|
-
data_array_name=attr_name,
|
|
41
|
-
coords=coords
|
|
42
|
-
)
|
|
43
|
-
return sd
|
|
1
|
+
import enum
|
|
2
|
+
from typing import List
|
|
3
|
+
import numpy as np
|
|
4
|
+
from subsurface.optional_requirements import require_scipy
|
|
5
|
+
|
|
6
|
+
from ....core.structs import UnstructuredData, StructuredData
|
|
7
|
+
|
|
8
|
+
__all__ = ['interpolate_unstructured_data_to_structured_data', ]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class InterpolationMethod(enum.Enum):
|
|
12
|
+
linear = "linear"
|
|
13
|
+
nearest = "nearest"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def interpolate_unstructured_data_to_structured_data(
|
|
17
|
+
ud: UnstructuredData, attr_name: str,
|
|
18
|
+
resolution: List[int] = None,
|
|
19
|
+
interpolation_method: InterpolationMethod = InterpolationMethod.nearest) -> StructuredData:
|
|
20
|
+
if resolution is None:
|
|
21
|
+
resolution = [50, 50, 50]
|
|
22
|
+
boundaries_max = ud.vertex.max(axis=0)
|
|
23
|
+
boundaries_min = ud.vertex.min(axis=0)
|
|
24
|
+
coords = dict()
|
|
25
|
+
dims = ['x', 'y', 'z']
|
|
26
|
+
for e, i in enumerate(dims):
|
|
27
|
+
coords[i] = np.linspace(boundaries_min[e], boundaries_max[e], resolution[e], endpoint=False)
|
|
28
|
+
|
|
29
|
+
grid = np.meshgrid(*coords.values())
|
|
30
|
+
scipy = require_scipy()
|
|
31
|
+
interpolated_attributes = scipy.interpolate.griddata(
|
|
32
|
+
points=ud.vertex,
|
|
33
|
+
values=ud.attributes.loc[:, attr_name],
|
|
34
|
+
xi=tuple(grid),
|
|
35
|
+
method=interpolation_method.value
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
sd = StructuredData.from_numpy(
|
|
39
|
+
array=interpolated_attributes,
|
|
40
|
+
data_array_name=attr_name,
|
|
41
|
+
coords=coords
|
|
42
|
+
)
|
|
43
|
+
return sd
|