Source code for gempy.core.solution

import numpy as np
from typing import Union
import warnings
from skimage import measure
from gempy.utils.input_manipulation import find_interfaces_from_block_bottoms
from gempy.core.data import Grid, Surfaces
from gempy.core.data_modules.stack import Series, Stack
from gempy.utils.meta import _setdoc_pro
import gempy.utils.docstring as ds

try:
    from gempy.core.xsolution import XSolution

    _xsolution_imported = True
    inheritance = XSolution
except ImportError:
    inheritance = object
    _xsolution_imported = False


[docs]@_setdoc_pro( [Grid.__doc__, Surfaces.__doc__, Series.__doc__, ds.weights_vector, ds.sfai, ds.bai, ds.mai, ds.vai, ds.lith_block, ds.sfm, ds.bm, ds.mm, ds.vm, ds.vertices, ds.edges, ds.geological_map]) class Solution(inheritance): """This class stores the output of the interpolation and the necessary objects to visualize and manipulate this data. Depending on the activated grid (see :class:`Grid`) a different number of properties are returned returned: Args: grid (Grid): [s0] surfaces (Surfaces): [s1] series (Series): [s2] Attributes: grid (Grid) surfaces (Surfaces) series (Series) weights_vector (numpy.array): [s3] scalar_field_at_surface_points (numpy.array): [s4] block_at_surface_points (numpy.array): [s5] mask_at_surface_points (numpy.array): [s6] values_at_surface_points (numpy.array): [s7] lith_block (numpy.array): [s8] scalar_field_matrix (numpy.array): [s9] block_matrix (numpy.array): [s10] mask_matrix (numpy.array): [s11] mask_matrix_pad (numpy.array): mask matrix padded 2 block in order to guarantee that the layers intersect each other after marching cubes values_matrix (numpy.array): [s12] vertices (list[numpy.array]): [s13] edges (list[numpy.array]): [s14] geological_map (numpy.array): [s15] """
[docs] def __init__(self, grid: Grid = None, surfaces: Surfaces = None, series: Series = None, ): if _xsolution_imported: super().__init__(grid, surfaces, series) self.grid = grid self.stack = series self.surfaces = surfaces # Input data results self.weights_vector = np.empty(0) self.scalar_field_at_surface_points = np.array([]) self.block_at_surface_points = np.array([]) self.mask_at_surface_points = np.array([]) self.values_at_surface_points = np.array([]) # Regular Grid self.lith_block = np.empty(0) self.scalar_field_matrix = np.array([]) self.block_matrix = np.array([]) self.mask_matrix = np.array([]) self.mask_matrix_pad = [] self.values_matrix = np.array([]) self.gradient = np.empty(0) self.vertices = [] self.edges = [] self.geological_map = None self.sections = None self.custom = None # Center Grid self.fw_gravity = None self.fw_magnetics = None
def __repr__(self): return '\nLithology ids \n %s \n' \ % (np.array2string(self.lith_block))
[docs] def set_solution_to_regular_grid(self, values: Union[list, np.ndarray], compute_mesh: bool = True, compute_mesh_options: dict = None): """If regular grid is active set all the solution objects dependent on them and compute mesh. Args: values (list[np.array]): list with result of the theano evaluation (values returned by :func:`gempy.compute_model` function): - block_matrix - weights_vector - scalar_field_matrix - scalar field at interfaces - mask_matrix compute_mesh (bool): if True perform marching cubes algorithm to recover the surface mesh from the implicit model. compute_mesh_options (dict): options for the marching cube function. - rescale: True Returns: :class:`gempy.core.solutions.Solutions` """ if compute_mesh_options is None: compute_mesh_options = {} self.set_values_to_regular_grid(values) if compute_mesh is True: self.compute_all_surfaces(**compute_mesh_options) return self
def set_solutions(self, sol, compute_mesh, sort_surfaces, to_subsurface=False, **kwargs): # Set geology: self.set_values_to_surface_points(sol) if self.grid.active_grids[0] is np.True_: self.set_solution_to_regular_grid(sol, compute_mesh=compute_mesh, **kwargs) if self.grid.active_grids[1] is np.True_: self.set_solution_to_custom(sol) if self.grid.active_grids[2] is np.True_: self.set_solution_to_topography(sol) if self.grid.active_grids[3] is np.True_: self.set_solution_to_sections(sol) # Set gravity self.fw_gravity = sol[12] # TODO: [X] Set magnetcs and [ ] set topology @A.Schaaf probably it should # populate the topology object? self.fw_magnetics = sol[13] if _xsolution_imported and to_subsurface is True: self.set_values(sol) if compute_mesh is True: try: self.set_meshes(self.surfaces) except ValueError: warnings.warn('Meshes are empty.') return self def set_solution_to_custom(self, values: Union[list, np.ndarray]): l0, l1 = self.grid.get_grid_args('custom') self.custom = np.array( [values[0][:, l0: l1], values[4][:, l0: l1].astype(float)]) def set_solution_to_topography(self, values: Union[list, np.ndarray]): l0, l1 = self.grid.get_grid_args('topography') self.geological_map = np.array( [values[0][:, l0: l1], values[4][:, l0: l1].astype(float)]) def set_solution_to_sections(self, values: Union[list, np.ndarray]): l0, l1 = self.grid.get_grid_args('sections') self.sections = np.array( [values[0][:, l0: l1], values[4][:, l0: l1].astype(float)])
[docs] def set_values_to_regular_grid(self, values: Union[list, np.ndarray]): """Set all solution values to the correspondent attribute. Args: values (np.ndarray): values returned by `function: gempy.compute_model` function compute_mesh (bool): if true compute automatically the grid Returns: :class:`gempy.core.solutions.Solutions` """ regular_grid_length_l0, regular_grid_length_l1 = self.grid.get_grid_args( 'regular') # Lithology final block self.lith_block = values[0][0, regular_grid_length_l0: regular_grid_length_l1] # Properties self.values_matrix = values[0][1:, regular_grid_length_l0: regular_grid_length_l1] # Axis 0 is the series. Axis 1 is the value self.block_matrix = values[1][:, :, regular_grid_length_l0: regular_grid_length_l1] self.fault_block = values[2] # This here does not make any sense self.weights_vector = values[3] self.scalar_field_matrix = values[4][:, regular_grid_length_l0: regular_grid_length_l1] self.mask_matrix = values[6][:, regular_grid_length_l0: regular_grid_length_l1] self.fault_mask = values[7][:, regular_grid_length_l0: regular_grid_length_l1] # TODO add topology solutions return self
def set_values_to_surface_points(self, values): x_to_intep_length = self.grid.length[-1] self.scalar_field_at_surface_points = values[5] self.values_at_surface_points = values[0][1:, x_to_intep_length:] self.block_at_surface_points = values[1][:, :, x_to_intep_length:] # todo disambiguate below from self.scalar_field_at_surface_points self._scalar_field_at_surface = values[4][:, x_to_intep_length:] self.mask_at_surface_points = values[6][:, x_to_intep_length:] return self.scalar_field_at_surface_points
[docs] def compute_marching_cubes_regular_grid(self, level: float, scalar_field, mask_array=None, rescale=False, **kwargs): """Compute the surface (vertices and edges) of a given surface by computing marching cubes (by skimage) Args: level (float): value of the scalar field at the surface scalar_field (np.array): scalar_field vector objects mask_array (np.array): mask vector with trues where marching cubes has to be performed rescale (bool): if True surfaces will be located between 0 and 1 **kwargs: skimage.measure.marching_cubes_lewiner args (see below) Returns: list: vertices, simplices, normals, values See Also: :func:`skimage.measure.marching_cubes` """ rg = self.grid.regular_grid spacing = self.grid.regular_grid.get_dx_dy_dz(rescale=rescale) vertices, simplices, normals, values = measure.marching_cubes( scalar_field.reshape(rg.resolution), level, spacing=spacing, mask=mask_array, **kwargs) idx = [0, 2, 4] loc_0 = rg.extent_r[idx] if rescale else rg.extent[idx] loc_0 = loc_0 + np.array(spacing) / 2 vertices += np.array(loc_0).reshape(1, 3) return [vertices, simplices, normals, values]
[docs] def padding_mask_matrix(self, mask_topography=True, shift=2): """Pad as many elements as in shift to the masking arrays. This is done to guarantee intersection of layers if masked marching cubes are done Args: mask_topography (bool): if True mask also the topography. Default True shift: Number of voxels shifted for the topology. Default 1. Returns: numpy.ndarray: masked regular grid """ self.mask_matrix_pad = [] series_type = self.stack.df['BottomRelation'] for e, mask_series in enumerate(self.mask_matrix): mask_series_reshape = mask_series.reshape( self.grid.regular_grid.resolution) mask_pad = (mask_series_reshape + find_interfaces_from_block_bottoms( mask_series_reshape, True, shift=shift)) if series_type[e] == 'Fault': mask_pad = np.invert(mask_pad) if mask_topography and self.grid.regular_grid.mask_topo.size != 0: mask_pad *= np.invert(self.grid.regular_grid.mask_topo) self.mask_matrix_pad.append(mask_pad) return self.mask_matrix_pad
[docs] def compute_all_surfaces(self, **kwargs): """Compute all surfaces of the model given the geological features rules. Args: **kwargs: :any:`skimage.measure.marching_cubes` args (see below) Returns: list: vertices and edges See Also: :meth:`gempy.core.solution.Solution.compute_marching_cubes_regular_grid` """ self.vertices = [] self.edges = [] mask_topography = kwargs.pop('mask_topography', True) masked_marching_cubes = kwargs.pop('masked_marching_cubes', True) self.mask_matrix_pad = self.padding_mask_matrix( mask_topography=mask_topography ) series_type = self.stack.df['BottomRelation'] s_n = 0 active_indices = self.surfaces.df.groupby('isActive').groups[True] rescale = kwargs.pop('rescale', False) # We loop the scalar fields for e, scalar_field in enumerate(self.scalar_field_matrix): # Drop mask_array, sfas = self.prepare_marching_cubes_args(e, masked_marching_cubes, series_type) for level in sfas: s, v = self.try_compute_marching_cubes_on_the_regular_grid( level, mask_array, rescale, s_n, scalar_field, kwargs ) s_n = self.set_vertices_edges(active_indices, s, s_n, v) return self.vertices, self.edges
def try_compute_marching_cubes_on_the_regular_grid( self, level, mask_array, rescale, s_n, scalar_field, kwargs): try: v, s, norm, val = self.compute_marching_cubes_regular_grid( level, scalar_field, mask_array, rescale=rescale, **kwargs) except Exception as e: warnings.warn('Surfaces not computed due to: ' + str( e) + '. The surface is: Series: ' + str(e) + '; Surface Number:' + str(s_n)) v = np.nan s = np.nan return s, v def set_vertices_edges(self, active_indices, s, s_n, v): self.vertices.append(v) self.edges.append(s) idx = active_indices[s_n] self.surfaces.df.loc[idx, 'vertices'] = [v] self.surfaces.df.loc[idx, 'edges'] = [s] s_n += 1 return s_n def prepare_marching_cubes_args(self, e, masked_marching_cubes, series_type): sfas = self.scalar_field_at_surface_points[e] sfas = sfas[np.nonzero(sfas)] if masked_marching_cubes is True: if series_type[e - 1] == 'Onlap' and series_type[e - 2] == 'Erosion': mask_array = self.mask_matrix_pad[e-1] else: mask_array = self.mask_matrix_pad[e] else: mask_array = None return mask_array, sfas