Source code for gempy.plot.plot_api

"""
    This file is part of gempy.

    gempy is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    gempy is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with gempy.  If not, see <http://www.gnu.org/licenses/>.

    Module with classes and methods to perform implicit regional modelling based on
    the potential field method.
    Tested on Ubuntu 16

    Created on 10/11/2019

    @author: Alex Schaaf, Elisa Heim, Miguel de la Varga
"""

# This is for sphenix to find the packages
# sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )

from typing import Union, List, Any

import matplotlib.pyplot as plt
# from .vista import Vista
# import gempy as _gempy
import numpy as np
import pandas as pn
from gempy.plot.vista import GemPyToVista

# Keep Alex code hidden until we merge it properly
try:
    import pyvista as pv
    from ._vista import Vista as Vista

    PYVISTA_IMPORT = True
except ImportError:
    PYVISTA_IMPORT = False

from .visualization_2d import Plot2D

try:
    import mplstereonet

    mplstereonet_import = True
except ImportError:
    mplstereonet_import = False


[docs]def plot_2d(model, n_axis=None, section_names: list = None, cell_number: list = None, direction: list = 'y', show_data: Union[bool, list] = True, show_results: Union[bool, list] = True, show_lith: Union[bool, list] = True, show_values: Union[bool, list] = False, show_block: Union[bool, list] = False, show_scalar: Union[bool, list] = False, show_boundaries: Union[bool, list] = True, show_topography: Union[bool, list] = False, show_section_traces: Union[bool, list] = True, series_n: Union[int, List[int]] = 0, ve=1, block=None, regular_grid=None, kwargs_topography=None, kwargs_regular_grid=None, **kwargs): """Plot 2-D sections of geomodel. Plot cross sections either based on custom section traces or cell number in xyz direction. Options to plot lithology block, scalar field or rendered surface lines. Input data and topography can be included. Args: show_block (bool): If True and model has been computed, plot cross section of the final model. show_values (bool): If True and model has been computed, plot cross section of the value... TODO need to add attribute to choose which value to be plot model: Geomodel object with solutions. n_axis (int): Subplot axis for multiple sections section_names (list): Names of predefined custom section traces cell_number (list): Position of the array to plot direction (str): Cartesian direction to be plotted (xyz) show_data (bool): Show original input data. Defaults to True. show_results (bool): If False, override show lith, show_calar, show_values show_lith (bool): Show lithological block volumes. Defaults to True. show_scalar (bool): Show scalar field isolines. Defaults to False. show_boundaries (bool): Show surface boundaries as lines. Defaults to True. show_topography (bool): Show topography on plot. Defaults to False. series_n (int): number of the scalar field. ve (float): vertical exageration regular_grid (numpy.ndarray): Numpy array of the size of model.grid.regular_grid kwargs_topography (dict): * fill_contour * hillshade (bool): Calculate and add hillshading using elevation data * azdeg (float): azimuth of sun for hillshade * altdeg (float): altitude in degrees of sun for hillshade Keyword Args: legend (bool): If True plot legend. Default True show (bool): Call matplotlib show Returns: :class:`gempy.plot.visualization_2d.Plot2D`: Plot2D object """ if kwargs_regular_grid is None: kwargs_regular_grid = dict() if kwargs_topography is None: kwargs_topography = dict() if section_names is None and cell_number is None and direction is not None: cell_number = ['mid'] show = kwargs.get('show', True) if block is not None: import warnings regular_grid = block warnings.warn('block is going to be deprecated. Use regular grid instead', DeprecationWarning) section_names = [] if section_names is None else section_names section_names = np.atleast_1d(section_names) if cell_number is None: cell_number = [] elif cell_number == 'mid': cell_number = ['mid'] direction = [] if direction is None else direction if type(cell_number) != list: cell_number = [cell_number] if type(direction) != list: direction = [direction] if n_axis is None: n_axis = len(section_names) + len(cell_number) if show_results is False: show_lith = False show_values = False show_block = False show_scalar = False show_boundaries = False if type(show_data) is bool: show_data = [show_data] * n_axis if type(show_lith) is bool: show_lith = [show_lith] * n_axis if type(show_values) is bool: show_values = [show_values] * n_axis if type(show_block) is bool: show_block = [show_block] * n_axis if type(show_scalar) is bool: show_scalar = [show_scalar] * n_axis if type(show_boundaries) is bool: show_boundaries = [show_boundaries] * n_axis if type(show_topography) is bool: show_topography = [show_topography] * n_axis if type(series_n) is int: series_n = [series_n] * n_axis # init e e = 0 # is 10 and 10 because in the ax pos is the second digit n_columns_ = 1 if len(section_names) + len(cell_number) < 2 else 2 n_columns = n_columns_ * 10 # This is for the axis location syntax n_rows = (len(section_names) + len(cell_number)) / n_columns_ n_columns_ = np.max([n_columns_, 1]) n_rows = np.max([n_rows, 1]) p = Plot2D(model, **kwargs) p.create_figure(cols=n_columns_, rows=n_rows, **kwargs) for e, sn in enumerate(section_names): # Check if a plot that fills all pixels is plotted _is_filled = False assert e < 10, 'Reached maximum of axes' ax_pos = (round(n_axis / 2 + 0.1)) * 100 + n_columns + e + 1 temp_ax = p.add_section(section_name=sn, ax_pos=ax_pos, ve=ve, **kwargs) if show_data[e] is True: p.plot_data(temp_ax, section_name=sn, **kwargs) if show_lith[e] is True and model.solutions.lith_block.shape[0] != 0: _is_filled = True p.plot_lith(temp_ax, section_name=sn, **kwargs) elif show_values[e] is True and model.solutions.values_matrix.shape[0] != 0: _is_filled = True p.plot_values(temp_ax, series_n=series_n[e], section_name=sn, **kwargs) elif show_block[e] is True and model.solutions.block_matrix.shape[0] != 0: _is_filled = True p.plot_block(temp_ax, series_n=series_n[e], section_name=sn, **kwargs) if show_scalar[e] is True and model.solutions.scalar_field_matrix.shape[0] != 0: _is_filled = True p.plot_scalar_field(temp_ax, series_n=series_n[e], section_name=sn, **kwargs) if show_boundaries[e] is True and model.solutions.scalar_field_matrix.shape[0] != 0: p.plot_contacts(temp_ax, section_name=sn, **kwargs) if show_topography[e] is True: # Check if anything dense is plot. If not plot dense topography f_c_ = not _is_filled # f_c = kwargs_topography.get('fill_contour', f_c_) if 'fill_contour' not in kwargs_topography: kwargs_topography['fill_contour'] = f_c_ p.plot_topography(temp_ax, section_name=sn, # fill_contour=f_c, **kwargs_topography) if show_section_traces is True and sn == 'topography': p.plot_section_traces(temp_ax) if regular_grid is not None: p.plot_regular_grid(temp_ax, block=regular_grid, section_name=sn, **kwargs_regular_grid) temp_ax.set_aspect(ve) # If there are section we need to shift one axis for the perpendicular e = e + 1 for e2 in range(len(cell_number)): assert (e + e2) < 10, 'Reached maximum of axes' ax_pos = (round(n_axis / 2 + 0.1)) * 100 + n_columns + e + e2 + 1 temp_ax = p.add_section(cell_number=cell_number[e2], direction=direction[e2], ax_pos=ax_pos, ve=ve) if show_data[e + e2] is True: p.plot_data(temp_ax, cell_number=cell_number[e2], direction=direction[e2], **kwargs) if show_lith[e + e2] is True and model.solutions.lith_block.shape[0] != 0: p.plot_lith(temp_ax, cell_number=cell_number[e2], direction=direction[e2], **kwargs) elif show_values[e + e2] is True and model.solutions.values_matrix.shape[0] != 0: p.plot_values(temp_ax, series_n=series_n[e], cell_number=cell_number[e2], direction=direction[e2], **kwargs) elif show_block[e + e2] is True and model.solutions.block_matrix.shape[0] != 0: p.plot_block(temp_ax, series_n=series_n[e], cell_number=cell_number[e2], direction=direction[e2], **kwargs) if show_scalar[e + e2] is True and model.solutions.scalar_field_matrix.shape[0] != 0: p.plot_scalar_field(temp_ax, series_n=series_n[e], cell_number=cell_number[e2], direction=direction[e2], **kwargs) if show_boundaries[e + e2] is True and model.solutions.scalar_field_matrix.shape[0] != 0: p.plot_contacts(temp_ax, cell_number=cell_number[e2], direction=direction[e2], **kwargs) if show_topography[e + e2] is True: p.plot_topography(temp_ax, cell_number=cell_number[e2], direction=direction[e2], **kwargs_topography) if regular_grid is not None: p.plot_regular_grid(temp_ax, block=regular_grid, cell_number=cell_number[e2], direction=direction[e2], **kwargs_regular_grid) temp_ax.set_aspect(ve) if show is True: p.fig.show() return p
[docs]def plot_3d(model, plotter_type='basic', show_data: bool = True, show_results: bool = True, show_surfaces: bool = True, show_lith: bool = True, show_scalar: bool = False, show_boundaries: bool = True, show_topography: Union[bool, list] = False, scalar_field: str = None, ve=None, kwargs_plot_structured_grid=None, kwargs_plot_topography=None, kwargs_plot_data=None, image=False, off_screen=False, **kwargs) -> GemPyToVista: """foobar Args: model (:class:`gempy.core.model.Project`): Container class of all objects that constitute a GemPy model. plotter_type: PyVista plotter types. Supported plotters are: 'basic', 'background', and 'notebook'. show_data (bool): Show original input data. Defaults to True. show_results (bool): If False, override show lith, show_scalar, show_values show_lith (bool): Show lithological block volumes. Defaults to True. show_scalar (bool): Show scalar field isolines. Defaults to False. show_boundaries (bool): Show surface boundaries as lines. Defaults to True. show_topography (bool): Show topography on plot. Defaults to False. scalar_field (str): Name of the field to be activated series_n (int): number of the scalar field. ve (float): Vertical Exaggeration kwargs_plot_structured_grid: kwargs_plot_topography: **kwargs: Returns: :class:`gempy.plot.vista.GemPyToVista` """ if image is True: off_screen = True kwargs['off_screen'] = True plotter_type = 'basic' if show_results is False: show_surfaces = False show_scalar = False show_lith = False if kwargs_plot_topography is None: kwargs_plot_topography = dict() if kwargs_plot_structured_grid is None: kwargs_plot_structured_grid = dict() if kwargs_plot_data is None: kwargs_plot_data = dict() fig_path: str = kwargs.get('fig_path', None) gpv = GemPyToVista(model, plotter_type=plotter_type, **kwargs) if show_surfaces and len(model.solutions.vertices) != 0: gpv.plot_surfaces() if show_lith is True and model.solutions.lith_block.shape[0] != 0: gpv.plot_structured_grid('lith', **kwargs_plot_structured_grid) if show_scalar is True and model.solutions.scalar_field_matrix.shape[0] != 0: gpv.plot_structured_grid("scalar", series=scalar_field) if show_data: gpv.plot_data(**kwargs_plot_data) if show_topography and model._grid.topography is not None: gpv.plot_topography(**kwargs_plot_topography) if ve is not None: gpv.p.set_scale(zscale=ve) if fig_path is not None: gpv.p.show(screenshot=fig_path) if image is True: img = gpv.p.show(screenshot=True) plt.imshow(img[1]) plt.axis('off') plt.show() gpv.p.close() if off_screen is False: gpv.p.show() return gpv
def plot_interactive_3d( geo_model, scalar_field: str = 'all', series=None, show_topography: bool = False, **kwargs, ): """Plot interactive 3-D geomodel with three cross sections in subplots. Args: geo_model: Geomodel object with solutions. name (str): Can be either one of the following 'lith' - Lithology id block. 'scalar' - Scalar field block. 'values' - Values matrix block. render_topography: Render topography. Defaults to False. **kwargs: Returns: :class:`gempy.plot.vista.GemPyToVista` """ gpv = GemPyToVista(geo_model, plotter_type='background', shape="1|3") gpv.plot_data() gpv.plot_structured_grid_interactive(scalar_field=scalar_field, series=series, render_topography=show_topography, **kwargs) return gpv def plot_section_traces(model): """Plot section traces of section grid in 2-D topview (xy). Args: model: Geomodel object with solutions. Returns: (Plot2D) Plot2D object """ pst = plot_2d(model, n_axis=1, direction=['z'], cell_number=[-1], show_data=False, show_boundaries=False, show_lith=False, show=False) pst.plot_section_traces(pst.axes[0], show_data=False) return pst def plot_stereonet(self, litho=None, planes=True, poles=True, single_plots=False, show_density=False): if mplstereonet_import is False: raise ImportError( 'mplstereonet package is not installed. No stereographic projection available.') from collections import OrderedDict if litho is None: litho = self.model._orientations.df['surface'].unique() if single_plots is False: fig, ax = mplstereonet.subplots(figsize=(5, 5)) df_sub2 = pn.DataFrame() for i in litho: df_sub2 = df_sub2.append(self.model._orientations.df[ self.model._orientations.df[ 'surface'] == i]) for formation in litho: if single_plots: fig = plt.figure(figsize=(5, 5)) ax = fig.add_subplot(111, projection='stereonet') ax.set_title(formation, y=1.1) # if series_only: # df_sub = self.model.orientations.df[self.model.orientations.df['series'] == formation] # else: df_sub = self.model._orientations.df[ self.model._orientations.df['surface'] == formation] if poles: ax.pole(df_sub['azimuth'] - 90, df_sub['dip'], marker='o', markersize=7, markerfacecolor=self._color_lot[formation], markeredgewidth=1.1, markeredgecolor='gray', label=formation + ': ' + 'pole point') if planes: ax.plane(df_sub['azimuth'] - 90, df_sub['dip'], color=self._color_lot[formation], linewidth=1.5, label=formation + ': ' + 'azimuth/dip') if show_density: if single_plots: ax.density_contourf(df_sub['azimuth'] - 90, df_sub['dip'], measurement='poles', cmap='viridis', alpha=.5) else: ax.density_contourf(df_sub2['azimuth'] - 90, df_sub2['dip'], measurement='poles', cmap='viridis', alpha=.5) fig.subplots_adjust(top=0.8) handles, labels = ax.get_legend_handles_labels() by_label = OrderedDict(zip(labels, handles)) ax.legend(by_label.values(), by_label.keys(), bbox_to_anchor=(1.9, 1.1)) ax._grid(True, color='black', alpha=0.25) def plot_topology(geo_model, edges, centroids, direction="y", scale=True, label_kwargs=None, edge_kwargs=None): """Plot the topology adjacency graph in 2-D. Args: geo_model ([type]): GemPy geomodel instance. edges (Set[Tuple[int, int]]): Set of topology edges. centroids (Dict[int, Array[int, 3]]): Dictionary of topology id's and their centroids. direction (Union["x", "y", "z", optional): Section direction. Defaults to "y". label_kwargs (dict, optional): Keyword arguments for topology labels. Defaults to None. edge_kwargs (dict, optional): Keyword arguments for topology edges. Defaults to None. """ res = geo_model._grid.regular_grid.resolution if direction == "y": c1, c2 = (0, 2) e1 = geo_model._grid.regular_grid.extent[1] - geo_model._grid.regular_grid.extent[0] e2 = geo_model._grid.regular_grid.extent[5] - geo_model._grid.regular_grid.extent[4] d1 = geo_model._grid.regular_grid.extent[0] d2 = geo_model._grid.regular_grid.extent[4] # if len(list(centroids.items())[0][1]) == 2: # c1, c2 = (0, 1) r1 = res[0] r2 = res[2] elif direction == "x": c1, c2 = (1, 2) e1 = geo_model._grid.regular_grid.extent[3] - geo_model._grid.regular_grid.extent[2] e2 = geo_model._grid.regular_grid.extent[5] - geo_model._grid.regular_grid.extent[4] d1 = geo_model._grid.regular_grid.extent[2] d2 = geo_model._grid.regular_grid.extent[4] # if len(list(centroids.items())[0][1]) == 2: # c1, c2 = (0, 1) r1 = res[1] r2 = res[2] elif direction == "z": c1, c2 = (0, 1) e1 = geo_model._grid.regular_grid.extent[1] - geo_model._grid.regular_grid.extent[0] e2 = geo_model._grid.regular_grid.extent[3] - geo_model._grid.regular_grid.extent[2] d1 = geo_model._grid.regular_grid.extent[0] d2 = geo_model._grid.regular_grid.extent[2] # if len(list(centroids.items())[0][1]) == 2: # c1, c2 = (0, 1) r1 = res[0] r2 = res[1] tkw = { "color": "white", "fontsize": 13, "ha": "center", "va": "center", "weight": "ultralight", "family": "monospace", "verticalalignment": "center", "horizontalalignment": "center", "bbox": dict(boxstyle='round', facecolor='black', alpha=1), } if label_kwargs is not None: tkw.update(label_kwargs) lkw = { "linewidth": 1, "color": "black" } if edge_kwargs is not None: lkw.update(edge_kwargs) for a, b in edges: # plot edges x = np.array([centroids[a][c1], centroids[b][c1]]) y = np.array([centroids[a][c2], centroids[b][c2]]) if scale: x = x * e1 / r1 + d1 y = y * e2 / r2 + d2 plt.plot(x, y, **lkw) for node in np.unique(list(edges)): x = centroids[node][c1] y = centroids[node][c2] if scale: x = x * e1 / r1 + d1 y = y * e2 / r2 + d2 plt.text(x, y, str(node), **tkw) def plot_ar(geo_model, path=None, project_name=None, api_token=None, secret=None): """ Create, upload and retrieve tag to visualize the model in AR in rexview https://www.rexos.org/getting-started/ Args: geo_model (gempy.Model): path: Location for rex files. Default cwd project_name: Name of the project in rexos api_token: rexos api token secret: rexos secret Returns: gempy.addons.rex_api.Rextag """ from gempy.addons.rex_api import upload_to_rexcloud from gempy.addons.gempy_to_rexfile import write_rex, geomodel_to_rex if project_name is None: project_name = geo_model.meta.project_name if path is None: path = './' rex_bytes = geomodel_to_rex(geo_model) files_path = write_rex(rex_bytes, path) project_name_ = project_name for i in range(40): try: tag = upload_to_rexcloud(files_path, project_name=project_name_, api_token=api_token, secret=secret) break except ConnectionError: project_name_ = project_name + str(i) pass return tag