CIGVis is a tool for geophysical data visualization, which developed by  Computational Interpretation Group (CIG)
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
CIGVis - a tool for visualizing multidimensional geophysical data
==================================================================

**cigvis** is a tool for visualizing multidimensional geophysical data, 
developed by the 
`Computational Interpretation Group (CIG) <https://cig.ustc.edu.cn/main.htm>`_. 
Users can quickly visualize data with just a few lines of code.

cigvis can be used for various geophysical data visualizations, 
including 3D seismic data, overlays of seismic data with other 
information like labels, faults, RGT, horizon surfaces, well log 
trajectories, and well log curves, 3D geological bodies, 2D data, 
and 1D data, among others. Its GitHub repository can be found at 
`github.com/JintaoLee-Roger/cigvis <https://github.com/JintaoLee-Roger/cigvis>`_, 
and documentation is available at 
`https://cigvis.readthedocs.io/ <https://cigvis.readthedocs.io/>`.

cigvis leverages the power of underlying libraries such as 
`vispy <https://github.com/vispy/vispy>`_ for 3D visualization, 
`matplotlib <https://matplotlib.org/>`_ for 2D and 1D visualization, 
and `plotly <https://plotly.com/>`_ for Jupyter environments (work in 
progress). The 3D visualization component is heavily based on the code from 
`yunzhishi/seismic-canvas <https://github.com/yunzhishi/seismic-canvas>`_ 
and has been further developed upon this foundation.
"""

import sys
from .config import *
from . import io
from . import colormap
from . import meshs

injupyter = 'ipykernel_launcher.py' in sys.argv[0] or 'lab' in sys.argv[0]

if injupyter:
    from .plotlyplot import *
else:
    from .vispyplot import *

from .mpl2dplot import *
from .mpl1dplot import *
cigvis/__init__.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
Tools for colormap
------------------

Including:

- custom colormap: petrel

- cmap to vispy and plotly

"""

from typing import List, Union
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap, LinearSegmentedColormap
from matplotlib.colors import Colormap as mplColormap
import matplotlib.colors as mcolors
import numpy as np

from vispy.color import Colormap as vispyColormap
from vispy.color import get_colormap as get_vispycmap

from .customcmap import *


def get_cmap_from_str(cmap: str, includevispy: bool = False):
    """
    return a Colormap from a cmap string
    
    Parameters
    ----------
    cmap : str
        colormap name string
    includevispy : bool
        include vispy's cmaps
    """
    if cmap in list_custom_cmap():
        cmap = get_custom_cmap(cmap)
    else:
        try:
            cmap = plt.get_cmap(cmap)
        except:
            pass

    if isinstance(cmap, str) and includevispy:
        try:
            cmap = get_vispycmap(cmap)
        except:
            pass

    return cmap


def cmap_to_plotly(cmap):
    """
    convert matplotlib's cmap into plotly's style

    Parameters
    ----------
    cmap : cmap in matplotlib
        matplotlib's cmap
    
    Returns
    -------
    plotly_cmap : List
        colormap used in plotly
    """
    if isinstance(cmap, str):
        if cmap in list_custom_cmap():
            cmap = get_custom_cmap(cmap)
        else:
            cmap = plt.get_cmap(cmap)

    d = cmap(np.arange(cmap.N))
    d = (d * 255).astype(int)
    plotly_cmap = []

    for k in range(256):
        color = f'rgb({d[k, 0]},{d[k, 1]},{d[k, 2]})'
        plotly_cmap.append([k / 255, color])

    return plotly_cmap


def cmap_to_vispy(cmap) -> vispyColormap:
    """
    convert matplotlib's cmap into vispy's style

    Parameters
    ----------
    cmap : cmap in matplotlib
        matplotlib's cmap
    
    Returns
    -------
    vispy_cmap : vispy.color.Colormap
        colormap used in vispy
    """
    if isinstance(cmap, str):
        cmap = get_cmap_from_str(cmap, True)
    if isinstance(cmap, mplColormap):
        colors = cmap(np.arange(cmap.N))
        return vispyColormap(colors)
    if isinstance(cmap, vispyColormap):
        return cmap


def custom_disc_cmap(values: List, colors: List):
    """
    Custom a discrete colormap from values and colors.
    Like this:

    >>> values: [v1, v2, v3, v4, v5]
    >>> colors: [c1, c2, c3, c4, c5]
    >>> bound: [v1, (v1+v2)/2, (v2+v3)/2, (v3+v4)/2, (v4+v5)/2, v5]
            = [b1, b2, b3, b4, b5, b6]
    >>> [b1, b2]: c1, [b2, b3]: c2, [b3, b4]: c3, [b4, b5]: c4, [b5, b6]: c5 

    Parameters
    ----------
    values : List
        values list, N elements
    colors : List
        the correspanding colors of values, N elements, 
        each elements can be str, tuple, such as 'red', '#7F0000', (0, 0.5, 0)

    Returns
    -------
    cmap : matplotlib.colors.LinearSegmentedColormap
        colormap

    Examples
    ----------
    >>> values = [0, 2, 7, 8]
    >>> colors = ['green', '#7f6580', 'blue', (0, 0.8, 0.2)]
    >>> cmap = custom_disc_cmap(values, colors)
    """
    assert len(values) == len(colors)
    colors = [mcolors.to_rgb(c) for c in colors]
    values, colors = [list(c) for c in zip(*sorted(zip(values, colors)))]
    colors.append(colors[-1])

    bound = [values[0]]
    for i in range(len(values) - 1):
        bound.append((values[i] + values[i + 1]) / 2)
    bound.append(values[-1])
    l = bound[-1] - bound[0]

    red = [(0, colors[0][0], colors[0][0])]
    green = [(0, colors[0][1], colors[0][1])]
    blue = [(0, colors[0][2], colors[0][2])]
    for i in range(len(bound) - 1):
        red.append(
            ((bound[i + 1] - bound[0]) / l, colors[i][0], colors[i + 1][0]))
        green.append(
            ((bound[i + 1] - bound[0]) / l, colors[i][1], colors[i + 1][1]))
        blue.append(
            ((bound[i + 1] - bound[0]) / l, colors[i][2], colors[i + 1][2]))

    cdict = {'red': red, 'green': green, 'blue': blue}

    return LinearSegmentedColormap('custom_cmap', cdict)


def get_colors_from_cmap(cmap, clim: List, values: List):
    """
    get colors from a cmap when special vmin, vmax and values

    Parameters
    ----------
    cmap : str or matplotlib.color.Colormap
        input cmap
    clim : List
        [vmin, vmax] to Normalize
    values : List
        list of values

    Returns
    -------
    c : List
        colors at norm(values) locations
    """
    values = np.array(values)
    if isinstance(cmap, str):
        if cmap in list_custom_cmap():
            cmap = get_custom_cmap(cmap)
        else:
            if mcolors.is_color_like(cmap):
                c = np.array([mcolors.to_rgba(cmap)] * len(values))
                c[np.isnan(values), -1] = 0
                return c
            else:
                cmap = plt.get_cmap(cmap)
    if isinstance(cmap, vispyColormap):
        rgba = cmap.colors.rgba
        cmap = LinearSegmentedColormap.from_list('vispy_cmap', rgba)

    norm = mpl.colors.Normalize(clim[0], clim[1])
    locs = norm(values)
    c = cmap(locs)

    return c


def discrete_cmap(cmap, clim, values):
    colors = get_colors_from_cmap(cmap, clim, values)
    cmap = custom_disc_cmap(values, colors)

    return cmap


def set_alpha(cmap, alpha: float, forvispy: bool = True):
    """
    Set the alpha blending value, between 0 (transparent) and 1 (opaque)
    for a cmap. This function is mainly used in vispy which
    doesn't contain a parameter like `alpha` in matplotlib to set opacity

    Parameters
    ----------
    cmap : str or vispyColormap or mplColormap
        the input cmap
    alpha : float
        opacity
    forvispy : bool
        return a vispy Colormap or matplotlib Colormap

    Returns
    -------
    cmap : str or vispyColormap or mplColormap
    """
    if isinstance(cmap, str):
        cmap = get_cmap_from_str(cmap)

    if isinstance(cmap, vispyColormap):
        colors = cmap.colors.rgba
        colors[:, -1] = alpha
        return vispyColormap(colors)
    elif isinstance(cmap, mplColormap):
        cmap = ListedColormap(cmap(np.arange(cmap.N), alpha=alpha))
        if forvispy:
            cmap = cmap_to_vispy(cmap)
        return cmap
    else:
        raise ValueError("unkown cmap")


def set_alpha_except_min(cmap, alpha: float, forvispy: bool = True):
    """
    Set the alpha blending value, between 0 (transparent) and 1 (opaque)
    for a cmap and set the alpha of the min value as 0.
    This means mask the min value when used for a discrete show.
    """
    if isinstance(cmap, str):
        cmap = get_cmap_from_str(cmap)

    if isinstance(cmap, vispyColormap):
        colors = cmap.colors.rgba
        colors[:, -1] = alpha
        colors[0, 3] = 0
        return vispyColormap(colors)
    elif isinstance(cmap, mplColormap):
        colors = cmap(np.arange(cmap.N), alpha=alpha)
        colors[0, 3] = 0
        cmap = ListedColormap(colors)
        if forvispy:
            cmap = cmap_to_vispy(cmap)
        return cmap
    else:
        raise ValueError("unkown cmap")


def set_alpha_except_max(cmap, alpha: float, forvispy: bool = True):
    """
    Set the alpha blending value, between 0 (transparent) and 1 (opaque)
    for a cmap and set the alpha of the **max** value as 0.
    This means mask the **max** value when used for a discrete show.
    """
    if isinstance(cmap, str):
        cmap = get_cmap_from_str(cmap)

    if isinstance(cmap, vispyColormap):
        colors = cmap.colors.rgba
        colors[:, -1] = alpha
        colors[-1, 3] = 0
        return vispyColormap(colors)
    elif isinstance(cmap, mplColormap):
        colors = cmap(np.arange(cmap.N), alpha=alpha)
        colors[-1, 3] = 0
        cmap = ListedColormap(colors)
        if forvispy:
            cmap = cmap_to_vispy(cmap)
        return cmap
    else:
        raise ValueError("unkown cmap")


def set_alpha_except_values(cmap,
                            alpha: float,
                            clim: List,
                            values: List,
                            forvispy: bool = True):
    """
    Set the alpha blending value, between 0 (transparent) and 1 (opaque)
    for a cmap. And set the alpha of the select values as 0 when clim is applied.
    This means mask the values when used for a discrete show.

    Parameters
    ----------
    cmap : str or vispyColormap or mplColormap
        the input cmap
    alpha : float
        opacity
    clim : List
        [vmin, vmax] for mpl.colors.Normalize
    values : List
        the select values to except (or mask) 
    forvispy : bool
        return a vispy Colormap or matplotlib Colormap

    Returns
    -------
    cmap : str or vispyColormap or mplColormap
    """
    if isinstance(cmap, str):
        cmap = get_cmap_from_str(cmap)
    if isinstance(cmap, vispyColormap):
        cmap = ListedColormap(cmap.colors.rgba)
    colors = cmap(np.arange(cmap.N), alpha=alpha)
    norm = mpl.colors.Normalize(vmin=clim[0], vmax=clim[1])
    index = np.interp(norm(values), np.linspace(0, 1, cmap.N),
                      np.arange(cmap.N))
    ceil = np.ceil(index).astype(int)
    floor = np.floor(index).astype(int)
    colors[ceil, 3] = 0
    colors[floor, 3] = 0

    if forvispy:
        return vispyColormap(colors, interpolation='zero')

    return ListedColormap(colors)


def set_alpha_except_top(cmap, alpha, clim, segm, forvispy=True):
    """
    Set the alpha blending value, between 0 (transparent) and 1 (opaque)
    for a cmap. And set alphas in range `[segm, clim[1]]` to 0
    """
    if isinstance(cmap, str):
        cmap = get_cmap_from_str(cmap)
    if isinstance(cmap, vispyColormap):
        cmap = ListedColormap(cmap.colors.rgba)
    colors = cmap(np.arange(cmap.N), alpha=alpha)
    norm = mpl.colors.Normalize(vmin=clim[0], vmax=clim[1])
    index = np.interp(norm(segm), np.linspace(0, 1, cmap.N),
                      np.arange(cmap.N)).astype(int)
    colors[index:, 3] = 0

    if forvispy:
        return vispyColormap(colors, interpolation='zero')

    return ListedColormap(colors)


def set_alpha_except_bottom(cmap, alpha, clim, segm, forvispy=True):
    """
    Set the alpha blending value, between 0 (transparent) and 1 (opaque)
    for a cmap. And set alphas in range `[clim[0], segm]` to 0
    """
    if isinstance(cmap, str):
        cmap = get_cmap_from_str(cmap)
    if isinstance(cmap, vispyColormap):
        cmap = ListedColormap(cmap.colors.rgba)
    colors = cmap(np.arange(cmap.N), alpha=alpha)
    norm = mpl.colors.Normalize(vmin=clim[0], vmax=clim[1])
    index = np.interp(norm(segm), np.linspace(0, 1, cmap.N),
                      np.arange(cmap.N)).astype(int)
    colors[:index, 3] = 0

    if forvispy:
        return vispyColormap(colors, interpolation='zero')

    return ListedColormap(colors)


def set_alpha_except_ranges(cmap, alpha, clim, r, forvispy=True):
    """
    Set the alpha blending value, between 0 (transparent) and 1 (opaque)
    for a cmap. And set the alpha of the range as 0 when clim is applied.
    This means mask the range of values when used for a discrete show.

    Parameters
    ----------
    r : List
        ranges, like [0, 2] or [[1, 2], [5, 8], ...]
    """
    if not isinstance(r[0], List):
        r = [r]
    assert all([len(c) == 2 for c in r])

    if isinstance(cmap, str):
        cmap = get_cmap_from_str(cmap)
    if isinstance(cmap, vispyColormap):
        cmap = ListedColormap(cmap.colors.rgba)
    colors = cmap(np.arange(cmap.N), alpha=alpha)

    norm = mpl.colors.Normalize(vmin=clim[0], vmax=clim[1])
    index = np.interp(norm(r), np.linspace(0, 1, cmap.N),
                      np.arange(cmap.N)).astype(int)

    for c in index:
        colors[c[0]:c[1], 3] = 0

    if forvispy:
        return vispyColormap(colors, interpolation='zero')

    return ListedColormap(colors)


def list_custom_cmap() -> List:
    """
    avaliable cmap list
    """

    return list(custom_cdict.keys())


def get_custom_cmap(cmap: str) -> LinearSegmentedColormap:
    """
    Get a cmap from a name used in Opendtect software. 
    The supported cmap can be see by `get_opendtect_cmap_list()`
    """
    cmap_list = list_custom_cmap()
    if cmap not in cmap_list:
        raise RuntimeWarning(
            f"cmap {cmap} not in opendtect cmap list {cmap_list}")

    return LinearSegmentedColormap(f'{cmap}', custom_cdict[cmap])


def plot_cmap(cmap, norm: List = None, save: str = None):
    """
    plot a cmap with a norm

    Parameters
    -----------
    cmap : str or Colormap
        colormap
    norm : List
        [vmin, vmax], set value range
    save : str
        name to save
    """
    if norm is None:
        norm = mpl.colors.Normalize(-10, 10)
    if isinstance(norm, List):
        norm = mpl.colors.Normalize(norm[0], norm[1])
    if isinstance(cmap, str):
        cmap = get_cmap_from_str(cmap)

    name = cmap if isinstance(cmap, str) else cmap.name
    fig, ax = plt.subplots(figsize=(10, 1))
    fig.subplots_adjust(bottom=0.5)
    fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap),
                 cax=ax,
                 orientation='horizontal',
                 label=f'{name} colormap')
    if save is not None:
        plt.savefig(save, bbox_inches='tight', pad_inches=0.02, dpi=600)
    plt.show()


def plot_all_custom_cmap(norm: List = None, save: str = None, dpi=300):
    """
    plot all custom cmaps with a norm
    """
    if norm is None:
        norm = mpl.colors.Normalize(-10, 10)
    if isinstance(norm, List):
        norm = mpl.colors.Normalize(norm[0], norm[1])
    cmap_list = list_custom_cmap()
    N = len(cmap_list)
    nrows = np.ceil(N / 4).astype(int)

    fig, axs = plt.subplots(nrows, 4, figsize=(15, nrows * 0.9))

    for i in range(nrows):
        for j in range(4):
            idx = j + i * 4
            if idx < N:
                cmap = get_custom_cmap(cmap_list[idx])
                fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap),
                             cax=axs[i, j],
                             orientation='horizontal',
                             label=f'{cmap_list[idx]} colormap')
    plt.tight_layout()

    if save is not None:
        plt.savefig(save, bbox_inches='tight', pad_inches=0.02, dpi=dpi)

    plt.show()

cigvis/colormap.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.

"""
global configs
--------------

Data order: is_line_first

axis system: is x/y/z reversed
"""


# Is the first dimension inline?
# If True, volume.shape is like (ni, nx, nt)
# If False, volume.shape is like (nt, nx, ni)
from typing import Tuple

LINE_FIRST = True

# axis reversed
X_REVERSED = False
Y_REVERSED = True
Z_REVERSED = True


def is_line_first() -> bool:
    global LINE_FIRST
    return LINE_FIRST


def set_order(line_first: bool) -> None:
    global LINE_FIRST
    assert isinstance(line_first, bool)
    LINE_FIRST = line_first


def is_x_reversed() -> bool:
    global X_REVERSED
    return X_REVERSED


def set_x_reversed(reverse: bool) -> None:
    global X_REVERSED
    assert isinstance(reverse, bool)
    X_REVERSED = reverse


def is_y_reversed() -> bool:
    global Y_REVERSED
    return Y_REVERSED


def set_y_reversed(reverse: bool) -> None:
    global Y_REVERSED
    assert isinstance(reverse, bool)
    Y_REVERSED = reverse


def is_z_reversed() -> bool:
    global Z_REVERSED
    return Z_REVERSED


def set_z_reversed(reverse: bool) -> None:
    global Z_REVERSED
    assert isinstance(reverse, bool)
    Z_REVERSED = reverse


def is_axis_reversed() -> Tuple:
    global X_REVERSED
    global Y_REVERSED
    global Z_REVERSED

    return X_REVERSED, Y_REVERSED, Z_REVERSED


def set_axis_reversed(x: bool, y: bool, z: bool) -> None:
    global X_REVERSED
    global Y_REVERSED
    global Z_REVERSED
    assert isinstance(x, bool)
    assert isinstance(y, bool)
    assert isinstance(z, bool)

    X_REVERSED = x
    Y_REVERSED = y
    Z_REVERSED = z
cigvis/config.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
Colormap from OpendTect software:
the file is in `7.0.0/data/ColTabs`


"""

custom_cdict = {
    'stratum': {
        'red': [(0.0, 0.9019607843137255, 0.9019607843137255),
                (0.03125, 0.9019607843137255, 0.9294117647058824),
                (0.0625, 0.9294117647058824, 1.0),
                (0.09375, 1.0, 0.4549019607843137),
                (0.125, 0.4549019607843137, 0.2196078431372549),
                (0.15625, 0.2196078431372549, 0.09803921568627451),
                (0.1875, 0.09803921568627451, 0.4549019607843137),
                (0.21875, 0.4549019607843137, 0.21568627450980393),
                (0.25, 0.21568627450980393, 0.011764705882352941),
                (0.28125, 0.011764705882352941, 0.0),
                (0.3125, 0.0, 0.21568627450980393),
                (0.34375, 0.21568627450980393, 0.9294117647058824),
                (0.375, 0.9294117647058824, 0.5019607843137255),
                (0.40625, 0.5019607843137255, 0.45098039215686275),
                (0.4375, 0.45098039215686275, 0.4666666666666667),
                (0.46875, 0.4666666666666667, 0.9215686274509803),
                (0.5, 0.9215686274509803, 0.9019607843137255),
                (0.53125, 0.9019607843137255, 0.47843137254901963),
                (0.5625, 0.47843137254901963, 0.5019607843137255),
                (0.59375, 0.5019607843137255, 0.7529411764705882),
                (0.625, 0.7529411764705882, 0.23529411764705882),
                (0.65625, 0.23529411764705882, 0.8705882352941177),
                (0.6875, 0.8705882352941177, 0.47843137254901963),
                (0.71875, 0.47843137254901963, 0.5098039215686274),
                (0.75, 0.5098039215686274, 0.6823529411764706),
                (0.78125, 0.6823529411764706, 0.49411764705882355),
                (0.8125, 0.49411764705882355, 0.7490196078431373),
                (0.84375, 0.7490196078431373, 0.6705882352941176),
                (0.875, 0.6705882352941176, 0.6901960784313725),
                (0.90625, 0.6901960784313725, 0.8784313725490196),
                (0.9375, 0.8784313725490196, 0.6823529411764706),
                (0.96875, 0.6823529411764706, 0.6588235294117647),
                (1.0, 0.6588235294117647, 0.6588235294117647)],
        'green': [(0.0, 0.19607843137254902, 0.19607843137254902),
                  (0.03125, 0.19607843137254902, 0.5294117647058824),
                  (0.0625, 0.5294117647058824, 1.0),
                  (0.09375, 1.0, 0.9803921568627451),
                  (0.125, 0.9803921568627451, 0.49411764705882355),
                  (0.15625, 0.49411764705882355, 0.24705882352941178),
                  (0.1875, 0.24705882352941178, 0.9803921568627451),
                  (0.21875, 0.9803921568627451, 0.49411764705882355),
                  (0.25, 0.49411764705882355, 0.0), (0.28125, 0.0, 0.0),
                  (0.3125, 0.0, 0.49019607843137253),
                  (0.34375, 0.49019607843137253, 0.5333333333333333),
                  (0.375, 0.5333333333333333, 0.5019607843137255),
                  (0.40625, 0.5019607843137255, 0.08235294117647059),
                  (0.4375, 0.08235294117647059, 0.08235294117647059),
                  (0.46875, 0.08235294117647059, 0.5333333333333333),
                  (0.5, 0.5333333333333333, 0.19607843137254902),
                  (0.53125, 0.19607843137254902, 0.2627450980392157),
                  (0.5625, 0.2627450980392157, 0.5019607843137255),
                  (0.59375, 0.5019607843137255, 0.7529411764705882),
                  (0.625, 0.7529411764705882, 0.023529411764705882),
                  (0.65625, 0.023529411764705882, 0.7411764705882353),
                  (0.6875, 0.7411764705882353, 0.5803921568627451),
                  (0.71875, 0.5803921568627451, 0.42745098039215684),
                  (0.75, 0.42745098039215684, 0.5529411764705883),
                  (0.78125, 0.5529411764705883, 0.5019607843137255),
                  (0.8125, 0.5019607843137255, 0.7333333333333333),
                  (0.84375, 0.7333333333333333, 0.8549019607843137),
                  (0.875, 0.8549019607843137, 0.2980392156862745),
                  (0.90625, 0.2980392156862745, 0.8509803921568627),
                  (0.9375, 0.8509803921568627, 0.9294117647058824),
                  (0.96875, 0.9294117647058824, 0.792156862745098),
                  (1.0, 0.792156862745098, 0.792156862745098)],
        'blue': [(0.0, 0.13725490196078433, 0.13725490196078433),
                 (0.03125, 0.13725490196078433, 0.2),
                 (0.0625, 0.2, 0.3333333333333333),
                 (0.09375, 0.3333333333333333, 0.30196078431372547),
                 (0.125, 0.30196078431372547, 0.13333333333333333),
                 (0.15625, 0.13333333333333333, 0.06274509803921569),
                 (0.1875, 0.06274509803921569, 0.9921568627450981),
                 (0.21875, 0.9921568627450981, 0.9647058823529412),
                 (0.25, 0.9647058823529412, 0.9490196078431372),
                 (0.28125, 0.9490196078431372, 0.6039215686274509),
                 (0.3125, 0.6039215686274509, 0.7294117647058823),
                 (0.34375, 0.7294117647058823, 0.5215686274509804),
                 (0.375, 0.5215686274509804, 0.9686274509803922),
                 (0.40625, 0.9686274509803922, 0.9529411764705882),
                 (0.4375, 0.9529411764705882, 0.47843137254901963),
                 (0.46875, 0.47843137254901963, 0.9686274509803922),
                 (0.5, 0.9686274509803922, 0.9568627450980393),
                 (0.53125, 0.9568627450980393, 0.08235294117647059),
                 (0.5625, 0.08235294117647059, 0.5019607843137255),
                 (0.59375, 0.5019607843137255, 0.7529411764705882),
                 (0.625, 0.7529411764705882, 0.24313725490196078),
                 (0.65625, 0.24313725490196078, 0.3137254901960784),
                 (0.6875, 0.3137254901960784, 0.5843137254901961),
                 (0.71875, 0.5843137254901961, 0.48627450980392156),
                 (0.75, 0.48627450980392156, 0.4588235294117647),
                 (0.78125, 0.4588235294117647, 0.15294117647058825),
                 (0.8125, 0.15294117647058825, 0.8549019607843137),
                 (0.84375, 0.8549019607843137, 0.9725490196078431),
                 (0.875, 0.9725490196078431, 0.2627450980392157),
                 (0.90625, 0.2627450980392157, 0.6627450980392157),
                 (0.9375, 0.6627450980392157, 0.7607843137254902),
                 (0.96875, 0.7607843137254902, 0.27450980392156865),
                 (1.0, 0.27450980392156865, 0.27450980392156865)]
    },
    'AI': {
        'red': [(0.0, 0.0, 0.0), (0.091, 0.0, 0.0),
                (0.1818, 0.7450980392156863, 0.7450980392156863),
                (0.2727, 1.0, 1.0), (0.3636, 1.0, 1.0), (0.4545, 1.0, 1.0),
                (0.5455, 0.7450980392156863, 0.7450980392156863),
                (0.6364, 0.0, 0.0), (0.7273, 0.0, 0.0), (0.8182, 0.0, 0.0),
                (0.9091, 1.0, 1.0),
                (1.0, 0.7490196078431373, 0.7490196078431373)],
        'green': [(0.0, 0.7450980392156863, 0.7450980392156863),
                  (0.091, 1.0, 1.0), (0.1818, 1.0, 1.0), (0.2727, 1.0, 1.0),
                  (0.3636, 0.7450980392156863, 0.7450980392156863),
                  (0.4545, 0.0, 0.0),
                  (0.5455, 0.4980392156862745, 0.4980392156862745),
                  (0.6364, 1.0, 1.0),
                  (0.7273, 0.7450980392156863, 0.7450980392156863),
                  (0.8182, 0.0, 0.0), (0.9091, 0.0, 0.0), (1.0, 0.0, 0.0)],
        'blue': [(0.0, 0.0, 0.0), (0.091, 0.0, 0.0), (0.1818, 0.0, 0.0),
                 (0.2727, 0.0, 0.0), (0.3636, 0.0, 0.0), (0.4545, 0.0, 0.0),
                 (0.5455, 0.0, 0.0), (0.6364, 1.0, 1.0), (0.7273, 1.0, 1.0),
                 (0.8182, 1.0, 1.0), (0.9091, 1.0, 1.0), (1.0, 1.0, 1.0)]
    },
    'Chimney': {
        'red': [(0.0, 0.0, 0.0),
                (0.436548, 0.19607843137254902, 0.19607843137254902),
                (0.482233, 0.16470588235294117, 0.16470588235294117),
                (0.832487, 0.0, 0.0), (0.959391, 1.0, 1.0), (1.0, 1.0, 1.0)],
        'green': [(0.0, 0.0, 0.0),
                  (0.436548, 0.44313725490196076, 0.44313725490196076),
                  (0.482233, 0.32941176470588235, 0.32941176470588235),
                  (0.832487, 1.0, 1.0), (0.959391, 1.0, 1.0), (1.0, 1.0, 1.0)],
        'blue': [(0.0, 1.0, 1.0),
                 (0.436548, 0.8117647058823529, 0.8117647058823529),
                 (0.482233, 1.0, 1.0), (0.832487, 0.0, 0.0),
                 (0.959391, 0.0, 0.0), (1.0, 0.0, 0.0)]
    },
    'Depth': {
        'red': [(0.0, 1.0, 1.0), (0.311558, 0.0, 0.0), (0.5, 1.0, 1.0),
                (0.653266, 0.6666666666666666, 0.6666666666666666),
                (0.819095, 0.4588235294117647, 0.4588235294117647),
                (1.0, 0.16470588235294117, 0.16470588235294117)],
        'green': [(0.0, 0.0, 0.0), (0.311558, 1.0, 1.0), (0.5, 1.0, 1.0),
                  (0.653266, 1.0, 1.0),
                  (0.819095, 0.7450980392156863, 0.7450980392156863),
                  (1.0, 0.16470588235294117, 0.16470588235294117)],
        'blue': [(0.0, 0.0, 0.0), (0.311558, 0.0, 0.0), (0.5, 0.0, 0.0),
                 (0.653266, 1.0, 1.0),
                 (0.819095, 0.7019607843137254, 0.7019607843137254),
                 (1.0, 0.49411764705882355, 0.49411764705882355)]
    },
    'DHI': {
        'red': [(0.0, 1.0, 1.0),
                (0.19797, 0.8274509803921568, 0.8274509803921568),
                (0.279188, 0.4, 0.4),
                (0.340102, 0.21568627450980393, 0.21568627450980393),
                (0.548223, 0.0, 0.0),
                (0.71066, 0.6666666666666666, 0.6666666666666666),
                (0.878173, 1.0, 1.0), (1.0, 1.0, 1.0)],
        'green': [(0.0, 1.0, 1.0),
                  (0.19797, 0.8274509803921568, 0.8274509803921568),
                  (0.279188, 0.396078431372549, 0.396078431372549),
                  (0.340102, 0.38823529411764707, 0.38823529411764707),
                  (0.548223, 0.0, 0.0), (0.71066, 0.0, 0.0),
                  (0.878173, 0.9294117647058824, 0.9294117647058824),
                  (1.0, 1.0, 1.0)],
        'blue': [(0.0, 1.0, 1.0),
                 (0.19797, 0.8235294117647058, 0.8235294117647058),
                 (0.279188, 0.3764705882352941, 0.3764705882352941),
                 (0.340102, 0.4980392156862745, 0.4980392156862745),
                 (0.548223, 0.0, 0.0),
                 (0.71066, 0.4980392156862745, 0.4980392156862745),
                 (0.878173, 0.0, 0.0), (1.0, 0.0, 0.0)]
    },
    'Extremes': {
        'red': [(0.0, 1.0, 1.0), (0.05, 1.0, 1.0), (0.4, 1.0, 1.0),
                (0.6, 1.0, 1.0), (0.95, 0.0, 0.0), (1.0, 0.0, 0.0)],
        'green': [(0.0, 1.0, 1.0), (0.05, 0.0, 0.0), (0.4, 1.0, 1.0),
                  (0.6, 1.0, 1.0), (0.95, 0.0, 0.0), (1.0, 1.0, 1.0)],
        'blue': [(0.0, 0.0, 0.0), (0.05, 0.0, 0.0), (0.4, 1.0, 1.0),
                 (0.6, 1.0, 1.0), (0.95, 1.0, 1.0), (1.0, 0.0, 0.0)]
    },
    'Faults': {
        'red': [(0.0, 1.0, 1.0), (0.4, 0.7725490196078432, 0.7725490196078432),
                (0.49, 0.8470588235294118, 0.8470588235294118),
                (0.58, 0.28627450980392155, 0.28627450980392155),
                (0.85, 0.0, 0.0), (1.0, 0.0, 0.0)],
        'green': [(0.0, 1.0, 1.0),
                  (0.4, 0.7058823529411765, 0.7058823529411765),
                  (0.49, 0.6784313725490196, 0.6784313725490196),
                  (0.58, 0.5686274509803921, 0.5686274509803921),
                  (0.85, 0.3333333333333333, 0.3333333333333333),
                  (1.0, 0.43529411764705883, 0.43529411764705883)],
        'blue': [(0.0, 1.0, 1.0), (0.4, 0.6, 0.6),
                 (0.49, 0.00392156862745098, 0.00392156862745098),
                 (0.58, 0.0, 0.0), (0.85, 0.0, 0.0),
                 (1.0, 0.6509803921568628, 0.6509803921568628)]
    },
    'Greyn': {
        'red': [(0.0, 1.0, 1.0), (0.5, 0.4980392156862745, 0.4980392156862745),
                (1.0, 0.0, 0.0)],
        'green': [(0.0, 1.0, 1.0), (0.5, 1.0, 1.0), (1.0, 0.0, 0.0)],
        'blue':
        [(0.0, 1.0, 1.0), (0.5, 0.4980392156862745, 0.4980392156862745),
         (1.0, 0.0, 0.0)]
    },
    'Flames': {
        'red': [(0.0, 1.0, 1.0), (0.2, 1.0, 1.0), (0.4, 0.0, 0.0),
                (0.6, 1.0, 1.0), (0.9, 1.0, 1.0), (1.0, 1.0, 1.0)],
        'green': [(0.0, 1.0, 1.0), (0.2, 1.0, 1.0), (0.4, 0.0, 0.0),
                  (0.6, 0.0, 0.0), (0.9, 1.0, 1.0), (1.0, 1.0, 1.0)],
        'blue': [(0.0, 1.0, 1.0), (0.2, 1.0, 1.0), (0.4, 0.0, 0.0),
                 (0.6, 0.0, 0.0), (0.9, 0.0, 0.0), (1.0, 0.0, 0.0)]
    },
    'Pastel': {
        'red': [(0.0, 0.0, 0.0), (0.25, 0.0, 0.0), (0.5, 1.0, 1.0),
                (0.75, 1.0, 1.0), (1.0, 1.0, 1.0)],
        'green': [(0.0, 0.0, 0.0), (0.25, 1.0, 1.0), (0.5, 1.0, 1.0),
                  (0.75, 1.0, 1.0), (1.0, 0.0, 0.0)],
        'blue': [(0.0, 1.0, 1.0), (0.25, 1.0, 1.0), (0.5, 1.0, 1.0),
                 (0.75, 0.0, 0.0), (1.0, 0.0, 0.0)]
    },
    'Rainbow': {
        'red': [(0.0, 0.0, 0.0), (0.25, 0.0, 0.0), (0.4, 0.0, 0.0),
                (0.55, 1.0, 1.0), (0.8, 1.0, 1.0), (1.0, 1.0, 1.0)],
        'green': [(0.0, 0.0, 0.0), (0.25, 1.0, 1.0), (0.4, 1.0, 1.0),
                  (0.55, 1.0, 1.0), (0.8, 0.0, 0.0), (1.0, 0.0, 0.0)],
        'blue': [(0.0, 1.0, 1.0), (0.25, 1.0, 1.0), (0.4, 0.0, 0.0),
                 (0.55, 0.0, 0.0), (0.8, 0.0, 0.0), (1.0, 1.0, 1.0)]
    },
    'SunRise': {
        'red': [(0.0, 1.0, 1.0), (0.5, 1.0, 1.0), (1.0, 1.0, 1.0)],
        'green': [(0.0, 1.0, 1.0), (0.5, 1.0, 1.0), (1.0, 0.0, 0.0)],
        'blue': [(0.0, 1.0, 1.0), (0.5, 0.0, 0.0), (1.0, 0.0, 0.0)]
    },
    'UVQs-Example': {
        'red': [(0.0, 1.0, 1.0),
                (0.249027, 0.996078431372549, 0.996078431372549),
                (0.505837, 0.023529411764705882, 0.023529411764705882),
                (0.762646, 0.9490196078431372, 0.9490196078431372),
                (1.0, 0.0, 0.0)],
        'green': [(0.0, 1.0, 1.0),
                  (0.249027, 0.6666666666666666, 0.6666666666666666),
                  (0.505837, 0.6666666666666666, 0.6666666666666666),
                  (0.762646, 0.03137254901960784, 0.03137254901960784),
                  (1.0, 0.3333333333333333, 0.3333333333333333)],
        'blue': [(0.0, 0.0, 0.0),
                 (0.249027, 0.00392156862745098, 0.00392156862745098),
                 (0.505837, 0.9764705882352941, 0.9764705882352941),
                 (0.762646, 0.050980392156862744, 0.050980392156862744),
                 (1.0, 0.0, 0.0)]
    },
    'HorizonCube': {
        'red':
        [(0.0, 0.0, 0.0), (0.1, 0.0, 0.0), (0.2, 0.0, 0.0),
         (0.3, 0.6666666666666666, 0.6666666666666666),
         (0.41, 0.37254901960784315, 0.37254901960784315),
         (0.5, 0.13333333333333333, 0.13333333333333333), (0.61, 0.0, 0.0),
         (0.72, 0.4627450980392157, 0.4627450980392157), (0.82, 1.0, 1.0),
         (0.91, 1.0, 1.0), (1.0, 1.0, 1.0)],
        'green': [(0.0, 0.0, 0.0),
                  (0.1, 0.35294117647058826, 0.35294117647058826),
                  (0.2, 1.0, 1.0), (0.3, 1.0, 1.0),
                  (0.41, 0.5607843137254902, 0.5607843137254902),
                  (0.5, 0.20784313725490197, 0.20784313725490197),
                  (0.61, 0.0, 0.0),
                  (0.72, 0.4627450980392157, 0.4627450980392157),
                  (0.82, 1.0, 1.0),
                  (0.91, 0.6666666666666666, 0.6666666666666666),
                  (1.0, 0.0, 0.0)],
        'blue':
        [(0.0, 0.0, 0.0), (0.1, 0.0, 0.0), (0.2, 0.0, 0.0), (0.3, 1.0, 1.0),
         (0.41, 0.9411764705882353, 0.9411764705882353),
         (0.5, 0.6745098039215687, 0.6745098039215687), (0.61, 1.0, 1.0),
         (0.72, 0.0, 0.0), (0.82, 0.0, 0.0), (0.91, 0.0, 0.0), (1.0, 0.0, 0.0)]
    },
    'Red-White-Black': {
        'red': [(0.0, 1.0, 1.0), (0.25, 1.0, 1.0), (0.5, 1.0, 1.0),
                (1.0, 0.0, 0.0)],
        'green': [(0.0, 0.0, 0.0),
                  (0.25, 0.7843137254901961, 0.7843137254901961),
                  (0.5, 1.0, 1.0), (1.0, 0.0, 0.0)],
        'blue': [(0.0, 0.0, 0.0), (0.25, 0.0, 0.0), (0.5, 1.0, 1.0),
                 (1.0, 0.0, 0.0)]
    },
    'Red-White-Blue': {
        'red': [(0.0, 0.6666666666666666, 0.6666666666666666),
                (0.25, 0.5137254901960784, 0.5137254901960784),
                (0.5, 1.0, 1.0),
                (0.781726, 0.23529411764705882, 0.23529411764705882),
                (1.0, 0.0, 0.0)],
        'green': [(0.0, 0.0, 0.0),
                  (0.25, 0.4666666666666667, 0.4666666666666667),
                  (0.5, 1.0, 1.0),
                  (0.781726, 0.23921568627450981, 0.23921568627450981),
                  (1.0, 0.0, 0.0)],
        'blue':
        [(0.0, 0.0, 0.0), (0.25, 0.3058823529411765, 0.3058823529411765),
         (0.5, 1.0, 1.0), (0.781726, 0.34901960784313724, 0.34901960784313724),
         (1.0, 0.4980392156862745, 0.4980392156862745)]
    },
    'Seismics': {
        'red': [(0.0, 0.6666666666666666, 0.6666666666666666),
                (0.070352, 1.0, 1.0), (0.25, 1.0, 1.0),
                (0.5, 0.9529411764705882, 0.9529411764705882),
                (0.883249, 0.2196078431372549, 0.2196078431372549),
                (1.0, 0.0, 0.0)],
        'green': [(0.0, 0.0, 0.0),
                  (0.070352, 0.10980392156862745, 0.10980392156862745),
                  (0.25, 0.7843137254901961, 0.7843137254901961),
                  (0.5, 0.9529411764705882, 0.9529411764705882),
                  (0.883249, 0.27450980392156865, 0.27450980392156865),
                  (1.0, 0.0, 0.0)],
        'blue': [(0.0, 0.0, 0.0), (0.070352, 0.0, 0.0), (0.25, 0.0, 0.0),
                 (0.5, 0.9529411764705882, 0.9529411764705882),
                 (0.883249, 0.4980392156862745, 0.4980392156862745),
                 (1.0, 0.0, 0.0)]
    },
    'Grurple': {
        'red': [(0.0, 1.0, 1.0), (0.5, 1.0, 1.0), (1.0, 0.0, 0.0)],
        'green':
        [(0.0, 1.0, 1.0), (0.5, 0.4980392156862745, 0.4980392156862745),
         (1.0, 0.0, 0.0)],
        'blue': [(0.0, 1.0, 1.0), (0.5, 1.0, 1.0), (1.0, 0.0, 0.0)]
    },
    'CCB': {
        'red': [(0.0, 0.6509803921568628, 0.6509803921568628),
                (0.1225, 0.3333333333333333, 0.3333333333333333),
                (0.1633, 0.7019607843137254, 0.7019607843137254),
                (0.2092, 0.4549019607843137, 0.4549019607843137),
                (0.2704, 0.0, 0.0), (0.3571, 0.0, 0.0), (0.4541, 1.0, 1.0),
                (0.5051, 1.0, 1.0), (0.551, 0.0, 0.0), (0.57143, 1.0, 1.0),
                (0.62245, 1.0, 1.0), (0.66837, 1.0, 1.0), (0.7092, 1.0, 1.0),
                (0.8571, 0.0, 0.0),
                (1.0, 0.3333333333333333, 0.3333333333333333)],
        'green': [(0.0, 0.0, 0.0), (0.1225, 0.0, 0.0),
                  (0.1633, 0.7019607843137254, 0.7019607843137254),
                  (0.2092, 0.4549019607843137, 0.4549019607843137),
                  (0.2704, 0.21568627450980393, 0.21568627450980393),
                  (0.3571, 0.2196078431372549, 0.2196078431372549),
                  (0.4541, 1.0, 1.0), (0.5051, 1.0, 1.0), (0.551, 0.0, 0.0),
                  (0.57143, 0.0, 0.0), (0.62245, 0.0, 0.0),
                  (0.66837, 0.6313725490196078, 0.6313725490196078),
                  (0.7092, 1.0, 1.0),
                  (0.8571, 0.47058823529411764, 0.47058823529411764),
                  (1.0, 0.6666666666666666, 0.6666666666666666)],
        'blue': [(0.0, 1.0, 1.0),
                 (0.1225, 0.4980392156862745, 0.4980392156862745),
                 (0.1633, 0.7019607843137254, 0.7019607843137254),
                 (0.2092, 0.4549019607843137, 0.4549019607843137),
                 (0.2704, 0.6509803921568628, 0.6509803921568628),
                 (0.3571, 0.0, 0.0), (0.4541, 1.0, 1.0), (0.5051, 1.0, 1.0),
                 (0.551, 0.0, 0.0), (0.57143, 0.0, 0.0), (0.62245, 0.0, 0.0),
                 (0.66837, 0.0, 0.0), (0.7092, 0.0, 0.0), (0.8571, 0.0, 0.0),
                 (1.0, 0.0, 0.0)]
    },
    'Experimental': {
        'red':
        [(0.0, 0.3333333333333333, 0.3333333333333333),
         (0.08, 0.15294117647058825, 0.15294117647058825),
         (0.22, 0.3333333333333333, 0.3333333333333333),
         (0.45, 0.6705882352941176, 0.6705882352941176), (0.5, 0.0, 0.0),
         (0.55, 0.6705882352941176, 0.6705882352941176),
         (0.9, 0.9882352941176471, 0.9882352941176471), (1.0, 1.0, 1.0)],
        'green': [(0.0, 0.6666666666666666, 0.6666666666666666),
                  (0.08, 0.4588235294117647, 0.4588235294117647),
                  (0.22, 1.0, 1.0),
                  (0.45, 0.6705882352941176, 0.6705882352941176),
                  (0.5, 0.0, 0.0),
                  (0.55, 0.6705882352941176, 0.6705882352941176),
                  (0.9, 0.5568627450980392, 0.5568627450980392),
                  (1.0, 0.0, 0.0)],
        'blue':
        [(0.0, 1.0, 1.0), (0.08, 0.23137254901960785, 0.23137254901960785),
         (0.22, 0.4980392156862745, 0.4980392156862745),
         (0.45, 0.6705882352941176, 0.6705882352941176), (0.5, 0.0, 0.0),
         (0.55, 0.6705882352941176, 0.6705882352941176),
         (0.9, 0.09411764705882353, 0.09411764705882353), (1.0, 0.0, 0.0)]
    },
    'Magic': {
        'red': [(0.0, 1.0, 1.0), (0.163265, 1.0, 1.0),
                (0.316327, 0.6666666666666666, 0.6666666666666666),
                (0.479592, 0.8784313725490196, 0.8784313725490196),
                (0.493827, 0.8980392156862745, 0.8980392156862745),
                (0.678571, 0.0, 0.0), (0.836735, 0.0, 0.0), (1.0, 0.0, 0.0)],
        'green': [(0.0, 1.0, 1.0), (0.163265, 0.0, 0.0),
                  (0.316327, 0.3333333333333333, 0.3333333333333333),
                  (0.479592, 0.8509803921568627, 0.8509803921568627),
                  (0.493827, 0.8980392156862745, 0.8980392156862745),
                  (0.678571, 0.0, 0.0), (0.836735, 0.0, 0.0), (1.0, 1.0, 1.0)],
        'blue': [(0.0, 0.0, 0.0), (0.163265, 0.0, 0.0), (0.316327, 0.0, 0.0),
                 (0.479592, 0.8274509803921568, 0.8274509803921568),
                 (0.493827, 0.8980392156862745, 0.8980392156862745),
                 (0.678571, 0.0, 0.0), (0.836735, 1.0, 1.0), (1.0, 1.0, 1.0)]
    },
    'Brown-4grades': {
        'red': [(0.0, 1.0, 1.0),
                (0.244898, 0.6666666666666666, 0.6666666666666666),
                (0.484694, 0.9882352941176471, 0.9882352941176471),
                (0.494898, 1.0, 1.0), (0.765306, 0.0, 0.0),
                (1.0, 0.6666666666666666, 0.6666666666666666)],
        'green': [(0.0, 1.0, 1.0), (0.244898, 0.0, 0.0),
                  (0.484694, 0.9607843137254902, 0.9607843137254902),
                  (0.494898, 1.0, 1.0), (0.765306, 0.0, 0.0), (1.0, 1.0, 1.0)],
        'blue': [(0.0, 0.0, 0.0), (0.244898, 0.0, 0.0),
                 (0.484694, 0.9607843137254902, 0.9607843137254902),
                 (0.494898, 1.0, 1.0), (0.765306, 0.0, 0.0), (1.0, 1.0, 1.0)]
    },
    'Curvature': {
        'red': [(0.0, 0.0, 0.0),
                (0.090452, 0.3333333333333333, 0.3333333333333333),
                (0.175879, 0.3333333333333333, 0.3333333333333333),
                (0.25, 0.6666666666666666, 0.6666666666666666),
                (0.291457, 1.0, 1.0), (0.356784, 1.0, 1.0),
                (0.522613, 0.3568627450980392, 0.3568627450980392),
                (0.673367, 0.24313725490196078, 0.24313725490196078),
                (0.78392, 1.0, 1.0), (1.0, 1.0, 1.0)],
        'green': [(0.0, 0.0, 0.0),
                  (0.090452, 0.6666666666666666, 0.6666666666666666),
                  (0.175879, 1.0, 1.0), (0.25, 1.0, 1.0), (0.291457, 1.0, 1.0),
                  (0.356784, 1.0, 1.0),
                  (0.522613, 0.3568627450980392, 0.3568627450980392),
                  (0.673367, 0.24313725490196078, 0.24313725490196078),
                  (0.78392, 0.6666666666666666, 0.6666666666666666),
                  (1.0, 1.0, 1.0)],
        'blue': [(0.0, 1.0, 1.0), (0.090452, 1.0, 1.0), (0.175879, 1.0, 1.0),
                 (0.25, 1.0, 1.0), (0.291457, 1.0, 1.0), (0.356784, 1.0, 1.0),
                 (0.522613, 0.3568627450980392, 0.3568627450980392),
                 (0.673367, 0.24313725490196078, 0.24313725490196078),
                 (0.78392, 0.4980392156862745, 0.4980392156862745),
                 (1.0, 0.0, 0.0)]
    },
    'Contour-Gradients': {
        'red': [(0.0, 0.0, 0.0),
                (0.13878, 0.20392156862745098, 0.20392156862745098),
                (0.13879, 0.0, 0.0),
                (0.266904, 0.1607843137254902, 0.1607843137254902),
                (0.266904, 0.0, 0.0),
                (0.391449, 0.1803921568627451, 0.1803921568627451),
                (0.391459, 0.0, 0.0),
                (0.512446, 0.12156862745098039, 0.12156862745098039),
                (0.512456, 0.23137254901960785, 0.23137254901960785),
                (0.633442, 1.0, 1.0),
                (0.633452, 0.23529411764705882, 0.23529411764705882),
                (0.754438, 1.0, 1.0),
                (0.754448, 0.24705882352941178, 0.24705882352941178),
                (0.878994, 1.0, 1.0),
                (0.879004, 0.3411764705882353, 0.3411764705882353),
                (1.0, 1.0, 1.0)],
        'green': [(0.0, 0.01568627450980392, 0.01568627450980392),
                  (0.13878, 0.23137254901960785, 0.23137254901960785),
                  (0.13879, 0.06274509803921569, 0.06274509803921569),
                  (0.266904, 0.4745098039215686, 0.4745098039215686),
                  (0.266904, 0.23529411764705882, 0.23529411764705882),
                  (0.391449, 0.9450980392156862, 0.9450980392156862),
                  (0.391459, 0.24705882352941178, 0.24705882352941178),
                  (0.512446, 1.0, 1.0),
                  (0.512456, 0.23529411764705882, 0.23529411764705882),
                  (0.633442, 1.0, 1.0),
                  (0.633452, 0.11764705882352941, 0.11764705882352941),
                  (0.754438, 0.0, 0.0),
                  (0.754448, 0.07450980392156863, 0.07450980392156863),
                  (0.878994, 0.5098039215686274, 0.5098039215686274),
                  (0.879004, 0.0, 0.0), (1.0, 0.0, 0.0)],
        'blue': [(0.0, 0.22745098039215686, 0.22745098039215686),
                 (0.13878, 1.0, 1.0),
                 (0.13879, 0.25882352941176473, 0.25882352941176473),
                 (0.266904, 1.0, 1.0),
                 (0.266904, 0.27450980392156865, 0.27450980392156865),
                 (0.391449, 1.0, 1.0),
                 (0.391459, 0.043137254901960784, 0.043137254901960784),
                 (0.512446, 0.0, 0.0), (0.512456, 0.0, 0.0),
                 (0.633442, 0.0, 0.0), (0.633452, 0.0, 0.0),
                 (0.754438, 0.0, 0.0), (0.754448, 0.0, 0.0),
                 (0.878994, 0.20392156862745098, 0.20392156862745098),
                 (0.879004, 0.07450980392156863, 0.07450980392156863),
                 (1.0, 1.0, 1.0)]
    },
    'Rainbow-Plus': {
        'red': [(0.0, 1.0, 1.0), (0.135135, 0.0, 0.0), (0.382239, 0.0, 0.0),
                (0.46332, 0.0, 0.0), (0.53668, 1.0, 1.0), (0.606178, 1.0, 1.0),
                (0.868726, 1.0, 1.0),
                (1.0, 0.36470588235294116, 0.36470588235294116)],
        'green': [(0.0, 0.0, 0.0), (0.135135, 0.0, 0.0), (0.382239, 1.0, 1.0),
                  (0.46332, 1.0, 1.0), (0.53668, 1.0, 1.0),
                  (0.606178, 0.6666666666666666, 0.6666666666666666),
                  (0.868726, 0.0, 0.0), (1.0, 0.0, 0.0)],
        'blue': [(0.0, 1.0, 1.0), (0.135135, 1.0, 1.0), (0.382239, 1.0, 1.0),
                 (0.46332, 0.0, 0.0), (0.53668, 0.0, 0.0),
                 (0.606178, 0.0, 0.0), (0.868726, 0.0, 0.0), (1.0, 0.0, 0.0)]
    },
    'Tasty': {
        'red': [(0.0, 0.0, 0.0), (0.1, 0.1568627450980392, 0.1568627450980392),
                (0.2, 0.3137254901960784, 0.3137254901960784),
                (0.3, 0.6274509803921569, 0.6274509803921569), (0.4, 1.0, 1.0),
                (0.5, 1.0, 1.0), (0.6, 1.0, 1.0), (0.7, 1.0, 1.0),
                (0.8, 1.0, 1.0), (0.9, 0.7843137254901961, 0.7843137254901961),
                (1.0, 0.4980392156862745, 0.4980392156862745)],
        'green':
        [(0.0, 0.4980392156862745, 0.4980392156862745),
         (0.1, 0.7450980392156863, 0.7450980392156863), (0.2, 1.0, 1.0),
         (0.3, 1.0, 1.0), (0.4, 1.0, 1.0),
         (0.5, 0.9019607843137255, 0.9019607843137255),
         (0.6, 0.7843137254901961, 0.7843137254901961),
         (0.7, 0.6666666666666666, 0.6666666666666666),
         (0.8, 0.39215686274509803, 0.39215686274509803),
         (0.9, 0.1568627450980392, 0.1568627450980392), (1.0, 0.0, 0.0)],
        'blue': [(0.0, 0.0, 0.0), (0.1, 0.0, 0.0), (0.2, 0.0, 0.0),
                 (0.3, 0.0, 0.0), (0.4, 0.0, 0.0),
                 (0.5, 0.00392156862745098, 0.00392156862745098),
                 (0.6, 0.0, 0.0), (0.7, 0.0, 0.0), (0.8, 0.0, 0.0),
                 (0.9, 0.0, 0.0), (1.0, 0.0, 0.0)]
    },
    'SandShale': {
        'red': [(0.0, 1.0, 1.0), (0.155116, 1.0, 1.0),
                (0.531353, 0.7372549019607844, 0.7372549019607844),
                (1.0, 0.25882352941176473, 0.25882352941176473)],
        'green': [(0.0, 1.0, 1.0), (0.155116, 1.0, 1.0),
                  (0.531353, 0.4980392156862745, 0.4980392156862745),
                  (1.0, 0.12941176470588237, 0.12941176470588237)],
        'blue': [(0.0, 0.0, 0.0),
                 (0.155116, 0.4980392156862745, 0.4980392156862745),
                 (0.531353, 0.01568627450980392, 0.01568627450980392),
                 (1.0, 0.0, 0.0)]
    },
    'Lithology': {
        'red': [(0.0, 0.0, 0.0), (0.049488, 0.0, 0.0), (0.129693, 0.0, 0.0),
                (0.372014, 0.8392156862745098, 0.8392156862745098),
                (0.593857, 0.6313725490196078, 0.6313725490196078),
                (0.687713, 0.5411764705882353, 0.5411764705882353),
                (0.812287, 0.43529411764705883, 0.43529411764705883),
                (1.0, 1.0, 1.0)],
        'green': [(0.0, 1.0, 1.0), (0.049488, 1.0, 1.0),
                  (0.129693, 0.7450980392156863, 0.7450980392156863),
                  (0.372014, 0.9450980392156862, 0.9450980392156862),
                  (0.593857, 0.41568627450980394, 0.41568627450980394),
                  (0.687713, 0.34509803921568627, 0.34509803921568627),
                  (0.812287, 0.26666666666666666, 0.26666666666666666),
                  (1.0, 0.0, 0.0)],
        'blue': [(0.0, 1.0, 1.0), (0.049488, 1.0, 1.0), (0.129693, 1.0, 1.0),
                 (0.372014, 0.1607843137254902, 0.1607843137254902),
                 (0.593857, 0.01568627450980392, 0.01568627450980392),
                 (0.687713, 0.011764705882352941, 0.011764705882352941),
                 (0.812287, 0.00784313725490196, 0.00784313725490196),
                 (1.0, 0.0, 0.0)]
    },
    'Pressure': {
        'red': [(0.0, 0.3333333333333333, 0.3333333333333333),
                (0.105058, 0.0, 0.0),
                (0.206226, 0.6666666666666666, 0.6666666666666666),
                (0.311284, 0.0, 0.0), (0.583658, 1.0, 1.0), (1.0, 1.0, 1.0)],
        'green': [(0.0, 0.3333333333333333, 0.3333333333333333),
                  (0.105058, 0.0, 0.0),
                  (0.206226, 0.6666666666666666, 0.6666666666666666),
                  (0.311284, 1.0, 1.0),
                  (0.583658, 0.6666666666666666, 0.6666666666666666),
                  (1.0, 0.0, 0.0)],
        'blue': [(0.0, 0.4980392156862745, 0.4980392156862745),
                 (0.105058, 1.0, 1.0), (0.206226, 1.0, 1.0),
                 (0.311284, 1.0, 1.0), (0.583658, 0.0, 0.0), (1.0, 0.0, 0.0)]
    },
    'Interval-Velocity': {
        'red': [(0.0, 0.0, 0.0), (0.190789, 0.0, 0.0), (0.322368, 0.0, 0.0),
                (0.490132, 1.0, 1.0), (0.680921, 1.0, 1.0),
                (0.848684, 0.6666666666666666, 0.6666666666666666),
                (1.0, 0.24705882352941178, 0.24705882352941178)],
        'green': [(0.0, 0.0, 0.0), (0.190789, 1.0, 1.0), (0.322368, 1.0, 1.0),
                  (0.490132, 1.0, 1.0), (0.680921, 0.0, 0.0),
                  (0.848684, 0.3333333333333333, 0.3333333333333333),
                  (1.0, 0.12156862745098039, 0.12156862745098039)],
        'blue': [(0.0, 1.0, 1.0), (0.190789, 1.0, 1.0), (0.322368, 0.0, 0.0),
                 (0.490132, 0.0, 0.0), (0.680921, 0.0, 0.0),
                 (0.848684, 0.0, 0.0), (1.0, 0.0, 0.0)]
    },
    'Porosity': {
        'red': [(0.0, 0.0, 0.0), (0.299611, 0.0, 0.0), (0.389105, 0.0, 0.0),
                (0.587549, 1.0, 1.0), (0.758755, 1.0, 1.0),
                (1.0, 0.25882352941176473, 0.25882352941176473)],
        'green': [(0.0, 0.3333333333333333, 0.3333333333333333),
                  (0.299611, 0.6666666666666666, 0.6666666666666666),
                  (0.389105, 1.0, 1.0), (0.587549, 1.0, 1.0),
                  (0.758755, 0.0, 0.0),
                  (1.0, 0.12941176470588237, 0.12941176470588237)],
        'blue': [(0.0, 1.0, 1.0), (0.299611, 1.0, 1.0), (0.389105, 0.0, 0.0),
                 (0.587549, 0.0, 0.0), (0.758755, 0.0, 0.0), (1.0, 0.0, 0.0)]
    },
    'Dip': {
        'red': [(0.0, 0.0, 0.0), (0.5, 1.0, 1.0), (1.0, 0.0, 0.0)],
        'green': [(0.0, 0.0, 0.0), (0.5, 1.0, 1.0), (1.0, 1.0, 1.0)],
        'blue': [(0.0, 1.0, 1.0), (0.5, 1.0, 1.0), (1.0, 0.0, 0.0)]
    },
    'Energy': {
        'red': [(0.0, 1.0, 1.0), (0.436293, 0.0, 0.0), (1.0, 1.0, 1.0)],
        'green': [(0.0, 1.0, 1.0), (0.436293, 0.0, 0.0), (1.0, 0.0, 0.0)],
        'blue': [(0.0, 1.0, 1.0), (0.436293, 0.0, 0.0), (1.0, 0.0, 0.0)]
    },
    'Depth1': {
        'red': [(0.0, 1.0, 1.0), (0.2, 1.0, 1.0), (0.45, 1.0, 1.0),
                (0.6, 0.0, 0.0), (0.75, 0.0, 0.0), (1.0, 0.0, 0.0)],
        'green': [(0.0, 0.0, 0.0), (0.2, 0.0, 0.0), (0.45, 1.0, 1.0),
                  (0.6, 1.0, 1.0), (0.75, 1.0, 1.0), (1.0, 0.0, 0.0)],
        'blue': [(0.0, 1.0, 1.0), (0.2, 0.0, 0.0), (0.45, 0.0, 0.0),
                 (0.6, 0.0, 0.0), (0.75, 1.0, 1.0), (1.0, 1.0, 1.0)]
    },
    'Similarity': {
        'red': [(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)],
        'green': [(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)],
        'blue': [(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)]
    },
    'Double-Tasty': {
        'red':
        [(0.0, 0.4980392156862745, 0.4980392156862745),
         (0.05, 0.7843137254901961, 0.7843137254901961), (0.1, 1.0, 1.0),
         (0.15, 1.0, 1.0), (0.2, 1.0, 1.0), (0.25, 1.0, 1.0), (0.3, 1.0, 1.0),
         (0.35, 0.6274509803921569, 0.6274509803921569),
         (0.39, 0.3137254901960784, 0.3137254901960784),
         (0.42, 0.1568627450980392, 0.1568627450980392), (0.45, 0.0, 0.0),
         (0.49, 0.0, 0.0), (0.51, 0.0, 0.0), (0.55, 0.0, 0.0),
         (0.58, 0.1568627450980392, 0.1568627450980392),
         (0.61, 0.3137254901960784, 0.3137254901960784),
         (0.65, 0.6274509803921569, 0.6274509803921569), (0.7, 1.0, 1.0),
         (0.75, 1.0, 1.0), (0.8, 1.0, 1.0), (0.85, 1.0, 1.0), (0.9, 1.0, 1.0),
         (0.95, 0.7843137254901961, 0.7843137254901961),
         (1.0, 0.4980392156862745, 0.4980392156862745)],
        'green': [(0.0, 0.0, 0.0),
                  (0.05, 0.1568627450980392, 0.1568627450980392),
                  (0.1, 0.39215686274509803, 0.39215686274509803),
                  (0.15, 0.6666666666666666, 0.6666666666666666),
                  (0.2, 0.7843137254901961, 0.7843137254901961),
                  (0.25, 0.9019607843137255, 0.9019607843137255),
                  (0.3, 1.0, 1.0), (0.35, 1.0, 1.0), (0.39, 1.0, 1.0),
                  (0.42, 0.7450980392156863, 0.7450980392156863),
                  (0.45, 0.4980392156862745, 0.4980392156862745),
                  (0.49, 0.0, 0.0), (0.51, 0.0, 0.0),
                  (0.55, 0.4980392156862745, 0.4980392156862745),
                  (0.58, 0.7450980392156863, 0.7450980392156863),
                  (0.61, 1.0, 1.0), (0.65, 1.0, 1.0), (0.7, 1.0, 1.0),
                  (0.75, 0.9019607843137255, 0.9019607843137255),
                  (0.8, 0.7843137254901961, 0.7843137254901961),
                  (0.85, 0.6666666666666666, 0.6666666666666666),
                  (0.9, 0.39215686274509803, 0.39215686274509803),
                  (0.95, 0.1568627450980392, 0.1568627450980392),
                  (1.0, 0.0, 0.0)],
        'blue': [(0.0, 0.0, 0.0), (0.05, 0.0, 0.0), (0.1, 0.0, 0.0),
                 (0.15, 0.0, 0.0), (0.2, 0.0, 0.0),
                 (0.25, 0.00392156862745098, 0.00392156862745098),
                 (0.3, 0.0, 0.0), (0.35, 0.0, 0.0), (0.39, 0.0, 0.0),
                 (0.42, 0.0, 0.0), (0.45, 0.0, 0.0), (0.49, 0.0, 0.0),
                 (0.51, 0.0, 0.0), (0.55, 0.0, 0.0), (0.58, 0.0, 0.0),
                 (0.61, 0.0, 0.0), (0.65, 0.0, 0.0), (0.7, 0.0, 0.0),
                 (0.75, 0.00392156862745098, 0.00392156862745098),
                 (0.8, 0.0, 0.0), (0.85, 0.0, 0.0), (0.9, 0.0, 0.0),
                 (0.95, 0.0, 0.0), (1.0, 0.0, 0.0)]
    },
    'Double-Rainbow': {
        'red': [(0.0, 1.0, 1.0), (0.1, 1.0, 1.0), (0.23, 1.0, 1.0),
                (0.3, 0.0, 0.0), (0.36, 0.0, 0.0), (0.48, 0.0, 0.0),
                (0.5, 0.0, 0.0), (0.52, 0.0, 0.0), (0.64, 0.0, 0.0),
                (0.7, 0.0, 0.0), (0.77, 1.0, 1.0), (0.9, 1.0, 1.0),
                (1.0, 1.0, 1.0)],
        'green': [(0.0, 0.0, 0.0), (0.1, 0.0, 0.0), (0.23, 1.0, 1.0),
                  (0.3, 1.0, 1.0), (0.36, 1.0, 1.0), (0.48, 0.0, 0.0),
                  (0.5, 0.0, 0.0), (0.52, 0.0, 0.0), (0.64, 1.0, 1.0),
                  (0.7, 1.0, 1.0), (0.77, 1.0, 1.0), (0.9, 0.0, 0.0),
                  (1.0, 0.0, 0.0)],
        'blue': [(0.0, 1.0, 1.0), (0.1, 0.0, 0.0), (0.23, 0.0, 0.0),
                 (0.3, 0.0, 0.0), (0.36, 1.0, 1.0), (0.48, 1.0, 1.0),
                 (0.5, 0.0, 0.0), (0.52, 1.0, 1.0), (0.64, 1.0, 1.0),
                 (0.7, 0.0, 0.0), (0.77, 0.0, 0.0), (0.9, 0.0, 0.0),
                 (1.0, 1.0, 1.0)]
    },
    'Matteo-terrain-cube': {
        'red': [(0.0, 0.5137254901960784, 0.5137254901960784),
                (0.07, 0.5215686274509804, 0.5215686274509804),
                (0.13, 0.49019607843137253, 0.49019607843137253),
                (0.2, 0.43137254901960786, 0.43137254901960786),
                (0.27, 0.38823529411764707, 0.38823529411764707),
                (0.33, 0.3333333333333333, 0.3333333333333333),
                (0.4, 0.2784313725490196, 0.2784313725490196),
                (0.47, 0.2196078431372549, 0.2196078431372549),
                (0.53, 0.24705882352941178, 0.24705882352941178),
                (0.6, 0.2823529411764706, 0.2823529411764706),
                (0.67, 0.3215686274509804, 0.3215686274509804),
                (0.73, 0.3607843137254902, 0.3607843137254902),
                (0.8, 0.4549019607843137, 0.4549019607843137),
                (0.87, 0.6039215686274509, 0.6039215686274509),
                (0.93, 0.7058823529411765, 0.7058823529411765),
                (1.0, 0.8, 0.8)],
        'green': [(0.0, 0.047058823529411764, 0.047058823529411764),
                  (0.07, 0.17647058823529413, 0.17647058823529413),
                  (0.13, 0.2901960784313726, 0.2901960784313726),
                  (0.2, 0.3843137254901961, 0.3843137254901961),
                  (0.27, 0.47843137254901963, 0.47843137254901963),
                  (0.33, 0.5647058823529412, 0.5647058823529412),
                  (0.4, 0.6431372549019608, 0.6431372549019608),
                  (0.47, 0.7137254901960784, 0.7137254901960784),
                  (0.53, 0.7607843137254902, 0.7607843137254902),
                  (0.6, 0.8117647058823529, 0.8117647058823529),
                  (0.67, 0.8509803921568627, 0.8509803921568627),
                  (0.73, 0.8901960784313725, 0.8901960784313725),
                  (0.8, 0.9137254901960784, 0.9137254901960784),
                  (0.87, 0.9254901960784314, 0.9254901960784314),
                  (0.93, 0.9254901960784314, 0.9254901960784314),
                  (1.0, 0.9254901960784314, 0.9254901960784314)],
        'blue': [(0.0, 0.6705882352941176, 0.6705882352941176),
                 (0.07, 0.807843137254902, 0.807843137254902),
                 (0.13, 0.9215686274509803, 0.9215686274509803),
                 (0.2, 0.9921568627450981, 0.9921568627450981),
                 (0.27, 0.9764705882352941, 0.9764705882352941),
                 (0.33, 0.9058823529411765, 0.9058823529411765),
                 (0.4, 0.8274509803921568, 0.8274509803921568),
                 (0.47, 0.7254901960784313, 0.7254901960784313),
                 (0.53, 0.6235294117647059, 0.6235294117647059),
                 (0.6, 0.5058823529411764, 0.5058823529411764),
                 (0.67, 0.396078431372549, 0.396078431372549),
                 (0.73, 0.2901960784313726, 0.2901960784313726),
                 (0.8, 0.30196078431372547, 0.30196078431372547),
                 (0.87, 0.32941176470588235, 0.32941176470588235),
                 (0.93, 0.3411764705882353, 0.3411764705882353),
                 (1.0, 0.35294117647058826, 0.35294117647058826)]
    },
    'Matteo-terrain-cube-red': {
        'red':
        [(0.0, 0.47058823529411764, 0.47058823529411764),
         (0.07, 0.5137254901960784, 0.5137254901960784),
         (0.13, 0.49411764705882355, 0.49411764705882355),
         (0.2, 0.43137254901960786, 0.43137254901960786),
         (0.27, 0.3686274509803922, 0.3686274509803922),
         (0.33, 0.2980392156862745, 0.2980392156862745),
         (0.4, 0.2196078431372549, 0.2196078431372549),
         (0.47, 0.2627450980392157, 0.2627450980392157),
         (0.53, 0.30980392156862746, 0.30980392156862746),
         (0.6, 0.3607843137254902, 0.3607843137254902),
         (0.67, 0.5215686274509804, 0.5215686274509804),
         (0.73, 0.6784313725490196, 0.6784313725490196), (0.8, 0.8, 0.8),
         (0.87, 0.8705882352941177, 0.8705882352941177),
         (0.93, 0.9529411764705882, 0.9529411764705882),
         (1.0, 0.9764705882352941, 0.9764705882352941)],
        'green': [(0.0, 0.0, 0.0),
                  (0.07, 0.050980392156862744, 0.050980392156862744),
                  (0.13, 0.25098039215686274, 0.25098039215686274),
                  (0.2, 0.3843137254901961, 0.3843137254901961),
                  (0.27, 0.5176470588235295, 0.5176470588235295),
                  (0.33, 0.6196078431372549, 0.6196078431372549),
                  (0.4, 0.7137254901960784, 0.7137254901960784),
                  (0.47, 0.7843137254901961, 0.7843137254901961),
                  (0.53, 0.8392156862745098, 0.8392156862745098),
                  (0.6, 0.8901960784313725, 0.8901960784313725),
                  (0.67, 0.9215686274509803, 0.9215686274509803),
                  (0.73, 0.9254901960784314, 0.9254901960784314),
                  (0.8, 0.9254901960784314, 0.9254901960784314),
                  (0.87, 0.8549019607843137, 0.8549019607843137),
                  (0.93, 0.7450980392156863, 0.7450980392156863),
                  (1.0, 0.5882352941176471, 0.5882352941176471)],
        'blue': [(0.0, 0.5215686274509804, 0.5215686274509804),
                 (0.07, 0.7098039215686275, 0.7098039215686275),
                 (0.13, 0.8784313725490196, 0.8784313725490196),
                 (0.2, 0.9921568627450981, 0.9921568627450981),
                 (0.27, 0.9490196078431372, 0.9490196078431372),
                 (0.33, 0.8509803921568627, 0.8509803921568627),
                 (0.4, 0.7215686274509804, 0.7215686274509804),
                 (0.47, 0.5764705882352941, 0.5764705882352941),
                 (0.53, 0.4235294117647059, 0.4235294117647059),
                 (0.6, 0.28627450980392155, 0.28627450980392155),
                 (0.67, 0.3137254901960784, 0.3137254901960784),
                 (0.73, 0.33725490196078434, 0.33725490196078434),
                 (0.8, 0.35294117647058826, 0.35294117647058826),
                 (0.87, 0.3607843137254902, 0.3607843137254902),
                 (0.93, 0.3686274509803922, 0.3686274509803922),
                 (1.0, 0.3568627450980392, 0.3568627450980392)]
    },
    'Matteo-terrain-linear': {
        'red': [(0.0, 0.01568627450980392, 0.01568627450980392),
                (0.07, 0.1411764705882353, 0.1411764705882353),
                (0.13, 0.17647058823529413, 0.17647058823529413),
                (0.2, 0.17254901960784313, 0.17254901960784313),
                (0.27, 0.10588235294117647, 0.10588235294117647),
                (0.33, 0.06666666666666667, 0.06666666666666667),
                (0.4, 0.0, 0.0), (0.47, 0.0, 0.0), (0.53, 0.0, 0.0),
                (0.6, 0.0, 0.0),
                (0.67, 0.21568627450980393, 0.21568627450980393),
                (0.73, 0.38823529411764707, 0.38823529411764707),
                (0.8, 0.6901960784313725, 0.6901960784313725),
                (0.87, 0.8549019607843137, 0.8549019607843137),
                (0.93, 0.9725490196078431, 0.9725490196078431),
                (1.0, 0.9686274509803922, 0.9686274509803922)],
        'green': [(0.0, 0.01568627450980392, 0.01568627450980392),
                  (0.07, 0.054901960784313725, 0.054901960784313725),
                  (0.13, 0.09019607843137255, 0.09019607843137255),
                  (0.2, 0.13333333333333333, 0.13333333333333333),
                  (0.27, 0.2235294117647059, 0.2235294117647059),
                  (0.33, 0.3058823529411765, 0.3058823529411765),
                  (0.4, 0.39215686274509803, 0.39215686274509803),
                  (0.47, 0.4745098039215686, 0.4745098039215686),
                  (0.53, 0.5568627450980392, 0.5568627450980392),
                  (0.6, 0.6392156862745098, 0.6392156862745098),
                  (0.67, 0.6980392156862745, 0.6980392156862745),
                  (0.73, 0.7568627450980392, 0.7568627450980392),
                  (0.8, 0.7803921568627451, 0.7803921568627451),
                  (0.87, 0.803921568627451, 0.803921568627451),
                  (0.93, 0.8431372549019608, 0.8431372549019608),
                  (1.0, 0.9254901960784314, 0.9254901960784314)],
        'blue': [(0.0, 0.01568627450980392, 0.01568627450980392),
                 (0.07, 0.12549019607843137, 0.12549019607843137),
                 (0.13, 0.2784313725490196, 0.2784313725490196),
                 (0.2, 0.4549019607843137, 0.4549019607843137),
                 (0.27, 0.4980392156862745, 0.4980392156862745),
                 (0.33, 0.5058823529411764, 0.5058823529411764),
                 (0.4, 0.42745098039215684, 0.42745098039215684),
                 (0.47, 0.34509803921568627, 0.34509803921568627),
                 (0.53, 0.24705882352941178, 0.24705882352941178),
                 (0.6, 0.16470588235294117, 0.16470588235294117),
                 (0.67, 0.0, 0.0), (0.73, 0.0, 0.0), (0.8, 0.0, 0.0),
                 (0.87, 0.4549019607843137, 0.4549019607843137),
                 (0.93, 0.7294117647058823, 0.7294117647058823),
                 (1.0, 0.8980392156862745, 0.8980392156862745)]
    },
    'Petrel': {
        'red':
        [(0.0, 0.6313725490196078, 0.6313725490196078), (0.33, 0.0, 0.0),
         (0.4, 0.30196078431372547, 0.30196078431372547), (0.5, 0.8, 0.8),
         (0.6, 0.3803921568627451, 0.3803921568627451),
         (0.67, 0.7490196078431373, 0.7490196078431373), (1.0, 1.0, 1.0)],
        'green': [(0.0, 1.0, 1.0), (0.33, 0.0, 0.0),
                  (0.4, 0.30196078431372547, 0.30196078431372547),
                  (0.5, 0.8, 0.8),
                  (0.6, 0.27058823529411763, 0.27058823529411763),
                  (0.67, 0.0, 0.0), (1.0, 1.0, 1.0)],
        'blue': [(0.0, 1.0, 1.0),
                 (0.33, 0.7490196078431373, 0.7490196078431373),
                 (0.4, 0.30196078431372547, 0.30196078431372547),
                 (0.5, 0.8, 0.8), (0.6, 0.0, 0.0), (0.67, 0.0, 0.0),
                 (1.0, 0.0, 0.0)]
    },
    'Depth_AS': {
        'red': [(0.0, 0.6392156862745098, 0.6392156862745098),
                (0.04379562, 0.7372549019607844, 0.7372549019607844),
                (0.33941606, 1.0, 1.0), (0.54014599, 0.0, 0.0),
                (0.729927, 0.0, 0.0), (0.7591241, 0.0, 0.0),
                (0.97445256, 0.0, 0.0), (1.0, 0.0, 0.0)],
        'green': [(0.0, 0.0, 0.0),
                  (0.04379562, 0.12156862745098039, 0.12156862745098039),
                  (0.33941606, 0.6666666666666666, 0.6666666666666666),
                  (0.54014599, 1.0, 1.0),
                  (0.729927, 0.9215686274509803, 0.9215686274509803),
                  (0.7591241, 0.9019607843137255, 0.9019607843137255),
                  (0.97445256, 0.0, 0.0), (1.0, 0.0, 0.0)],
        'blue': [(0.0, 0.0, 0.0), (0.04379562, 0.0, 0.0),
                 (0.33941606, 0.0, 0.0), (0.54014599, 0.0, 0.0),
                 (0.729927, 0.9215686274509803, 0.9215686274509803),
                 (0.7591241, 1.0, 1.0),
                 (0.97445256, 0.7647058823529411, 0.7647058823529411),
                 (1.0, 0.8235294117647058, 0.8235294117647058)]
    },
    'AI_NedMag': {
        'red': [(0.0, 0.9411764705882353, 0.9411764705882353),
                (0.14, 0.4823529411764706, 0.4823529411764706),
                (0.215, 0.20784313725490197, 0.20784313725490197),
                (0.33, 0.16470588235294117, 0.16470588235294117),
                (0.48, 0.3333333333333333, 0.3333333333333333),
                (0.61, 0.2823529411764706, 0.2823529411764706),
                (0.75, 1.0, 1.0), (0.93, 1.0, 1.0), (1.0, 1.0, 1.0)],
        'green': [(0.0, 0.2549019607843137, 0.2549019607843137),
                  (0.14, 0.29411764705882354, 0.29411764705882354),
                  (0.215, 0.20784313725490197, 0.20784313725490197),
                  (0.33, 0.7490196078431373, 0.7490196078431373),
                  (0.48, 1.0, 1.0),
                  (0.61, 0.8509803921568627, 0.8509803921568627),
                  (0.75, 0.9686274509803922, 0.9686274509803922),
                  (0.93, 0.5686274509803921, 0.5686274509803921),
                  (1.0, 0.3333333333333333, 0.3333333333333333)],
        'blue': [(0.0, 1.0, 1.0), (0.14, 1.0, 1.0), (0.215, 1.0, 1.0),
                 (0.33, 1.0, 1.0), (0.48, 1.0, 1.0),
                 (0.61, 0.19607843137254902, 0.19607843137254902),
                 (0.75, 0.3333333333333333, 0.3333333333333333),
                 (0.93, 0.0, 0.0), (1.0, 0.0, 0.0)]
    },
    'Depth_Spectral': {
        'red':
        [(0.0, 0.8431372549019608, 0.8431372549019608),
         (0.25, 0.9764705882352941, 0.9764705882352941), (0.5, 1.0, 1.0),
         (0.75, 0.8274509803921568, 0.8274509803921568),
         (1.0, 0.16862745098039217, 0.16862745098039217)],
        'green': [(0.0, 0.09803921568627451, 0.09803921568627451),
                  (0.25, 0.6196078431372549, 0.6196078431372549),
                  (0.5, 1.0, 1.0),
                  (0.75, 0.6549019607843137, 0.6549019607843137),
                  (1.0, 0.5137254901960784, 0.5137254901960784)],
        'blue':
        [(0.0, 0.10980392156862745, 0.10980392156862745),
         (0.25, 0.34901960784313724, 0.34901960784313724),
         (0.5, 0.7490196078431373, 0.7490196078431373), (0.75, 0.0, 0.0),
         (1.0, 0.7294117647058823, 0.7294117647058823)]
    },
    'od_seismic1': {
        'red':
        [(0.0, 0.3333333333333333, 0.3333333333333333),
         (0.2, 0.03529411764705882, 0.03529411764705882),
         (0.35, 0.34901960784313724, 0.34901960784313724), (0.5, 1.0, 1.0),
         (0.65, 0.9764705882352941, 0.9764705882352941),
         (0.8, 0.5137254901960784, 0.5137254901960784), (1.0, 1.0, 1.0)],
        'green': [(0.0, 1.0, 1.0),
                  (0.2, 0.19215686274509805, 0.19215686274509805),
                  (0.35, 0.4392156862745098, 0.4392156862745098),
                  (0.5, 1.0, 1.0),
                  (0.65, 0.9254901960784314, 0.9254901960784314),
                  (0.8, 0.3843137254901961, 0.3843137254901961),
                  (1.0, 0.0, 0.0)],
        'blue': [(0.0, 1.0, 1.0),
                 (0.2, 0.4392156862745098, 0.4392156862745098),
                 (0.35, 0.28627450980392155, 0.28627450980392155),
                 (0.5, 1.0, 1.0),
                 (0.65, 0.33725490196078434, 0.33725490196078434),
                 (0.8, 0.20392156862745098, 0.20392156862745098),
                 (1.0, 0.0, 0.0)]
    },
    'od_seismic2': {
        'red': [(0.0, 0.3333333333333333, 0.3333333333333333), (0.2, 0.0, 0.0),
                (0.29, 0.047058823529411764, 0.047058823529411764),
                (0.37, 0.47058823529411764, 0.47058823529411764),
                (0.45, 0.7372549019607844, 0.7372549019607844),
                (0.5, 0.9764705882352941, 0.9764705882352941),
                (0.55, 0.8549019607843137, 0.8549019607843137),
                (0.63, 0.6470588235294118, 0.6470588235294118),
                (0.71, 0.5647058823529412, 0.5647058823529412),
                (0.8, 1.0, 1.0), (1.0, 1.0, 1.0)],
        'green': [(0.0, 1.0, 1.0), (0.2, 0.0, 0.0),
                  (0.29, 0.07058823529411765, 0.07058823529411765),
                  (0.37, 0.5490196078431373, 0.5490196078431373),
                  (0.45, 0.7843137254901961, 0.7843137254901961),
                  (0.5, 0.9803921568627451, 0.9803921568627451),
                  (0.55, 0.8549019607843137, 0.8549019607843137),
                  (0.63, 0.5450980392156862, 0.5450980392156862),
                  (0.71, 0.0, 0.0), (0.8, 0.0, 0.0), (1.0, 1.0, 1.0)],
        'blue': [(0.0, 1.0, 1.0), (0.2, 1.0, 1.0),
                 (0.29, 0.5254901960784314, 0.5254901960784314),
                 (0.37, 0.40784313725490196, 0.40784313725490196),
                 (0.45, 0.5803921568627451, 0.5803921568627451),
                 (0.5, 0.9333333333333333, 0.9333333333333333),
                 (0.55, 0.6509803921568628, 0.6509803921568628),
                 (0.63, 0.37254901960784315, 0.37254901960784315),
                 (0.71, 0.0, 0.0), (0.8, 0.0, 0.0), (1.0, 0.0, 0.0)]
    },
    'od_seismic3': {
        'red': [(0.0, 0.3333333333333333, 0.3333333333333333),
                (0.2149837, 0.03529411764705882, 0.03529411764705882),
                (0.38762218, 0.34901960784313724, 0.34901960784313724),
                (0.6, 1.0, 1.0), (0.75, 1.0, 1.0), (1.0, 1.0, 1.0)],
        'green': [(0.0, 1.0, 1.0),
                  (0.2149837, 0.19215686274509805, 0.19215686274509805),
                  (0.38762218, 0.4392156862745098, 0.4392156862745098),
                  (0.6, 0.996078431372549, 0.996078431372549),
                  (0.75, 0.9529411764705882, 0.9529411764705882),
                  (1.0, 0.0, 0.0)],
        'blue': [(0.0, 1.0, 1.0),
                 (0.2149837, 0.4392156862745098, 0.4392156862745098),
                 (0.38762218, 0.28627450980392155, 0.28627450980392155),
                 (0.6, 0.9529411764705882, 0.9529411764705882),
                 (0.75, 0.4196078431372549, 0.4196078431372549),
                 (1.0, 0.0, 0.0)]
    },
    'Viridis': {
        'red': [(0.0, 0.2627450980392157, 0.2627450980392157),
                (0.021164, 0.27450980392156865, 0.27450980392156865),
                (0.040564, 0.2784313725490196, 0.2784313725490196),
                (0.061728, 0.2784313725490196, 0.2784313725490196),
                (0.081129, 0.2823529411764706, 0.2823529411764706),
                (0.100529, 0.2784313725490196, 0.2784313725490196),
                (0.121693, 0.2784313725490196, 0.2784313725490196),
                (0.141093, 0.26666666666666666, 0.26666666666666666),
                (0.160494, 0.2627450980392157, 0.2627450980392157),
                (0.181658, 0.25882352941176473, 0.25882352941176473),
                (0.201058, 0.25098039215686274, 0.25098039215686274),
                (0.220459, 0.23529411764705882, 0.23529411764705882),
                (0.241623, 0.22745098039215686, 0.22745098039215686),
                (0.261023, 0.2235294117647059, 0.2235294117647059),
                (0.280423, 0.21176470588235294, 0.21176470588235294),
                (0.301587, 0.2, 0.2),
                (0.320988, 0.18823529411764706, 0.18823529411764706),
                (0.340388, 0.1803921568627451, 0.1803921568627451),
                (0.361552, 0.17254901960784313, 0.17254901960784313),
                (0.380952, 0.16470588235294117, 0.16470588235294117),
                (0.400353, 0.1568627450980392, 0.1568627450980392),
                (0.421517, 0.14901960784313725, 0.14901960784313725),
                (0.440917, 0.1450980392156863, 0.1450980392156863),
                (0.460317, 0.13725490196078433, 0.13725490196078433),
                (0.481481, 0.13333333333333333, 0.13333333333333333),
                (0.500882, 0.12156862745098039, 0.12156862745098039),
                (0.520282, 0.12156862745098039, 0.12156862745098039),
                (0.541446, 0.11764705882352941, 0.11764705882352941),
                (0.560847, 0.11764705882352941, 0.11764705882352941),
                (0.580247, 0.11764705882352941, 0.11764705882352941),
                (0.601411, 0.12941176470588237, 0.12941176470588237),
                (0.620811, 0.1450980392156863, 0.1450980392156863),
                (0.640212, 0.16470588235294117, 0.16470588235294117),
                (0.661376, 0.19215686274509805, 0.19215686274509805),
                (0.680776, 0.2196078431372549, 0.2196078431372549),
                (0.700176, 0.25098039215686274, 0.25098039215686274),
                (0.72134, 0.29411764705882354, 0.29411764705882354),
                (0.740741, 0.32941176470588235, 0.32941176470588235),
                (0.760141, 0.37254901960784315, 0.37254901960784315),
                (0.781305, 0.41568627450980394, 0.41568627450980394),
                (0.800705, 0.4627450980392157, 0.4627450980392157),
                (0.820106, 0.5098039215686274, 0.5098039215686274),
                (0.84127, 0.5647058823529412, 0.5647058823529412),
                (0.86067, 0.615686274509804, 0.615686274509804),
                (0.880071, 0.6627450980392157, 0.6627450980392157),
                (0.901235, 0.7176470588235294, 0.7176470588235294),
                (0.920635, 0.7725490196078432, 0.7725490196078432),
                (0.940035, 0.8235294117647058, 0.8235294117647058),
                (0.961199, 0.8745098039215686, 0.8745098039215686),
                (0.9806, 0.9254901960784314, 0.9254901960784314),
                (1.0, 0.9725490196078431, 0.9725490196078431)],
        'green': [(0.0, 0.00392156862745098, 0.00392156862745098),
                  (0.021164, 0.03529411764705882, 0.03529411764705882),
                  (0.040564, 0.06666666666666667, 0.06666666666666667),
                  (0.061728, 0.09411764705882353, 0.09411764705882353),
                  (0.081129, 0.11764705882352941, 0.11764705882352941),
                  (0.100529, 0.1450980392156863, 0.1450980392156863),
                  (0.121693, 0.16862745098039217, 0.16862745098039217),
                  (0.141093, 0.19215686274509805, 0.19215686274509805),
                  (0.160494, 0.21568627450980393, 0.21568627450980393),
                  (0.181658, 0.23921568627450981, 0.23921568627450981),
                  (0.201058, 0.2627450980392157, 0.2627450980392157),
                  (0.220459, 0.28627450980392155, 0.28627450980392155),
                  (0.241623, 0.30980392156862746, 0.30980392156862746),
                  (0.261023, 0.32941176470588235, 0.32941176470588235),
                  (0.280423, 0.34901960784313724, 0.34901960784313724),
                  (0.301587, 0.37254901960784315, 0.37254901960784315),
                  (0.320988, 0.3843137254901961, 0.3843137254901961),
                  (0.340388, 0.40784313725490196, 0.40784313725490196),
                  (0.361552, 0.42745098039215684, 0.42745098039215684),
                  (0.380952, 0.45098039215686275, 0.45098039215686275),
                  (0.400353, 0.4666666666666667, 0.4666666666666667),
                  (0.421517, 0.48627450980392156, 0.48627450980392156),
                  (0.440917, 0.5058823529411764, 0.5058823529411764),
                  (0.460317, 0.5254901960784314, 0.5254901960784314),
                  (0.481481, 0.5411764705882353, 0.5411764705882353),
                  (0.500882, 0.5568627450980392, 0.5568627450980392),
                  (0.520282, 0.5803921568627451, 0.5803921568627451),
                  (0.541446, 0.596078431372549, 0.596078431372549),
                  (0.560847, 0.615686274509804, 0.615686274509804),
                  (0.580247, 0.6313725490196078, 0.6313725490196078),
                  (0.601411, 0.6509803921568628, 0.6509803921568628),
                  (0.620811, 0.6705882352941176, 0.6705882352941176),
                  (0.640212, 0.6901960784313725, 0.6901960784313725),
                  (0.661376, 0.7058823529411765, 0.7058823529411765),
                  (0.680776, 0.7215686274509804, 0.7215686274509804),
                  (0.700176, 0.7411764705882353, 0.7411764705882353),
                  (0.72134, 0.7607843137254902, 0.7607843137254902),
                  (0.740741, 0.7725490196078432, 0.7725490196078432),
                  (0.760141, 0.7843137254901961, 0.7843137254901961),
                  (0.781305, 0.803921568627451, 0.803921568627451),
                  (0.800705, 0.8117647058823529, 0.8117647058823529),
                  (0.820106, 0.8274509803921568, 0.8274509803921568),
                  (0.84127, 0.8392156862745098, 0.8392156862745098),
                  (0.86067, 0.8509803921568627, 0.8509803921568627),
                  (0.880071, 0.8549019607843137, 0.8549019607843137),
                  (0.901235, 0.8627450980392157, 0.8627450980392157),
                  (0.920635, 0.8745098039215686, 0.8745098039215686),
                  (0.940035, 0.8823529411764706, 0.8823529411764706),
                  (0.961199, 0.8901960784313725, 0.8901960784313725),
                  (0.9806, 0.8901960784313725, 0.8901960784313725),
                  (1.0, 0.9019607843137255, 0.9019607843137255)],
        'blue': [(0.0, 0.32941176470588235, 0.32941176470588235),
                 (0.021164, 0.3607843137254902, 0.3607843137254902),
                 (0.040564, 0.38823529411764707, 0.38823529411764707),
                 (0.061728, 0.41568627450980394, 0.41568627450980394),
                 (0.081129, 0.4392156862745098, 0.4392156862745098),
                 (0.100529, 0.4588235294117647, 0.4588235294117647),
                 (0.121693, 0.47843137254901963, 0.47843137254901963),
                 (0.141093, 0.49411764705882355, 0.49411764705882355),
                 (0.160494, 0.5019607843137255, 0.5019607843137255),
                 (0.181658, 0.5176470588235295, 0.5176470588235295),
                 (0.201058, 0.5294117647058824, 0.5294117647058824),
                 (0.220459, 0.5372549019607843, 0.5372549019607843),
                 (0.241623, 0.5411764705882353, 0.5411764705882353),
                 (0.261023, 0.5450980392156862, 0.5450980392156862),
                 (0.280423, 0.5490196078431373, 0.5490196078431373),
                 (0.301587, 0.5529411764705883, 0.5529411764705883),
                 (0.320988, 0.5529411764705883, 0.5529411764705883),
                 (0.340388, 0.5529411764705883, 0.5529411764705883),
                 (0.361552, 0.5568627450980392, 0.5568627450980392),
                 (0.380952, 0.5568627450980392, 0.5568627450980392),
                 (0.400353, 0.5568627450980392, 0.5568627450980392),
                 (0.421517, 0.5568627450980392, 0.5568627450980392),
                 (0.440917, 0.5568627450980392, 0.5568627450980392),
                 (0.460317, 0.5529411764705883, 0.5529411764705883),
                 (0.481481, 0.5529411764705883, 0.5529411764705883),
                 (0.500882, 0.5490196078431373, 0.5490196078431373),
                 (0.520282, 0.5450980392156862, 0.5450980392156862),
                 (0.541446, 0.5411764705882353, 0.5411764705882353),
                 (0.560847, 0.5294117647058824, 0.5294117647058824),
                 (0.580247, 0.5215686274509804, 0.5215686274509804),
                 (0.601411, 0.5137254901960784, 0.5137254901960784),
                 (0.620811, 0.5058823529411764, 0.5058823529411764),
                 (0.640212, 0.48627450980392156, 0.48627450980392156),
                 (0.661376, 0.47843137254901963, 0.47843137254901963),
                 (0.680776, 0.4627450980392157, 0.4627450980392157),
                 (0.700176, 0.44313725490196076, 0.44313725490196076),
                 (0.72134, 0.4196078431372549, 0.4196078431372549),
                 (0.740741, 0.4, 0.4),
                 (0.760141, 0.3764705882352941, 0.3764705882352941),
                 (0.781305, 0.34509803921568627, 0.34509803921568627),
                 (0.800705, 0.3176470588235294, 0.3176470588235294),
                 (0.820106, 0.2901960784313726, 0.2901960784313726),
                 (0.84127, 0.2549019607843137, 0.2549019607843137),
                 (0.86067, 0.2196078431372549, 0.2196078431372549),
                 (0.880071, 0.19215686274509805, 0.19215686274509805),
                 (0.901235, 0.1568627450980392, 0.1568627450980392),
                 (0.920635, 0.12156862745098039, 0.12156862745098039),
                 (0.940035, 0.10196078431372549, 0.10196078431372549),
                 (0.961199, 0.09411764705882353, 0.09411764705882353),
                 (0.9806, 0.09803921568627451, 0.09803921568627451),
                 (1.0, 0.12549019607843137, 0.12549019607843137)]
    },
    'Plasma': {
        'red': [(0.0, 0.058823529411764705, 0.058823529411764705),
                (0.021164, 0.11372549019607843, 0.11372549019607843),
                (0.040564, 0.15294117647058825, 0.15294117647058825),
                (0.061728, 0.19607843137254902, 0.19607843137254902),
                (0.081129, 0.22745098039215686, 0.22745098039215686),
                (0.100529, 0.25882352941176473, 0.25882352941176473),
                (0.121693, 0.29411764705882354, 0.29411764705882354),
                (0.141093, 0.3254901960784314, 0.3254901960784314),
                (0.160494, 0.3568627450980392, 0.3568627450980392),
                (0.181658, 0.38823529411764707, 0.38823529411764707),
                (0.201058, 0.4196078431372549, 0.4196078431372549),
                (0.220459, 0.4470588235294118, 0.4470588235294118),
                (0.241623, 0.47843137254901963, 0.47843137254901963),
                (0.261023, 0.5058823529411764, 0.5058823529411764),
                (0.280423, 0.5333333333333333, 0.5333333333333333),
                (0.301587, 0.5647058823529412, 0.5647058823529412),
                (0.320988, 0.592156862745098, 0.592156862745098),
                (0.340388, 0.6196078431372549, 0.6196078431372549),
                (0.361552, 0.6431372549019608, 0.6431372549019608),
                (0.380952, 0.6666666666666666, 0.6666666666666666),
                (0.400353, 0.6901960784313725, 0.6901960784313725),
                (0.421517, 0.7137254901960784, 0.7137254901960784),
                (0.440917, 0.7333333333333333, 0.7333333333333333),
                (0.460317, 0.7490196078431373, 0.7490196078431373),
                (0.481481, 0.7725490196078432, 0.7725490196078432),
                (0.500882, 0.792156862745098, 0.792156862745098),
                (0.520282, 0.8117647058823529, 0.8117647058823529),
                (0.541446, 0.8313725490196079, 0.8313725490196079),
                (0.560847, 0.8470588235294118, 0.8470588235294118),
                (0.580247, 0.8588235294117647, 0.8588235294117647),
                (0.601411, 0.8745098039215686, 0.8745098039215686),
                (0.620811, 0.8901960784313725, 0.8901960784313725),
                (0.640212, 0.9058823529411765, 0.9058823529411765),
                (0.661376, 0.9176470588235294, 0.9176470588235294),
                (0.680776, 0.9294117647058824, 0.9294117647058824),
                (0.700176, 0.9411764705882353, 0.9411764705882353),
                (0.72134, 0.9568627450980393, 0.9568627450980393),
                (0.740741, 0.9647058823529412, 0.9647058823529412),
                (0.760141, 0.9725490196078431, 0.9725490196078431),
                (0.781305, 0.9803921568627451, 0.9803921568627451),
                (0.800705, 0.984313725490196, 0.984313725490196),
                (0.820106, 0.9882352941176471, 0.9882352941176471),
                (0.84127, 0.9921568627450981, 0.9921568627450981),
                (0.86067, 0.9921568627450981, 0.9921568627450981),
                (0.880071, 0.9921568627450981, 0.9921568627450981),
                (0.901235, 0.9882352941176471, 0.9882352941176471),
                (0.920635, 0.9803921568627451, 0.9803921568627451),
                (0.940035, 0.9725490196078431, 0.9725490196078431),
                (0.961199, 0.9647058823529412, 0.9647058823529412),
                (0.9806, 0.9529411764705882, 0.9529411764705882),
                (1.0, 0.9411764705882353, 0.9411764705882353)],
        'green': [(0.0, 0.023529411764705882, 0.023529411764705882),
                  (0.021164, 0.0196078431372549, 0.0196078431372549),
                  (0.040564, 0.0196078431372549, 0.0196078431372549),
                  (0.061728, 0.01568627450980392, 0.01568627450980392),
                  (0.081129, 0.011764705882352941, 0.011764705882352941),
                  (0.100529, 0.011764705882352941, 0.011764705882352941),
                  (0.121693, 0.00784313725490196, 0.00784313725490196),
                  (0.141093, 0.00392156862745098, 0.00392156862745098),
                  (0.160494, 0.0, 0.0), (0.181658, 0.0, 0.0),
                  (0.201058, 0.0, 0.0), (0.220459, 0.0, 0.0),
                  (0.241623, 0.00784313725490196, 0.00784313725490196),
                  (0.261023, 0.011764705882352941, 0.011764705882352941),
                  (0.280423, 0.027450980392156862, 0.027450980392156862),
                  (0.301587, 0.054901960784313725, 0.054901960784313725),
                  (0.320988, 0.07450980392156863, 0.07450980392156863),
                  (0.340388, 0.09803921568627451, 0.09803921568627451),
                  (0.361552, 0.11764705882352941, 0.11764705882352941),
                  (0.380952, 0.1411764705882353, 0.1411764705882353),
                  (0.400353, 0.16470588235294117, 0.16470588235294117),
                  (0.421517, 0.1843137254901961, 0.1843137254901961),
                  (0.440917, 0.20784313725490197, 0.20784313725490197),
                  (0.460317, 0.23137254901960785, 0.23137254901960785),
                  (0.481481, 0.25098039215686274, 0.25098039215686274),
                  (0.500882, 0.27450980392156865, 0.27450980392156865),
                  (0.520282, 0.29411764705882354, 0.29411764705882354),
                  (0.541446, 0.3215686274509804, 0.3215686274509804),
                  (0.560847, 0.34509803921568627, 0.34509803921568627),
                  (0.580247, 0.36470588235294116, 0.36470588235294116),
                  (0.601411, 0.38823529411764707, 0.38823529411764707),
                  (0.620811, 0.4117647058823529, 0.4117647058823529),
                  (0.640212, 0.43137254901960786, 0.43137254901960786),
                  (0.661376, 0.4588235294117647, 0.4588235294117647),
                  (0.680776, 0.4823529411764706, 0.4823529411764706),
                  (0.700176, 0.5058823529411764, 0.5058823529411764),
                  (0.72134, 0.5372549019607843, 0.5372549019607843),
                  (0.740741, 0.5607843137254902, 0.5607843137254902),
                  (0.760141, 0.5882352941176471, 0.5882352941176471),
                  (0.781305, 0.615686274509804, 0.615686274509804),
                  (0.800705, 0.6431372549019608, 0.6431372549019608),
                  (0.820106, 0.6745098039215687, 0.6745098039215687),
                  (0.84127, 0.7019607843137254, 0.7019607843137254),
                  (0.86067, 0.7333333333333333, 0.7333333333333333),
                  (0.880071, 0.7568627450980392, 0.7568627450980392),
                  (0.901235, 0.796078431372549, 0.796078431372549),
                  (0.920635, 0.8313725490196079, 0.8313725490196079),
                  (0.940035, 0.8588235294117647, 0.8588235294117647),
                  (0.961199, 0.8941176470588236, 0.8941176470588236),
                  (0.9806, 0.9294117647058824, 0.9294117647058824),
                  (1.0, 0.9607843137254902, 0.9607843137254902)],
        'blue': [(0.0, 0.5215686274509804, 0.5215686274509804),
                 (0.021164, 0.5529411764705883, 0.5529411764705883),
                 (0.040564, 0.5686274509803921, 0.5686274509803921),
                 (0.061728, 0.5882352941176471, 0.5882352941176471),
                 (0.081129, 0.6039215686274509, 0.6039215686274509),
                 (0.100529, 0.615686274509804, 0.615686274509804),
                 (0.121693, 0.6313725490196078, 0.6313725490196078),
                 (0.141093, 0.6392156862745098, 0.6392156862745098),
                 (0.160494, 0.6470588235294118, 0.6470588235294118),
                 (0.181658, 0.6509803921568628, 0.6509803921568628),
                 (0.201058, 0.6549019607843137, 0.6549019607843137),
                 (0.220459, 0.6588235294117647, 0.6588235294117647),
                 (0.241623, 0.6588235294117647, 0.6588235294117647),
                 (0.261023, 0.6549019607843137, 0.6549019607843137),
                 (0.280423, 0.6470588235294118, 0.6470588235294118),
                 (0.301587, 0.6352941176470588, 0.6352941176470588),
                 (0.320988, 0.6274509803921569, 0.6274509803921569),
                 (0.340388, 0.6078431372549019, 0.6078431372549019),
                 (0.361552, 0.592156862745098, 0.592156862745098),
                 (0.380952, 0.5764705882352941, 0.5764705882352941),
                 (0.400353, 0.5607843137254902, 0.5607843137254902),
                 (0.421517, 0.5411764705882353, 0.5411764705882353),
                 (0.440917, 0.5254901960784314, 0.5254901960784314),
                 (0.460317, 0.5058823529411764, 0.5058823529411764),
                 (0.481481, 0.48627450980392156, 0.48627450980392156),
                 (0.500882, 0.4666666666666667, 0.4666666666666667),
                 (0.520282, 0.45098039215686275, 0.45098039215686275),
                 (0.541446, 0.42745098039215684, 0.42745098039215684),
                 (0.560847, 0.4117647058823529, 0.4117647058823529),
                 (0.580247, 0.4, 0.4),
                 (0.601411, 0.3803921568627451, 0.3803921568627451),
                 (0.620811, 0.36470588235294116, 0.36470588235294116),
                 (0.640212, 0.34901960784313724, 0.34901960784313724),
                 (0.661376, 0.3254901960784314, 0.3254901960784314),
                 (0.680776, 0.3137254901960784, 0.3137254901960784),
                 (0.700176, 0.2980392156862745, 0.2980392156862745),
                 (0.72134, 0.27450980392156865, 0.27450980392156865),
                 (0.740741, 0.25882352941176473, 0.25882352941176473),
                 (0.760141, 0.24313725490196078, 0.24313725490196078),
                 (0.781305, 0.22745098039215686, 0.22745098039215686),
                 (0.800705, 0.20784313725490197, 0.20784313725490197),
                 (0.820106, 0.19215686274509805, 0.19215686274509805),
                 (0.84127, 0.17647058823529413, 0.17647058823529413),
                 (0.86067, 0.16862745098039217, 0.16862745098039217),
                 (0.880071, 0.1568627450980392, 0.1568627450980392),
                 (0.901235, 0.1450980392156863, 0.1450980392156863),
                 (0.920635, 0.1411764705882353, 0.1411764705882353),
                 (0.940035, 0.1411764705882353, 0.1411764705882353),
                 (0.961199, 0.1411764705882353, 0.1411764705882353),
                 (0.9806, 0.14901960784313725, 0.14901960784313725),
                 (1.0, 0.13725490196078433, 0.13725490196078433)]
    }
}

cigvis/customcmap.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
geophysics data io, 

Including:
    - well logs (.las format)
    - vds file (need install openvds)
    - fault skin
    
"""

from .las import load_las
from .vds import VDSReader, create_vds_from_array
from .fault_skin import load_skins, load_one_skin
cigvis/io/__init__.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
Load three types of fault skin. One fault skin file 
contains three parts: header, mid, tail

- classic fault skin: 
    header: 1 int32 number for ncells, 
    mid: 9*ncells float32 numbers, [z, y, x, likelihood, strike, dip, slip[3]]
    tail: 4*ncells int32 numbers, [above, below, left, right] indices

- 12 numbers tail fault skin:
    header: 4 int32 numbers, [ncells, i1seed, 12seed, i3seed]
    mid: 12*ncells float32 numbers, [z, y, x, likelihood, strike, dip, ...]
    tail: 12*ncells int32 numbers, [i1a, i2a, i3a, i1b, i2b, i3b, i1l, i2l, i3l, i1r, i2r, i3r]

- .cig format (fault skin and control points)
    header: 2 int32 numbers, [nctrl, ncells]
    mid1: 6 * nctrl float32 numbers, [cpsz, cpsy, cpsx, cusz, cusy, cusx]
    mid2: 6 * ncells float32 numbers, [z, y, x, likelihood, strike, dip]
    tail: 4 * ncells int32 numbers, [above, below, left, right] indices


see: https://github.com/xinwucwp/osv/blob/f4e2564fc27b9539edc4caff0944b1ddb94997b8/src/osv/FaultSkin.java#L569
for classic fault skin
"""

import numpy as np
import glob
from cigvis.meshs import merge_meshs


def load_skins(filedir: str,
               suffix='*',
               endian: str = '>',
               values_type: str = None):
    """
    load muiltiple skin file and merge all vertices and faces into one

    Parameters
    -----------
    filedir : str
        skin files dir
    endian : str
        endianess, big is '>', little is '<'
    values_type : str
        can be 'likelihood', 'strike', 'dip', ...

    Returns
    --------
    vertices : array-like 
        shape is [N, 3]
    faces: array-like 
        shape is [N2, 3]
    vertex_values : array-like
        shape is [N]
    """
    if filedir[-1] != '/':
        filedir += '/'
    flist = glob.glob(filedir + suffix)
    vertices = []
    faces = []
    values = []
    for f in flist:
        vert, face, v = load_one_skin(f, endian, values_type)
        vertices.append(vert)
        faces.append(face)
        values.append(v)

    vertices, faces = merge_meshs(vertices, faces)
    if values_type is not None:
        values = np.concatenate(values)
    else:
        values = None

    return vertices, faces, values


def load_one_skin(filename: str, endian: str = '>', values_type: str = None):
    """
    load one skin file

    Parameters
    ----------
    filename : str
        skin file name
    endian : str
        endianess, big is '>', little is '<'
    values_type : str
        can be 'likelihood', 'strike', 'dip', ...

    Returns
    --------
    vertices : array-like 
        shape is [N, 3]
    faces: array-like 
        shape is [N2, 3]
    vertex_values : array-like
        shape is [N]
    """
    # load file
    mid, tail = _load_skin(filename, endian)

    # vertices
    vertices = mid[:, [2, 1, 0]]

    # vertex_colors
    vertex_values = None
    if values_type is not None:
        vertex_values = mid[:, idx(values_type)]

    # faces
    if tail.shape[1] == 4:
        cells = _cells4_to_faces(tail)
    else:
        cells = _cells12_to_faces(mid, tail)

    faces = []

    for i in range(len(cells)):
        R = cells[i, 2]
        if R is not None:
            RB = cells[R, 1]
        else:
            RB = None
        B = cells[i, 1]
        if B is not None:
            BR = cells[B, 2]
        else:
            BR = None

        if R is not None and RB is not None:
            faces.append([i, cells[RB, 0], cells[R, 0]])
        elif R is not None and BR is not None:
            faces.append([i, cells[BR, 0], cells[R, 0]])
        if B is not None and RB is not None:
            faces.append([i, cells[B, 0], cells[RB, 0]])
        elif B is not None and BR is not None:
            faces.append([i, cells[B, 0], cells[BR, 0]])

    faces = np.array(faces).astype(int)

    return vertices, faces, vertex_values


def _cells12_to_faces(mid: np.ndarray, tail: np.ndarray):
    """
    new verison cells

    Parameters
    -----------
    mid : array-like
        shape is [ncells, 12], each row is like [z, y, x, ...]
    tail : array-like
        shape is [ncells, 12], each row is like
        [i1a, i2a, i3a, i1b, i2b, i3b, i1l, i2l, i3l, i1r, i2r, i3r],
        i1 means z, i2 means y, i3 means x

    Returns
    --------
    cells : array-like
        shape is [ncells, 3], each row is like 
        [idx, below_cell_index, right_cell_index]
    """
    pos = mid[:, [2, 1, 0]]
    intpos = np.round(pos).astype(np.int32)
    xmin = intpos[:, 0].min()
    ymin = intpos[:, 1].min()
    zmin = intpos[:, 2].min()
    nx = 1 + intpos[:, 0].max() - xmin
    ny = 1 + intpos[:, 1].max() - ymin
    nz = 1 + intpos[:, 2].max() - zmin
    tz = tail[:, ::3] - zmin
    ty = tail[:, 1::3] - ymin
    tx = tail[:, 2::3] - xmin
    tz[np.logical_or(tz < 0, tz > nz)] = nz
    ty[np.logical_or(ty < 0, ty > ny)] = ny
    tx[np.logical_or(tx < 0, tx > nx)] = nx

    ix = intpos[:, 0] - xmin
    iy = intpos[:, 1] - ymin
    iz = intpos[:, 2] - zmin
    cell_idx = np.full((nx + 1, ny + 1, nz + 1), None)
    cell_idx[ix, iy, iz] = np.arange(len(mid))

    # cells = np.full((len(mid), 3), None)
    # for i in range(len(mid)):
    #     b = cell_idx[tx[i, 1], ty[i, 1], tz[i, 1]]
    #     r = cell_idx[tx[i, 3], ty[i, 3], tz[i, 3]]
    #     cells[i] = [i, b, r]

    b_indices = cell_idx[tx[:, 1], ty[:, 1], tz[:, 1]]
    r_indices = cell_idx[tx[:, 3], ty[:, 3], tz[:, 3]]
    cells = np.column_stack((np.arange(len(mid)), b_indices, r_indices))

    return cells


def _cells4_to_faces(tail: np.ndarray):
    """
    old verison cells

    Parameters
    -----------
    tail : array-like
        shape is [ncells, 4], each row is like [above, below, left, right] 
        indices

    Returns
    --------
    cells : array-like
        shape is [ncells, 3], each row is like 
        [idx, below_cell_index, right_cell_index]
    """
    ncell = len(tail)
    tail = tail.astype(object)
    tail[tail < 0] = None
    b_indices = tail[:, 1]
    r_indices = tail[:, 3]
    cells = np.column_stack((np.arange(ncell), b_indices, r_indices))

    return cells


def idx(name: str):
    if name == 'x' or name == 'iline':
        ix = 2
    elif name == 'y' or name == 'xline':
        ix = 1
    elif name == 'z' or name == 'time':
        ix = 0
    elif name == 'likelihood':
        ix = 3
    elif name == 'strike' or name == 'phi':
        ix = 4
    elif name == 'dip' or name == 'theta':
        ix = 5
    elif name == 'vector':
        ix = slice(6, 9)
    else:
        raise ValueError("Unknow key name")

    return ix


def _load_skin(filename: str, endian: str = '>'):
    """
    The structure of skin file: header, mid, tail
    header: ncells, (i1seed, i2seed, i3seed)(new version), int32
    mid: [z, y, x, likelihood, strike, dip, ...] * ncells, float32
    tail: [above, below, left, right] * ncells, int32

    load a skin file into two parts: mid and tail

    Parameters
    -----------
    filename : str
        skin file name
    endian : str
        little endian is '<', big endian is '>'

    Returns
    --------
    mid : array-like
        shape is [ncells, 12] or [ncells, 9] (old version)
    tail : array-like
        shape is [ncells, 12] or [ncells, 4] (old version)
    """
    d = np.fromfile(filename, dtype=np.int32)
    if endian == '>':
        d = d.byteswap()
    if (d[0] * 24 + 4) == len(d) or (d[0] * 13 + 4) == len(d):
        ncell = d[0]

        nele = (len(d) - 4) // ncell
        nattr = 12 if nele == 24 else 9
        nnigb = 12 if nele == 24 else 4

        mid = d[4:4 + ncell * nattr].view(np.float32).reshape(ncell, nattr)
        tail = d[4 + ncell * nattr:].reshape(ncell, nnigb)
    elif (2 + d[0] * 6 + d[1] * 10) == len(d):
        """.cig format"""
        nctrl = d[0]
        ncell = d[1]
        start = 2 + nctrl * 6
        mid = d[start:start + ncell * 6].view(np.float32).reshape(ncell, 6)
        tail = d[start + ncell * 6:].reshape(ncell, 4)
    else:
        raise RuntimeError("Unknow file type")

    return mid, tail

cigvis/io/fault_skin.py
JintaoLee-Roger/cigvis

# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.

from typing import Dict, List, Tuple
import numpy as np

def load_las(fpath: str) -> Dict:
    """
    Load las format file (Well Logs).
    Return a dict contains all informations of the las file.

    Each section is a dict, which contains `name`, 'unit', 'value' 
    and 'descreption'
    
    Use `data = las['data']` to obtain data, `las['CURVE']['name']`
    to obtain data name of each column
    
    Parameters
    ----------
    fpath : str
        las file path
    
    Returns
    -------
    las : Dict
        las data and its information

    Examples
    ----------
    >>> las = load_las('log01.las')
    >>> las.keys() # sections
    dict_keys(['VERSION', 'WELL', 'CURVE', 'PARAMETER', 'data'])
    >>> data = las['data']
    >>> data.shape
    (921, 5)
    >>> curves = las['CURVE'] # curves
    >>> curves['name']
    ['DEPT', 'ILD', 'DPHI', 'NPHI', 'GR']
    >>> curves['unit']
    ['M', 'OHMM', 'V/V', 'V/V', 'API']
    >>> las['WELL']['name'] # meta information
    ['WELL', 'LOC', 'UWI', 'ENTR', 'SRVC', 'DATE', 'STRT', 'STOP', 'STEP', 'NULL']
    >>> las['WELL]['value][1] # location
    '00/01-12-079-14W4/0'
    """

    with open(fpath, 'r') as f:
        text = f.read()

    sections = [s.strip() for s in text.split('~') if s.strip() != '']
    las = {}
    for section in sections:
        # data
        if section[:2] == 'A ' or section[:5] == 'Ascii':
            section = [
                l.strip() for l in section.split('\n')
                if l.strip() != '' and l.strip()[0] != '#'
            ]
            data = np.loadtxt(section[1:], np.float32)
            las['data'] = data
        else:  # sections
            name, cdict = _process_las_section(section)
            las[name] = cdict

    return las




##### private functions, don't call them ##############


def _process_las_line(line: str) -> Tuple:
    parts = [l.strip() for l in line.strip().split(':')]
    descreption = parts[-1]
    line = ':'.join(parts[:-1]).strip()
    parts = [l for l in line.split('.')]
    name = parts[0].strip()
    line = '.'.join(parts[1:])

    if len(line) > 0 and line[0] != ' ':
        parts = line.split(' ')
        unit = parts[0].strip()
        value = ' '.join(parts[1:]).strip()
    else:
        unit = None
        value = line.strip()

    return name, unit, value, descreption


def _process_las_section(text: str) -> Tuple:
    text = [
        l.strip() for l in text.split('\n')
        if l.strip() != '' and l.strip()[0] != '#'
    ]
    name = text[0].split(' ')[0]
    cdict = {'name': [], 'unit': [], 'value': [], 'description': []}
    for t in text[1:]:
        out = _process_las_line(t)
        cdict['name'].append(out[0])
        cdict['unit'].append(out[1])
        cdict['value'].append(out[2])
        cdict['description'].append(out[3])
    return name, cdict
cigvis/io/las.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.

from typing import Dict, List, Tuple
import numpy as np
import cigvis


class VDSReader:
    """
    A vds file reader that mimics the numpy array style.

    Examples
    ---------
    >>> d = VDSReader('test.vds')
    >>> d.shape
    # (601, 203, 400) # (n-inline, n-xline, n-time)
    >>> k = d[20:100, 100:200, 100:300] # k is np.array
    >>> k = d[20, :, :] # shape is (203, 400)
    >>> k = d[20, ...] # same as k = d[20, :, :]
    >>> k = d[20] # same as k = d[20, :, :]
    >>> k = d[:, 20, :]
    >>> print(d.min(), d.max())
    # will call `Layout.getChannelDescriptor(0).getValueRangeMin()`
    # and `Layout.getChannelDescriptor(0).getValueRangeMax()`
    """

    def __init__(self, filename) -> None:
        import openvds

        self.vds = openvds.open(filename)
        self.access_manager = openvds.getAccessManager(self.vds)
        self.layout = openvds.getLayout(self.vds)
        self.ch_des = self.layout.getChannelDescriptor(0)
        # (ni, nx, nt)
        self._shape = (self.layout.getDimensionNumSamples(2),
                       self.layout.getDimensionNumSamples(1),
                       self.layout.getDimensionNumSamples(0))

    def read(self, ib, ie, xb, xe, tb, te) -> np.ndarray:
        """
        read sub volume
        """
        req = self.access_manager.requestVolumeSubset(min=(tb, xb, ib),
                                                      max=(te, xe, ie))
        shape = [ie - ib, xe - xb, te - tb]
        while shape.count(1) > 0:
            shape.remove(1)
        shape = tuple(shape)
        if len(shape) == 0:
            return req.data[0]
        return req.data.reshape(shape)

    @property
    def shape(self) -> Tuple:
        if not cigvis.is_line_first():
            return self._shape[::-1]
        return self._shape

    def __getitem__(self, slices) -> np.ndarray:
        idx = self._process_keys(slices)
        # to seismic index
        if not cigvis.is_line_first():
            idx = [idx[4], idx[5], idx[2], idx[3], idx[0], idx[1]]

        self._check_bound(*idx)
        data = self.read(*idx)

        if cigvis.is_line_first():
            return data  # seismic index
        else:  # seismic transpose index
            return data.transpose()

    def _process_keys(self, key) -> List:
        if isinstance(key, int):
            if key < 0:
                key += self.shape[0]
            if key < 0 or key >= self.shape[0]:
                raise IndexError("Index out of range")
            return key, key + 1, 0, self.shape[1], 0, self.shape[2]
        elif key is Ellipsis:
            return 0, self.shape[0], 0, self.shape[1], 0, self.shape[2]
        elif isinstance(key, Tuple):
            num_ellipsis = key.count(Ellipsis)
            if num_ellipsis > 1:
                raise ValueError("Only one ellipsis (...) allowed")
            elif num_ellipsis == 1:
                key = (key[0], slice(None, None,
                                     None), slice(None, None, None))

            start_idx = [None] * 3
            end_idx = [None] * 3
            for i, k in enumerate(key):
                if k is None:
                    continue
                if isinstance(k, int):
                    if k < 0:
                        k += self.shape[i]
                    start_idx[i] = k
                    end_idx[i] = k + 1
                elif isinstance(k, slice):
                    start_idx[i] = k.start or 0
                    end_idx[i] = k.stop or self.shape[i]

            ib, xb, tb = start_idx
            ie, xe, te = end_idx

            return ib, ie, xb, xe, tb, te
        else:
            raise IndexError("Invalid index slices")

    def _check_bound(self, ib, ie, xb, xe, tb, te) -> None:
        assert ib < ie and ie <= self._shape[0]
        assert xb < xe and xe <= self._shape[1]
        assert tb < te and te <= self._shape[2]

    def max(self) -> float:
        return self.ch_des.getValueRangeMax()

    def min(self) -> float:
        return self.ch_des.getValueRangeMin()


def create_vds_from_array(d: np.ndarray,
                          vdsf: str,
                          clim: List = None,
                          brick_size: int = 64):
    """
    create a vds file from a np.ndarray,
    check the official repository to view the original examples,
    path: `open-vds/examples/NpzToVds/npz_to_vds.py`,
    repository: 
    https://community.opengroup.org/osdu/platform/domain-data-mgmt-services/seismic/open-vds

    Parameters
    -----------
    d : array-like
        input data
    vdsf : str
        output vdsfile
    clim : List
        [vmin, vmax], if None, will use [d.min(), d.max()]
    brick_size : int
        brick size, can be one of {32, 64, 128, 256, 512, 1024, 2048}
    """
    import openvds
    if cigvis.is_line_first():
        ni, nx, nt = d.shape
    else:
        nt, nx, ni = d.shape

    brick_size_swith = {
        32: openvds.VolumeDataLayoutDescriptor.BrickSize.BrickSize_32,
        64: openvds.VolumeDataLayoutDescriptor.BrickSize.BrickSize_64,
        128: openvds.VolumeDataLayoutDescriptor.BrickSize.BrickSize_128,
        256: openvds.VolumeDataLayoutDescriptor.BrickSize.BrickSize_256,
        512: openvds.VolumeDataLayoutDescriptor.BrickSize.BrickSize_512,
        1024: openvds.VolumeDataLayoutDescriptor.BrickSize.BrickSize_1024,
        2048: openvds.VolumeDataLayoutDescriptor.BrickSize.BrickSize_2048
    }
    brick_size = brick_size_swith[brick_size]

    if clim is None:
        clim = [d.min(), d.max()]

    layout_des = openvds.VolumeDataLayoutDescriptor(
        brick_size,
        0,
        0,
        4,
        openvds.VolumeDataLayoutDescriptor.LODLevels.LODLevels_None,
        openvds.VolumeDataLayoutDescriptor.Options.Options_None,
    )

    axis_des = [
        openvds.VolumeDataAxisDescriptor(nt, 'Sample', 'ms', 0.0, nt * 2),
        openvds.VolumeDataAxisDescriptor(nx, 'Crossline', 'm', 0.0, nx),
        openvds.VolumeDataAxisDescriptor(ni, 'Inline', 'm', 0.0, ni)
    ]

    channel_des = [
        openvds.VolumeDataChannelDescriptor(
            openvds.VolumeDataChannelDescriptor.Format.Format_R32,
            openvds.VolumeDataChannelDescriptor.Components.Components_1,
            "Value",
            'unitless',
            clim[0],
            clim[1],
            flags=openvds.VolumeDataChannelDescriptor.Flags.NoLossyCompression)
    ]

    metaData = openvds.MetadataContainer()
    metaData.setMetadataDoubleVector2(
        openvds.KnownMetadata.surveyCoordinateSystemOrigin().category,
        openvds.KnownMetadata.surveyCoordinateSystemOrigin().name,
        (1234.0, 4321.0))
    vds = openvds.create(vdsf, layout_des, axis_des, channel_des, metaData)

    layout = openvds.getLayout(vds)
    manager = openvds.getAccessManager(vds)
    accessor = manager.createVolumeDataPageAccessor(
        openvds.DimensionsND.Dimensions_012, 0, 0, 8,
        openvds.IVolumeDataAccessManager.AccessMode.AccessMode_Create, 1024)

    rang = range
    try:
        from tqdm import trange
        rang = trange
    except:
        pass

    for c in rang(accessor.getChunkCount()):
        page = accessor.createPage(c)
        buf = np.array(page.getWritableBuffer(), copy=False)
        (rmin, rmax) = page.getMinMax()
        if cigvis.is_line_first():
            buf[:, :, :] = d[rmin[2]:rmax[2], rmin[1]:rmax[1], rmin[0]:rmax[0]]
        else:
            buf[:, :, :] = d[rmin[0]:rmax[0], rmin[1]:rmax[1],
                             rmin[2]:rmax[2]].T

        page.release()

    accessor.commit()
    openvds.close(vds)

cigvis/io/vds.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.

from .merge import merge_meshs
from .points import *
cigvis/meshs/__init__.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
Merge all mesh to one mesh
"""

from typing import List
import numpy as np


def merge_meshs(vertices, faces):
    """
    Merge all meshs to one mesh
    """
    assert isinstance(vertices, List)
    assert isinstance(faces, List)
    assert len(vertices) == len(faces)

    num = [len(v) for v in vertices]
    vertices = np.vstack(vertices)

    out_faces = np.empty((0, 3), faces[0].dtype)
    offset = 0
    for i in range(len(num)):
        out_faces = np.vstack([out_faces, faces[i] + offset])
        offset += num[i]
    
    return vertices, out_faces

cigvis/meshs/merge.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
"""

import numpy as np


def cube_points(points, radius):
    """
    """
    assert points.ndim == 2
    assert points.shape[1] == 3
    x = points[:, 0]
    y = points[:, 1]
    z = points[:, 2]

    p1 = np.c_[x - radius, y - radius, z - radius]
    p2 = np.c_[x - radius, y - radius, z + radius]
    p3 = np.c_[x - radius, y + radius, z - radius]
    p4 = np.c_[x - radius, y + radius, z + radius]
    p5 = np.c_[x + radius, y - radius, z - radius]
    p6 = np.c_[x + radius, y - radius, z + radius]
    p7 = np.c_[x + radius, y + radius, z - radius]
    p8 = np.c_[x + radius, y + radius, z + radius]

    # N, 8, 3
    p = np.stack([p1, p2, p3, p4, p5, p6, p7, p8]).transpose(1, 0, 2)

    # start vert for each point mesh
    offsets = np.arange(len(points)) * 8

    # faces for one points
    vertex_indices = np.array([[0, 3, 2], [0, 1, 3], [0, 6, 2], [0, 4, 6],
                               [0, 5, 1], [0, 4, 5], [4, 7, 6], [4, 5, 7],
                               [2, 7, 3], [2, 6, 7], [1, 7, 3], [1, 5, 7]])
    vertex_indices = vertex_indices.reshape(1, -1)

    # faces.shape = (N, 36)
    faces = vertex_indices + offsets[:, np.newaxis]
    faces = faces.reshape(len(points) * 12, 3)

    return p.reshape(-1, 3).astype(np.float32), faces.astype(np.int32)


def regular_poly_points(points, radius, poly_points):
    """"""
    angles = np.arange(poly_points)
cigvis/meshs/points.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.

"""
surface
"""

cigvis/meshs/surfaces.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
To merge all curves into one Mesh,
To merge all trajectory into one Mesh.
"""

import numpy as np


def trajectory_mesh(points: np.ndarray,
                    radius,
                    tube_points: int,
                    normals=None,
                    binormals=None):
    """
    make the trajectory as a tube, return vertices and faces

    Parameters
    -----------
    points : array-like
        trajectory points, shape is like (N, 3)
    
    """
    assert points.shape[1] >= 3

    points = points[:, :3]

    if normals is None or binormals is None:
        tangents, normals, binormals = _frenet_frames(points, False)
    segments = len(points) - 1

    if not isinstance(radius, (float, int)):
        radius = np.array(radius).reshape(1, -1, 1)
    points = points[np.newaxis, ...]
    normals = normals[np.newaxis, ...]
    binormals = binormals[np.newaxis, ...]
    vertices = np.empty((tube_points, len(points), 3))
    angles = np.arange(tube_points) / tube_points * 2 * np.pi
    angles = angles.reshape(-1, 1, 1)
    vertices = points + -1. * radius * np.cos(angles) * normals + \
        radius * np.sin(angles) * binormals
    vertices = vertices.transpose(1, 0, 2)
    vertices = vertices.reshape(-1, 3)

    # construct the mesh
    indices = np.empty((segments * 2 * tube_points, 3), dtype=int)
    i, j = np.meshgrid(np.arange(segments),
                       np.arange(tube_points),
                       indexing='ij')
    i = i.flatten()
    j = j.flatten()
    jp = (j + 1) % tube_points
    indices[0::2, :] = np.c_[i * tube_points + j, (i + 1) * tube_points + j,
                             i * tube_points + jp]
    indices[1::2, :] = np.c_[(i + 1) * tube_points + j,
                             (i + 1) * tube_points + jp, i * tube_points + jp]

    # closed
    exdices = []
    for start in [0, len(vertices) - tube_points]:
        for i in range(tube_points - 2):
            j = start + i
            exdices.append([start, j + 1, j + 2])
    indices = np.vstack([indices, np.array(exdices)])

    return vertices, indices


def curves_mesh(points,
                radius,
                tube_points,
                vidx,
                traj_vertices,
                normals=None,
                binormals=None):
    """
    make curves as faces, return vertices and faces

    Parameters
    -----------
    points : array-like
        trajectory points, shape is like (N, 3)
    radius : array-like
        shape is like (m, N, 3)
    tube_points : int
        tube points
    vidx : array-like
        len(vidx) == m
    traj_vertices : array-like
        vertices of the trajectory mesh, shape is like (N*tube_points, 3)
    """

    assert points.shape[1] >= 3

    points = points[:, :3]

    if normals is None or binormals is None:
        tangents, normals, binormals = _frenet_frames(points, False)
    segments = len(points) - 1

    # TODO
    # get the positions of each vertex
    radius = radius[:, np.newaxis]
    angles = vidx / tube_points * 2 * np.pi
    grid = points + -1. * radius * np.cos(angles) * normals + \
        radius * np.sin(angles) * binormals

    start = len(vertices)
    vertices = np.vstack([vertices, grid])

    # each point has two face:
    # [[start + i, i, i + 1], [start + i, i + 1, start + i + 1]]
    indices = np.empty((2 * segments, 3), dtype=int)
    indices[:, 1] = np.repeat(np.arange(0, segments), 2)
    indices[:, 0] = indices[:, 1] + start
    indices[:, 2] = indices[:, 1] + 1
    indices[1::2, 1] += 1
    indices[1::2, 2] += start

    return vertices, indices


###### don't call ########


def _frenet_frames(points, closed=False):
    """
    This function is from vispy.visual.TubeVisual

    Calculates and returns the tangents, normals and binormals for
    the tube.
    """
    tangents = np.zeros((len(points), 3))
    normals = np.zeros((len(points), 3))

    epsilon = 0.0001

    # Compute tangent vectors for each segment
    tangents = np.roll(points, -1, axis=0) - np.roll(points, 1, axis=0)
    if not closed:
        tangents[0] = points[1] - points[0]
        tangents[-1] = points[-1] - points[-2]
    mags = np.sqrt(np.sum(tangents * tangents, axis=1))
    tangents /= mags[:, np.newaxis]

    # Get initial normal and binormal
    t = np.abs(tangents[0])

    smallest = np.argmin(t)
    normal = np.zeros(3)
    normal[smallest] = 1.

    vec = np.cross(tangents[0], normal)

    normals[0] = np.cross(tangents[0], vec)

    # Compute normal and binormal vectors along the path
    for i in range(1, len(points)):
        normals[i] = normals[i - 1]

        vec = np.cross(tangents[i - 1], tangents[i])
        if np.linalg.norm(vec) > epsilon:
            vec /= np.linalg.norm(vec)
            theta = np.arccos(np.clip(tangents[i - 1].dot(tangents[i]), -1, 1))
            normals[i] = rotate(-np.degrees(theta),
                                vec)[:3, :3].dot(normals[i])

    if closed:
        theta = np.arccos(np.clip(normals[0].dot(normals[-1]), -1, 1))
        theta /= len(points) - 1

        if tangents[0].dot(np.cross(normals[0], normals[-1])) > 0:
            theta *= -1.

        for i in range(1, len(points)):
            normals[i] = rotate(-np.degrees(theta * i),
                                tangents[i])[:3, :3].dot(normals[i])

    binormals = np.cross(tangents, normals)

    return tangents, normals, binormals


def rotate(angle, axis, dtype=None):
    """
    This function is from vispy.utils.transforms

    The 4x4 rotation matrix for rotation about a vector.

    Parameters
    ----------
    angle : float
        The angle of rotation, in degrees.
    axis : ndarray
        The x, y, z coordinates of the axis direction vector.

    Returns
    -------
    M : ndarray
        Transformation matrix describing the rotation.
    """
    angle = np.radians(angle)
    assert len(axis) == 3
    x, y, z = axis / np.linalg.norm(axis)
    c, s = np.cos(angle), np.sin(angle)
    cx, cy, cz = (1 - c) * x, (1 - c) * y, (1 - c) * z
    M = np.array(
        [[cx * x + c, cy * x - z * s, cz * x + y * s, .0],
         [cx * y + z * s, cy * y + c, cz * y - x * s, 0.],
         [cx * z - y * s, cy * z + x * s, cz * z + c, 0.], [0., 0., 0., 1.]],
        dtype).T
    return M

cigvis/meshs/well_logs.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
Functions for plotting 1D data using matplotlib
----------------------------------------------------

Some 1D plotting tools (based on matplotlib). 
Since matplotlib is already very convenient 
and provides more freedom, the implementations in this package 
are very simple and are only for reference. 

In the future, we will implement some demos that have more 
geophysical features.
"""

from typing import List, Tuple, Dict
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator

################## For traces plot #############


def plot1d(data: np.ndarray or List,
           dt: float = 1,
           beg: float = 0,
           orient: str = 'v',
           figsize: Tuple or List = (2, 8),
           title: str = None,
           axis_label: str = None,
           value_label: str = None,
           fill_up=None,
           fill_down=None,
           fill_color=None,
           c='#1f77b4',
           save=None,
           show=True,
           dpi=600,
           ax=None):
    """
    plot a 1d trace

    Parameters
    -----------
    data : array-like
        input data
    dt : float
        interval of data, such as 0.2 means data sampling in 0.2, 0.4, ...
    beg : float
        begin sampling, beg=1.6, dt=0.2 means data sampling is 1.6, 1.8, ..
    orient : Optinal ['v', 'h']
        orientation of the data, 'v' means vertical, 'h' means horizontal
    figsize : Tuple or List
        (value-axis length, sampling-axis length)
    title : str
        title
    axis_label : str
        sampling-axis label
    value_label : str
        value axis label
    c : mpl.colors.Color
        color for the line
    """
    assert len(data.shape) == 1
    figsize = figsize

    if fill_up is not None:
        assert fill_up > 0 and fill_up < 1
    if fill_down is not None:
        assert fill_down > 0 and fill_down < 1
    fill_color = c if fill_color is None else fill_color

    sampling = np.arange(beg, beg + len(data) * dt, dt)[:len(data)]

    if orient == 'h':
        data, sampling = sampling, data
        value_label, axis_label = axis_label, value_label
        figsize = (figsize[1], figsize[0])

    show = show and ax is None
    if ax is None:
        fig, ax = plt.subplots(figsize=figsize)
    ax.plot(data, sampling, c=c)
    _fill_traces(ax, data, sampling, fill_down, fill_up, orient, fill_color)
    ax.set_ylabel(axis_label)
    ax.set_xlabel(value_label)
    ax.set_title(title)
    if orient == 'v':
        plt.gca().invert_yaxis()

    if save is not None:
        plt.savefig(save, bbox_inches='tight', pad_inches=0.01, dpi=dpi)

    if show:
        plt.tight_layout()
        plt.show()


def plot_multi_traces(data,
                      dt=0.002,
                      beg=0,
                      c='black',
                      fill_up=None,
                      fill_down=None,
                      fill_color='black',
                      figsize=None,
                      xlabel='Trace number',
                      ylabel='Time / s',
                      save: str = None,
                      show: bool = True,
                      dpi=600,
                      ax=None):
    """
    data.shape = (h, n_traces)
    """
    h, n = data.shape
    r = data.max() - data.min()
    r = r if r != 0 else 1
    y = np.arange(beg, beg + h * dt, dt)[:h]

    if fill_up is not None:
        assert fill_up > 0 and fill_up < 1
    if fill_down is not None:
        assert fill_down > 0 and fill_down < 1

    # figsize = figsize if figsize is not None else (4, 6)
    show = show and ax is None
    if ax is None:
        fig, ax = plt.subplots(figsize=figsize)

    prev_max = 0
    tick_pos = []
    for i in range(n):
        l = (data[:, i] - data.min()) / r + prev_max
        prev_max = l.max() if data[:, i].sum() != 0 else prev_max + 1
        tick_pos.append(l.mean())
        ax.plot(l, y, c=c)
        _fill_traces(ax, l, y, fill_down, fill_up, color=fill_color)

    tick_label = np.arange(0, n)
    step = 1 if n // 10 == 0 else n // 10
    ax.set_xticks(tick_pos[::step])
    ax.set_xticklabels(tick_label[::step])
    ax.invert_yaxis()
    ax.tick_params(top=True, labeltop=True, bottom=False, labelbottom=False)
    # ax.xaxis.set_ticks_position('top')
    ax.xaxis.set_label_position('top')

    # plt.xticks(fontproperties='Times New Roman', size=tick_size)
    # plt.yticks(fontproperties='Times New Roman', size=tick_size)
    # fontdict = {
    #         'family': 'Times New Roman',
    #         'weight': 'bold',
    #         'size': label_size
    #     }
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)

    if save is not None:
        plt.savefig(save, bbox_inches='tight', pad_inches=0.01, dpi=dpi)

    if show:
        plt.tight_layout()
        plt.show()


def plot_signal_compare(raw,
                        offset_df=None,
                        offset_index=None,
                        with_offset=False,
                        ntstart=0,
                        ntend=6144,
                        show=True,
                        save=None,
                        dpi=600,
                        ax=None):

    show = show and ax is None
    fontdict1 = {'weight': 'normal', 'size': 14}

    i = 0
    temp = raw.copy()
    scale = 10
    if ax is None:
        fig, ax = plt.subplots(1, 1, figsize=(8, 4), dpi=150)

    x = np.arange(ntstart / 100, ntend / 100, 0.01)
    xtick = list(np.arange(0, 64 + 0.1, 5))
    xtick_label = ['%.0f' % i for i in xtick]

    if with_offset == False:
        for j in range(len(raw)):
            line1, = ax.plot(x,
                             temp[j, 0, :] + scale * j,
                             'black',
                             linewidth=0.5,
                             alpha=0.3)
            line1, = ax.plot(x,
                             temp[j, 1, :] + scale * j,
                             'black',
                             linewidth=0.5,
                             alpha=0.3)
            line1, = ax.plot(x,
                             temp[j, 2, :] + scale * j,
                             'black',
                             linewidth=0.5,
                             alpha=0.3)

        ax.set_ylim(len(raw) * scale + 5, -15)
        ax.set_ylabel('Station', fontdict=fontdict1)

    elif with_offset == True:
        for j in range(len(raw)):
            line1, = ax.plot(x,
                             temp[int(offset_index[j])][0] + offset_df[j],
                             'black',
                             linewidth=0.5,
                             alpha=0.3)
            line1, = ax.plot(x,
                             temp[int(offset_index[j])][1] + offset_df[j],
                             'black',
                             linewidth=0.5,
                             alpha=0.3)
            line1, = ax.plot(x,
                             temp[int(offset_index[j])][2] + offset_df[j],
                             'black',
                             linewidth=0.5,
                             alpha=0.3)

        ax.set_ylim(offset_df.max() + 5, offset_df.min() - 15)
        ax.set_ylabel('Offset (km)', fontdict=fontdict1)

    ax.yaxis.set_minor_locator(MultipleLocator(2))
    ax.set_xlabel('Time (s)', fontdict=fontdict1)
    ax.set_xticks(xtick, xtick_label)
    ax.set_xlim(0, 61.4)

    if save is not None:
        plt.savefig(save, bbox_inches='tight', pad_inches=0.01, dpi=dpi)

    if show:
        plt.tight_layout()
        plt.show()


####### don't call ########


def _fill_traces(ax, x, y, fill_down, fill_up, orient='v', color='black'):
    h = len(y)
    if fill_down is not None:
        if orient == 'v':
            fmin = x.mean() - (x.max() - x.min()) / 2 * fill_down
            ax.fill_betweenx(y,
                             x, [fmin] * h,
                             where=(x < fmin),
                             interpolate=True,
                             color=color)
        else:
            fmin = y.mean() - (y.max() - y.min()) / 2 * fill_down
            ax.fill_between(x,
                            y, [fmin] * h,
                            where=(y < fmin),
                            interpolate=True,
                            color=color)
    if fill_up is not None:
        if orient == 'v':
            fmax = x.mean() + (x.max() - x.min()) / 2 * fill_up
            ax.fill_betweenx(y,
                             x, [fmax] * h,
                             where=(x > fmax),
                             interpolate=True,
                             color=color)
        else:
            fmax = y.mean() + (y.max() - y.min()) / 2 * fill_up
            ax.fill_between(x,
                            y, [fmax] * h,
                            where=(y > fmax),
                            interpolate=True,
                            color=color)

cigvis/mpl1dplot.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
Functions for plotting 2D data using matplotlib
----------------------------------------------------

Some 2D plotting tools (based on matplotlib). 
Since matplotlib is already very convenient 
and provides more freedom, the implementations in this package 
are very simple and are only for reference. 

In the future, we will implement some demos that have more 
geophysical features.
"""

from typing import List, Tuple, Dict
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
from matplotlib.colors import ListedColormap, BoundaryNorm

import cigvis
from cigvis import colormap


def fg_image_args(img,
                  cmap='jet',
                  clim=None,
                  alpha=None,
                  interpolation='bicubic',
                  show_cbar=True,
                  **kwargs):
    """"""
    if cigvis.is_line_first():
        img = img.T

    kw = {}
    kw['X'] = img
    kw['cmap'] = cmap
    if clim is None:
        clim = [img.min(), img.max()]
    kw['vmin'] = clim[0]
    kw['vmax'] = clim[1]
    kw['alpha'] = alpha
    kw['interpolation'] = interpolation
    kw['show_cbar'] = show_cbar
    kw.update(kwargs)

    return [kw]


def line_args(x,
              y,
              color=None,
              alpha=None,
              lw=None,
              label=None,
              marker=None,
              linestyle=None,
              markersize=None,
              **kwargs):
    """"""
    kw = dict(x=x,
              y=y,
              color=color,
              alpha=alpha,
              lw=lw,
              label=label,
              marker=marker,
              linestyle=linestyle,
              markersize=markersize)
    kw.update(kwargs)
    return [kw]


def marker_args(x,
                y,
                s=None,
                c=None,
                marker=None,
                cmap=None,
                norm=None,
                vmin=None,
                vmax=None,
                alpha=None,
                zorder=2,
                **kwargs):
    """"""
    kw = dict(x=x,
              y=y,
              s=s,
              c=c,
              marker=marker,
              cmap=cmap,
              norm=norm,
              vmin=vmin,
              vmax=vmax,
              alpha=alpha,
              zorder=zorder)
    kw.update(kwargs)
    return [kw]


def annotate_args(x, y, text, w=1, h=1, **kwargs):
    """"""
    assert len(x) == len(y)
    assert len(x) == len(text)
    kw = []
    for i in range(len(x)):
        args = dict(text=text[i], xy=(x[i], y[i]), xytext=(x[i] + w, y[i] + h))
        args.update(kwargs)
        kw.append(args)
    return kw


def _check_is_disceret(data):
    size = int(data.size * (1 / 100.0))
    sampled = np.random.choice(data.ravel(), size=size, replace=False)
    unique = np.unique(sampled)
    if len(unique) > 20:
        return False
    else:
        return True


def discrete_cbar(im, ticks_label=None, remove_trans=False):
    cmap = im.cmap
    alpha = im._alpha
    if alpha is not None:
        cmap = colormap.set_alpha(cmap, alpha, False)

    if not _check_is_disceret(im._A):
        raise RuntimeError(
            "You data is not disceret, set `discrete=False` or " +
            "discerete data `show_cbar=True` instead")

    values = np.unique(im._A)
    if ticks_label is not None:
        assert len(values) == len(ticks_label)

    clim = [im._norm._vmin, im._norm._vmax]
    colors = colormap.get_colors_from_cmap(cmap, clim, values)

    if remove_trans:
        indices = np.where(colors[:, -1] == 0)[0]
        colors = colors[~np.isin(np.arange(len(colors)), indices)]
        values = values[~np.isin(np.arange(len(values)), indices)]
        if ticks_label is not None:
            ticks_label = [
                ticks_label[i] for i in range(len(ticks_label))
                if i not in indices
            ]

    cmap = ListedColormap(colors)
    # norm of equal intervals
    norm = BoundaryNorm(np.arange(0, (len(colors) + 1) * 2, 2), cmap.N)
    # set ticks in the center of boundaries
    ticks = np.arange(1, len(colors) * 2, 2)
    if ticks_label is None:
        ticks_label = values
    sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm)

    return sm, ticks, ticks_label,


def plot2d(
        img: np.ndarray,
        fg: Dict = None,
        cmap: str = 'gray',
        clim: List = None,
        interpolation: str = 'bicubic',

        # fig and label
        figsize: Tuple = None,
        title: str = None,
        xlabel: str = None,
        ylabel: str = None,

        # axis
        aspect: str = 'equal',
        axisoff: bool = False,
        xsample: List = None,
        ysample: List = None,

        # cbar
        cbar: str = None,
        discrete: bool = False,
        tick_labels: List = None,
        remove_trans: bool = True,

        # legend
        show_legend: bool = False,

        # font size
        xlabel_size: float = None,
        ylabel_size: float = None,
        title_size: float = None,
        ticklabels_size: float = None,
        cbar_ticklabels_size: float = None,
        cbar_label_size: float = None,

        # save
        save: str = None,
        dpi: float = 600,
        show: bool = True,
        ax=None):
    """
    plot2d

    Parameters
    -----------
    img : array-like
        background img, shape=(n1, n2)
    fg : Dict
        foreground, it can be a combination of 
        'img', 'line', 'marker', 'annotate'
    cmap : str or Colormap
        cmap for background image ('img')
    clim : List
        [vmin, vmax] for background
    interpolation : str 
        interpolation method for background
    
    figsize : Tuple
        figure size
    title : str
        title string
    xlabel : str
        label for x axis
    ylabel : str
        label for y axis
    
    aspect : str or float
        can be 'equal' (default), 'auto' or a float number,
        if is a float number v, it means H / W = w
    axisoff : bool
        turn off axis
    xsample : List
        sampling information for x axis, it can be [x_start, x_step].
        x_start is the start of axis, x_step is interval of x axis, 
    ysample : List
        sampling information for y axis, it can be [y_start, y_step].
        y_start is the start of axis, y_step is interval of y axis, 

    cbar : str
        If is not None, will show a colorbar at the right, cabr is 
        the label. If `cbar=''`, will ignore colorbar label
    discrete : bool
        discrete colorbar
    tick_labels : List
        If discrete, set a label for each value
    remove_trans : bool
        If alpha of the value is 0, colorbar will remove the value
    
    show_legend : bool
        show legend if fg contains 'line'
    
    font_size : float
        each font size
    
    save : str
        save name
    show : bool
        call plt.show()
    ax : Any
        parent axes, used when plt.subplots()
    """
    if cigvis.is_line_first():
        img = img.T

    if clim is None:
        clim = [img.min(), img.max()]

    show = show and ax is None
    if ax is None:
        fig, ax = plt.subplots(figsize=figsize)

    if isinstance(cmap, str):
        cmap = colormap.get_cmap_from_str(cmap)

    cbar_im = ax.imshow(img,
                        cmap=cmap,
                        vmin=clim[0],
                        vmax=clim[1],
                        aspect=aspect,
                        interpolation=interpolation)

    if fg is not None:
        for k, v in fg.items():
            if k == 'img':
                for i in range(len(v)):
                    show_cbar = v[i].pop('show_cbar', False)
                    im = ax.imshow(aspect=aspect, **v[i])
                    if show_cbar:
                        cbar_im = im
            elif k == 'line':
                for i in range(len(v)):
                    x = v[i]['x']
                    y = v[i]['y']
                    v[i].pop('x', None)
                    v[i].pop('y', None)
                    ax.plot(x, y, **v[i])
            elif k == 'marker':
                for i in range(len(v)):
                    ax.scatter(**v[i])
            elif k == 'annotate':
                for i in range(len(v)):
                    ax.annotate(**v[i])

    if cbar is not None:
        divider = make_axes_locatable(ax)
        box_apspect = None
        if isinstance(aspect, float) and aspect < 1:
            box_apspect = 20 * aspect
        cax = divider.append_axes('right',
                                  size='5%',
                                  pad=0.05,
                                  box_aspect=box_apspect)

        if not discrete:
            cb = plt.colorbar(cbar_im, cax=cax)
        else:
            sm, ticks, tick_labels = discrete_cbar(cbar_im, tick_labels,
                                                   remove_trans)
            cb = plt.colorbar(sm, cax=cax)
            cb.set_ticks(ticks)
            cb.set_ticklabels(tick_labels)

        cb.ax.set_ylabel(cbar, fontsize=cbar_label_size)
        cb.ax.tick_params(labelsize=cbar_ticklabels_size)

    if show_legend:
        plt.legend()

    if xsample is not None:
        xtick = ax.get_xticks()
        xtick = xtick[xtick >= 0]
        xtick = xtick[xtick < img.shape[1]]
        xticklabels = xsample[0] + xtick * xsample[1]
        ax.set_xticks(xtick)
        ax.set_xticklabels(xticklabels)

    if ysample is not None:
        ytick = ax.get_yticks()
        ytick = ytick[ytick >= 0]
        ytick = ytick[ytick < img.shape[0]]
        yticklabels = ysample[0] + ytick * ysample[1]
        ax.set_yticks(ytick)
        ax.set_yticklabels(yticklabels)

    if ticklabels_size is not None:
        ax.tick_params(axis='both', labelsize=ticklabels_size)

    if axisoff:
        ax.axis('off')

    if title is not None:
        ax.set_title(title, fontsize=title_size)
    if xlabel is not None:
        ax.set_xlabel(xlabel, fontsize=xlabel_size)
    if ylabel is not None:
        ax.set_ylabel(ylabel, fontsize=ylabel_size)

    if save is not None:
        plt.savefig(save, bbox_inches='tight', pad_inches=0.01, dpi=dpi)

    if show:
        plt.tight_layout()
        plt.show()


if __name__ == '__main__':
    pass
cigvis/mpl2dplot.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
Functions for drawing 3D seismic figure using plotly
----------------------------------------------------

TODO: The code for the `plotly` part is not yet fully developed,
and there are only some basic implementations.
We will continue to improve it in the future.

Note
----
Only run in jupyter environment (not include Ipython)

In plotly, for a seismic volume,
- x means inline order
- y means crossline order
- z means time order

- ni means the dimension size of inline / x
- nx means the dimension size of crossline / y
- nt means the dimension size of time / depth / z


Examples
---------
>>> volume.shape = (192, 200, 240) # (ni, nx, nt)

\# only slices
>>> slice_traces = create_slices(volume, pos=[0, 0, 239], cmap='Petrel', show_cbar=True)
>>> plot3D(slice_traces)

\# add surface

\# surfs = [surf1, surf2, ...], each shape is (ni, nx)
>>> sf_traces = create_surfaces(surfs, surf_color='depth')

\# or use amplitude as color
>>> sf_traces = create_surfaces(surfs, volume, surf_color='amp')
>>> plot3D(slice_traces+sf_traces)

For more and detail examples, please refer our documents
"""
import warnings
from typing import List, Tuple, Union, Dict
import copy
import numpy as np
import plotly.graph_objects as go
# from skimage.measure import marching_cubes

import cigvis
from cigvis import colormap
from cigvis.utils import plotlyutils


def create_slices(volume: np.ndarray,
                  pos: Union[List, Dict] = None,
                  clim: List = None,
                  cmap: str = 'Petrel',
                  alpha: float = 1,
                  show_cbar: bool = False,
                  cbar_params: Dict = None):
    """
    Parameters
    ----------
    volume : array-like
        3D array
    pos : List or Dict
        init position of the slices, can be a List or Dict, such as:
        ```
        pos = [0, 0, 200] # x: 0, y: 0, z: 200
        pos = [[0, 200], [9], []] # x: 0 and 200, y: 9, z: None
        pos = {'x': [0, 200], 'y': [1], z: []}
        ```
    clim : List
        [vmin, vmax] for plotting 
    cmap : str or Colormap
        colormap, it can be str or matplotlib's Colormap or vispy's Colormap
    alpha : float
        opacity
    show_bar : bool
        show colorbar
    cbar_params : Dict
        parameters pass to colorbar

    Returns
    -------
    traces : List
        List of go.Surface
    """
    line_first = cigvis.is_line_first()

    shape = volume.shape
    nt = shape[2] if line_first else shape[0]

    if pos is None:
        pos = dict(x=[0], y=[0], z=[nt - 1])
    if isinstance(pos, List):
        assert len(pos) == 3
        if isinstance(pos[0], List):
            x, y, z = pos
        else:
            x, y, z = [pos[0]], [pos[1]], [pos[2]]
        pos = {'x': x, 'y': y, 'z': z}
    assert isinstance(pos, Dict)

    if clim is None:
        clim = [volume.min(), volume.max()]
    vmin, vmax = clim

    slices, pos = plotlyutils.make_slices(volume, pos=pos)

    dimname = dict(x='inline', y='crossline', z='time')

    cmap = colormap.cmap_to_plotly(cmap)

    traces = []

    idx = 0
    for dim in ['x', 'y', 'z']:

        assert len(slices[dim]) == len(pos[dim])

        for j in range(len(slices[dim])):

            if show_cbar and idx == 0:
                showscale = True
            else:
                showscale = False

            idx += 1

            s = slices[dim][j]
            plotlyutils.verifyshape(s.shape, shape, dim)
            num = pos[dim][j]
            name = f'{dim}/{dimname[dim]}'
            xx, yy, zz = plotlyutils.make_xyz(num, shape, dim)

            traces.append(
                go.Surface(x=xx,
                           y=yy,
                           z=zz,
                           surfacecolor=s,
                           colorscale=cmap,
                           cmin=vmin,
                           cmax=vmax,
                           opacity=alpha,
                           name=name,
                           colorbar=cbar_params,
                           showscale=showscale,
                           showlegend=False))

    return traces


def create_surfaces(surfs,
                    volume=None,
                    value_type='depth',
                    clim=None,
                    cmap='jet',
                    show_cbar=False):

    line_first = cigvis.is_line_first()

    # add surface
    if not isinstance(surfs, List):
        surfs = [surfs]

    surfs_values = []
    if value_type == 'amp':
        if volume is None:
            print("Must input volume if value_type is 'amp' (amplitude)")
        for s in surfs:
            surfs_values.append(
                cigvis.utils.surfaceutils.interp_surf(volume, s))
    else:
        surfs_values = copy.deepcopy(surfs)

    if clim is None:
        vmin = min([s.min() for s in surfs_values])
        vmax = max([s.max() for s in surfs_values])
    else:
        vmin, vmax = clim

    cmap = colormap.cmap_to_plotly(cmap)

    traces = []

    for s, v in zip(surfs, surfs_values):
        if line_first:
            s = s.T
            v = v.T

        traces.append(
            go.Surface(z=s,
                       surfacecolor=v,
                       colorscale=cmap,
                       cmin=vmin,
                       cmax=vmax,
                       showscale=show_cbar))

    return traces


def create_Line_logs(logs, cmap='jet', line_width=8):
    """
    logs can be a np.ndarray (one log), or List of np.ndarray (muti-logs).
    each element's shape is (N, 3) or (N, 4).
    each row is (x, y, z) or (x, y, z, value)
    """

    cmap = colormap.cmap_to_plotly(cmap)

    if isinstance(logs, np.ndarray):
        logs = [logs]

    traces = []
    for log in logs:
        assert log.shape[1] >= 3
        if log.shape[1] == 3:
            value = log[:, 2]
        else:
            value = log[:, 3]

        traces.append(
            go.Scatter3d(x=log[:, 0],
                         y=log[:, 1],
                         z=log[:, 2],
                         line=dict(color=value,
                                   colorscale=cmap,
                                   width=line_width),
                         mode='lines',
                         showlegend=False))

    return traces


def create_well_logs(points, values=None):
    """
    use Mesh3D to create tube logs
    """
    tube = go.Streamtube()



def create_points(points, color='red', size=3):
    points = np.array(points)

    trace = go.Scatter3d(x=points[:, 0],
                         y=points[:, 1],
                         z=points[:, 2],
                         mode='markers',
                         marker=dict(symbol='square',
                                     size=size,
                                     color=color,
                                     line=dict(width=1, color='black')),
                         showlegend=False)

    return [trace]


def create_bodys(volume, level, margin: float = None, color='yellow'):
    if margin is not None:
        if isinstance(volume, np.memmap):
            assert volume.mode != 'r', "margin will modify the volume, set `mode='c'` instead of `mode='r'` in np.memmap"
        volume[0, :, :] = margin
        volume[:, 0, :] = margin
        volume[:, :, 0] = margin
        volume[volume.shape[0] - 1, :, :] = margin
        volume[:, volume.shape[1] - 1, :] = margin
        volume[:, :, volume.shape[2] - 1] = margin

    verties, faces, _, _1 = marching_cubes(volume, level)
    x = verties[:, 0]
    y = verties[:, 1]
    z = verties[:, 2]

    i = faces[:, 0]
    j = faces[:, 1]
    k = faces[:, 2]

    trace = go.Mesh3d(x=x,
                      y=y,
                      z=z,
                      i=i,
                      j=j,
                      k=k,
                      color=color,
                      showscale=False,
                      flatshading=True)

    return [trace]


def create_overlay(**kwargs):
    """
    """
    warnings.warn(
        "Plotly has poor support for overlay display, so we won't implement this",
        UserWarning)
    pass


def plot3D(traces, **kwargs):

    size = kwargs.get('size', (900, 900))
    size = (size, size) if isinstance(size, int) else size

    scene = plotlyutils.make_3Dscene(**kwargs)

    fig = go.Figure(data=traces)

    fig.update_layout(height=size[0],
                      width=size[1],
                      scene=scene,
                      margin=dict(t=0, l=0, b=0))

    savequality = kwargs.get('savequality', 1)

    fig.show(
        config={
            'toImageButtonOptions': {
                'format': 'png',
                'filename': 'custom_image',
                'scale': savequality
            }
        })

cigvis/plotlyplot.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.

from .utils import *
from . import surfaceutils
from . import vispyutils
from . import plotlyutils
cigvis/utils/__init__.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.

"""
utils for plotly visualization in jupyter

TODO: To be improved
"""


from typing import Union, Tuple, List, Dict
import numpy as np

import cigvis


def make_xyz(idx: int, shape: Union[Tuple, List],
             axis: str) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    """
    make xx, yy, zz (meshgrid) for plotly

    Parameters
    ----------
    idx : int
        index
    shape : List or Tuple
        len(shape) == 3, data's shape
    axis : str
        axis can be: 'x' or 'inline', 'y' or 'crossline', 'z' or 'time'

    Returns
    -------
    xx : np.ndarray
    yy : np.ndarray
    zz : np.ndarray

    """
    line_first = cigvis.is_line_first()

    if line_first:
        ni, nx, nt = shape
    else:
        nt, nx, ni = shape

    if axis == 'x' or axis == 'inline':
        assert idx >= 0 and idx < ni
        yy, zz = np.meshgrid(np.arange(nx), np.arange(nt))
        xx = idx * np.ones((nt, nx))
    elif axis == 'y' or axis == 'crossline':
        assert idx >= 0 and idx < nx
        xx, zz = np.meshgrid(np.arange(ni), np.arange(nt))
        yy = idx * np.ones((nt, ni))
    elif axis == 'z' or axis == 'time':
        assert idx >= 0 and idx < nt
        xx, yy = np.meshgrid(np.arange(ni), np.arange(nx))
        zz = idx * np.ones((nx, ni))
    else:
        raise ValueError(f"Invalid value of axis: {axis}")

    return xx, yy, zz


def get_image_func(volume, axis, idx, prefunc=None):
    """
    get a slice image from a volume with axis and idx
    """
    line_first = cigvis.is_line_first()

    if line_first:
        if axis == 'x':
            out = volume[idx, :, :].T
        elif axis == 'y':
            out = volume[:, idx, :].T
        elif axis == 'z':
            out = volume[:, :, idx].T
    else:
        if axis == 'x':
            out = volume[:, :, idx]
        elif axis == 'y':
            out = volume[:, idx, :]
        elif axis == 'z':
            out = volume[idx, :, :]

    if prefunc is not None:
        out = prefunc(out)

    return out


def make_slices(data: np.ndarray,
                x: List or int = [],
                y: List or int = [],
                z: List or int = [],
                pos: Dict = None) -> Tuple[Dict, Dict]:
    """
    make slices and locations for plotly

    Parameters
    ----------
    data : np.ndarray
        Input data
    x : List or int
        x or inline index
    y : List or int
        y or crossline index
    z : List or int
        z or time index

    Returns
    -------
    slices : Dict
        slices, Dict[str: np.ndarray]
    pos : Dict
        positions of the slices
    """

    slices = {'x': [], 'y': [], 'z': []}
    if pos is None:
        x = [x] if isinstance(x, int) else x
        y = [y] if isinstance(y, int) else y
        z = [z] if isinstance(z, int) else z

        pos = {'x': x, 'y': y, 'z': z}

    assert isinstance(pos, Dict)

    for axis in pos.keys():
        for idx in pos[axis]:
            s = get_image_func(data, axis, idx)
            slices[axis].append(s)

    return slices, pos


def verifyshape(sshape: tuple,
                shape: tuple,
                axis: str) -> None:
    """
    verify the slice shape is invalid
    Note: sshape is slice.shape which is transposed if line_first is False
    """
    line_first = cigvis.is_line_first()

    if line_first:
        ni, nx, nt = shape
    else:
        nt, nx, ni = shape
    if axis == 'x' or axis == 'inline':
        assert sshape == (nt, nx)
    elif axis == 'y' or axis == 'crossline':
        assert sshape == (nt, ni)
    elif axis == 'z' or axis == 'time':
        assert sshape == (nx, ni)
    else:
        raise ValueError(f"Invalid value of axis: {axis}")


######## **kwargs for figure *******************
def kwargs_todict(v):
    if isinstance(v, dict):
        return v
    elif isinstance(v, (int, float, bool, str)):
        return dict(x=v, y=v, z=v)
    elif isinstance(v, list) or isinstance(v, tuple):
        assert len(v) == 3
        return dict(x=v[0], y=v[1], z=v[2])
    else:
        raise TypeError(f"Invalid type of v: {type(v)}")


def kwargs_toaxies(v, key, scene):
    out = []
    if isinstance(v, (int, float, bool, str)):
        out = [v, v, v]
    elif isinstance(v, (list, tuple)):
        out = v
    elif isinstance(v, dict):
        out = [v['x'], v['y'], v['z']]
    else:
        raise TypeError(f"Invalid type of v: {type(v)}")

    scene['xaxis'][key] = out[0]
    scene['yaxis'][key] = out[1]
    scene['zaxis'][key] = out[2]
    return scene


def make_3Dscene(**kwargs):
    scene = {'camera': {}, 'xaxis': {}, 'yaxis': {}, 'zaxis': {}}

    # modify eye to change view
    # x < 0, clockwise, z > 0, top -> down
    if cigvis.is_z_reversed() and cigvis.is_y_reversed():
        scene['camera']['eye'] = kwargs_todict(
            kwargs.get('eye', dict(x=1.25, y=-1.5, z=1.5)))
    elif cigvis.is_z_reversed() and not cigvis.is_y_reversed():
        scene['camera']['eye'] = kwargs_todict(
            kwargs.get('eye', dict(x=1.5, y=1.5, z=1.5)))

    scene['camera']['center'] = kwargs_todict(kwargs.get('center', 0))
    scene['camera']['up'] = kwargs_todict(kwargs.get('up', dict(x=0, y=0,
                                                                z=1)))
    scene['aspectratio'] = kwargs.get('aspectratio', None)
    scene['aspectmode'] = kwargs.get('aspectmode', 'data')

    if cigvis.is_x_reversed():
        scene['xaxis']['autorange'] = 'reversed'
    if cigvis.is_y_reversed():
        scene['yaxis']['autorange'] = 'reversed'
    if cigvis.is_z_reversed():
        scene['zaxis']['autorange'] = 'reversed'

    if 'clean' in kwargs and kwargs.get('clean'):
        scene = kwargs_toaxies(False, 'showticklabels', scene)
        scene = kwargs_toaxies(False, 'showbackground', scene)
        scene = kwargs_toaxies(False, 'showspikes', scene)
        scene = kwargs_toaxies('', 'title', scene)
        return scene

    scene = kwargs_toaxies(kwargs.get('nticks', 5), 'nticks', scene)

    optionkeys = [
        'showticklabels', 'showbackground', 'showspikes', 'backgroundcolor',
        'linecolor', 'gridcolor', 'spikecolor'
    ]

    for key in optionkeys:
        if key in kwargs:
            scene = kwargs_toaxies(kwargs.get(key), key, scene)
    if 'tickcolor' in kwargs:
        scene = kwargs_toaxies(kwargs.get('tickcolor'), 'color', scene)

    if 'showtitle' in kwargs and not kwargs.get('showtitle'):
        title = ['', '', '']
    else:
        title = ['Inline', 'Crossline', 'Time']
    scene['xaxis']['title'] = title[0]
    scene['yaxis']['title'] = title[1]
    scene['zaxis']['title'] = title[2]

    return scene

cigvis/utils/plotlyutils.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
utils to process surface, such as create triangular mesh
"""

from typing import List, Union, Tuple
import numpy as np
from scipy.interpolate import griddata
from scipy.interpolate import interp1d
from cigvis import is_line_first

try:
    import numba
    USE_NUMBA = True
except ImportError:
    USE_NUMBA = False

##### Triangular meshing #######


def get_vertices_and_faces(surf: np.ndarray,
                           mask: np.ndarray = None,
                           mask_type: str = 'e',
                           anti_rot: bool = True) -> Tuple:
    """
    Get vertices and faces, which will be used in Mesh

    Parameters
    ----------
    surf : array-like
        the input surface, shape as (ni, nx)
    mask : bool, array-like
        mask is a bool array, whose shape is same as `surf`,
        mask is used to remove some points in the x-y meshgrid.
    mask_type : str
        Choice of {'e', 'i'}, 
        'e' means the `surf[i, j]` will be remove if `mask[i, j]` is True,
        'i' means the `surf[i, j]` will be kept if `mask[i, j]` is True.
    anti_rot : bool
        If True, is anticlock rotation.
        a         a, b, c is index of vertices
        .  .      ==> If anticlock, face is [a, b, c]
        .   .     ==> If not anticlock, face is [a, c, b]
        b ... c


    Returns
    -------
    vertices : array-like
        shape as (n1*n2, 3), each row is (x, y, z) position
    faces : array-like
        shape as ((n1-1)*(n2-1)*2, 3), each row contains 3 points to
        consist a face. Like:
        a ... b    a           a ... b
        .     . => .  .     &    .   .
        .     . => .   .    &      . .
        c ... d    c ... d           c
        vertices (4 points) = [[0, 0, a], [0, 1, b], [1, 0, c], [1, 1, d]]
        faces (2 faces) = [[0, 2, 3], [0, 1, 3]] (clockwise), 
        or [[0, 3, 2], [0, 3, 2]] (anticlock)
        In faces, x means x-th vertices
    """
    n1, n2 = surf.shape
    if mask is not None and mask_type == 'e':
        mask = ~mask

    # set grid
    if mask is None:
        grid = np.arange(n1 * n2).reshape(n1, n2)
    else:
        grid = np.zeros_like(surf) - 1
        grid[mask] = np.arange(mask.sum())

    # get vertices
    y, x = np.meshgrid(np.arange(n2), np.arange(n1))
    vertices = np.stack((x, y, surf), axis=-1).reshape(-1, 3)
    if mask is not None:
        vertices = vertices[mask.flatten()]

    faces = np.zeros((n1 - 1, n2 - 1, 2, 3))
    faces[:, :, 0, 0] = grid[:-1, :-1]
    faces[:, :, 0, 1] = grid[1:, :-1]
    faces[:, :, 0, 2] = grid[1:, 1:]
    faces[:, :, 1, 0] = grid[:-1, :-1]
    faces[:, :, 1, 1] = grid[1:, 1:]
    faces[:, :, 1, 2] = grid[:-1, 1:]

    faces = faces.reshape(-1, 3).astype(int)
    faces = faces[~np.any(faces == -1, axis=1)]
    if anti_rot:
        faces[:, [1, 2]] = faces[:, [2, 1]]

    return vertices, faces.astype(int)


def fill_grid(points: np.ndarray,
              shape: Union[List, Tuple],
              interp: bool = True,
              method: str = 'cubic',
              fill: float = -1) -> np.ndarray:
    """
    fill `points` in a grid in `shape`. If interp, interpolation,
    this function doesn't extrapolate, so we fill the edge
    as `fill`.

    Parameters
    ----------
    points : array-like
        shape as (N, 3), N points, each row means (x, y, z) coordinate
    shape : List or Tuple
        [n1, n2], the grid shape, points will be filled into a n1*n2 grid
    interp : bool
        if True, will interpolate the grid from points, but not extrapolate,
        this means some edge points will be `fill`
    method : str
        interpolation method
    fill : float
        the points can not be interpolated will be filled as `fill`, default is -1 
    """

    n1, n2 = shape
    assert points[:, 0].max() < n1 and points[:, 0].min() >= 0
    assert points[:, 1].max() < n2 and points[:, 1].min() >= 0

    if interp:
        coords = points[:, :2]
        values = points[:, 2]
        y, x = np.meshgrid(np.arange(n2), np.arange(n1))

        grid = griddata(coords, values, (x, y), method=method)
        grid[np.isnan(grid)] = fill
    else:
        n3 = points.shape[1] - 2
        shape = (n1, n2, n3)
        grid = np.full(shape, fill).astype(float)
        x = points[:, 0].astype(int)
        y = points[:, 1].astype(int)
        grid[x, y, ...] = points[:, 2:]
        if n3 == 1:
            grid = grid.reshape(n1, n2)

    return grid


##### preprocess ######


def preproc_surf_array2(surf, volume=None, value_type='depth'):
    """
    surf shape as (n1, n2)
    """
    assert surf.ndim == 2
    if value_type == 'depth':
        value = None
    else:
        assert volume is not None
        value = interp_surf(volume, surf)

    return surf, value, None


def preproc_surf_array3(surf, value_type='depth'):
    """
    surf shape as (n1, n2, 2) or (n1, n2, 4) or (n1, n2, 5)
    """
    assert surf.ndim == 3
    if value_type == 'depth':
        value = None
        color = None
    else:
        if surf.shape[2] == 2:
            value = surf[:, :, 1]
            color = None
        elif surf.shape[2] == 4 or surf.shape[2] == 5:
            value = None
            color = surf[:, :, 1:]
        else:
            raise RuntimeError("Invalid shape")

    return surf[:, :, 0], value, color


def preproc_surf_pos(surf,
                     shape,
                     volume=None,
                     value_type='depth',
                     interp=True,
                     method='cubic',
                     fill=-1):
    """
    surf shape as (N, 3) or (N, 4) or (N, 6) or (N, 7)
    """
    assert surf.ndim == 2
    assert surf.shape[1] >= 3

    out_surf = fill_grid(surf[:, :3], shape, interp, method, fill)

    if value_type == 'depth':
        value = None
        color = None
    else:
        if surf.shape[1] == 3:
            assert volume is not None
            value = interp_surf(volume, out_surf)
            color = None
        elif surf.shape[1] == 4:
            value = surf[:, 3]
            # fill grid
            value = fill_grid(surf[:, [0, 1, 3]], shape, interp, method,
                              np.nan)
            color = None
        elif surf.shape[1] == 6 or surf.shape[1] == 7:
            assert interp == False
            value = None
            indices = [0, 1, 3, 4, 5]
            if surf.shape[1] == 7:
                indices += [6]
            color = fill_grid(surf[:, indices], shape, False, fill=np.nan)
        else:
            raise RuntimeError("Invalid shape")

    return out_surf, value, color


##### cdpxy to iline/xline


def xy_to_ix(f,
             cdpx_beg,
             cdpy_beg,
             i_interval,
             x_interval,
             time_beg,
             dt,
             istep=1,
             xstep=1):
    if not isinstance(f, np.ndarray):
        f = np.loadtxt(f, np.float32)
    out = np.zeros_like(f)
    out[:, 2] = (f[:, 2] - time_beg) / dt
    out[:, 1] = (f[:, 0] - cdpx_beg) / i_interval * istep
    out[:, 0] = (f[:, 1] - cdpy_beg) / x_interval * xstep

    return out


##### interpolation #####


def interp_linefirst_impl(volume: np.ndarray, surf: np.ndarray) -> np.ndarray:
    """
    interp value of linefirst volume in surf positions

    Parameters
    ----------
    volume : array-like
        3D array, shape as (ni, nx, nt)
    surf : array-like
        2D array, shape as (ni, nx), each point means z pos

    Returns
    -------
    value : array-like
        2D array, shape as (ni, nx), each point means value in z pos
    """
    ni, nx, nt = volume.shape
    out = np.zeros_like(surf)
    for i in range(ni):
        for j in range(nx):
            x = np.arange(nt)
            out[i, j] = np.interp(surf[i, j], x, volume[i, j, :])

    return out


# @numba.jit(nopython=True)
def interp_timefirst_impl(volume: np.ndarray, surf: np.ndarray) -> np.ndarray:
    """
    interp value of timefirst volume in surf positions

    Parameters
    ----------
    volume : array-like
        3D array, shape as (nt, nx, ni)
    surf : array-like
        2D array, shape as (nx, ni), each point means z/nt pos

    Returns
    -------
    value : array-like
        2D array, shape as (nx, ni), each point means value in z/nt pos
    """
    nt, nx, ni = volume.shape
    out = np.zeros_like(surf)
    for j in range(nx):
        for i in range(ni):
            x = np.arange(nt)
            out[j, i] = np.interp(surf[j, i], x, volume[:, j, i])

    return out


# add numba to accelerate
if USE_NUMBA:
    interp_linefirst = numba.jit(nopython=True)(interp_linefirst_impl)
    interp_timefirst = numba.jit(nopython=True)(interp_timefirst_impl)
else:
    interp_linefirst = interp_linefirst_impl
    interp_timefirst = interp_timefirst_impl


def interp_surf(volume: np.ndarray, surf: np.ndarray) -> np.ndarray:
    """
    interp value of volume in surf positions

    Parameters
    ----------
    volume : array-like
        3D array
    surf : array-like
        2D array, each point means z/nt pos
    

    Returns
    -------
    value : array-like
        2D array, each point means value in z/nt pos

    """
    line_first = is_line_first()

    if line_first:
        assert volume.shape[:2] == surf.shape
    else:
        assert volume.shape[1:] == surf.shape

    out = np.zeros_like(surf)
    if line_first:
        out = interp_linefirst(volume, surf)
    else:
        out = interp_timefirst(volume, surf)

    return out


def interp_surf_scipy(volume: np.ndarray,
                      surf: np.ndarray,
                      method: str = 'linear') -> np.ndarray:
    """
    interp value of volume in surf positions use scipy's interp1d

    Parameters
    ----------
    volume : array-like
        3D array
    surf : array-like
        2D array, each point means z/nt pos
    method : str
        method of scipy's interp1d
    
    Returns
    -------
    value : array-like
        2D array, each point means value in z/nt pos
    """
    line_first = is_line_first()
    if line_first:
        ni, nx, nt = volume.shape
        assert surf.shape == (ni, nx)
    else:
        nt, nx, ni = volume.shape
        assert surf.shape == (nx, ni)

    out = np.zeros_like(surf)
    for i in range(ni):
        for j in range(nx):
            x = np.arange(nt)
            if line_first:
                intfunc = interp1d(x, volume[i, j, :], kind=method)
                out[i, j] = intfunc(surf[i, j])
            else:
                intfunc = interp1d(x, volume[:, j, i], kind=method)
                out[j, i] = intfunc(surf[j, i])

    return out

cigvis/utils/surfaceutils.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.

"""
some utils
"""

import warnings
import numpy as np


def check_mmap(d: np.ndarray) -> None:
    if isinstance(d, np.memmap):
        if d.mode != 'r' and d.mode != 'c':
            warnings.warn(
                f"Your memmap data mode is '{d.mode}'. " +
                f"We strongly recommend using `mode='r'` or " +
                f"`mode='c'`, as `mode='{d.mode}'` may change " +
                f"file in some cases", UserWarning)

cigvis/utils/utils.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
utils for vispy visualization
"""

from typing import List
import numpy as np


def set_canvas_size(size, ratio, append=False):
    """
    set canvas size and colorbar size
    """
    if append:
        size = (size[0] / (1 - ratio), size[1])

    cbar_size = (size[0] * ratio, size[1])

    return cbar_size, size


def init_cbar_region_ratio(cbar):
    """
    init colorbar region ratio
    """
    cbar_label = cbar.label_str
    clim = cbar.clim_
    cbar_region_ratio = 0.1
    if cbar_label is not None and cbar_label != '':
        cbar_region_ratio += 0.025
    if np.abs(clim).max() >= 10:
        cbar_region_ratio += 0.01
    if np.abs(clim).max() >= 100:
        cbar_region_ratio += 0.01
    if np.abs(clim).max() >= 1000:
        cbar_region_ratio += 0.01
    if np.abs(clim).max() >= 10000:
        cbar_region_ratio += 0.01
    if np.abs(clim).max() <= 1:
        cbar_region_ratio += 0.03

    return cbar_region_ratio


def valid_kwargs(ktype):
    if ktype == 'mesh':
        mesh_kwargs = [
            'vertices', 'faces', 'vertex_colors', 'face_colors', 'color',
            'vertex_values', 'meshdata', 'shading', 'mode', 'gcode', 'program',
            'vshare'
        ]
        return mesh_kwargs
    elif ktype == 'image':
        image_kwargs = [
            'data', 'method', 'grid', 'cmap', 'clim', 'gamma', 'interpolation',
            'texture_format', 'custom_kernel'
        ]
        return image_kwargs
    elif ktype == 'align_image':
        align_image_kwargs = [
            'image_funcs', 'axis', 'pos', 'limit', 'cmaps', 'clims',
            'interpolation', 'method'
        ]
        return align_image_kwargs
    elif ktype == 'colorbar':
        colorbar_kwargs = [
            'size', 'cmap', 'clim', 'discrete', 'disc_ticks', 'dpi_scale',
            'label_str', 'label_color', 'label_size', 'tick_size',
            'border_width', 'border_color', 'savedir', 'visible', 'parent'
        ]
        return colorbar_kwargs
    elif ktype == 'canvas':
        canvas_kwargs = [
            'title', 'size', 'position', 'show', 'autoswap', 'app',
            'create_native', 'vsync', 'resizable', 'decorate', 'fullscreen',
            'config', 'shared', 'keys', 'parent', 'dpi', 'always_on_top',
            'px_scale', 'bgcolor'
        ]
        return canvas_kwargs
    elif ktype == 'viscanvas':
        seiscanvas_kwargs = [
            'size', 'bgcolor', 'visual_nodes', 'grid', 'share',
            'cbar_region_ratio', 'scale_factor', 'center', 'fov',
            'azimuth', 'elevation', 'zoom_factor', 'axis_scales', 'auto_range',
            'savedir', 'title'
        ]
        return seiscanvas_kwargs
    elif ktype == 'line':
        line_kwargs = [
            'pos', 'color', 'width', 'connect', 'method', 'antialias'
        ]
        return line_kwargs
    else:
        raise ValueError(f"Invalid ktype: '{ktype}'. ktype can be one " +
                         "of 'mesh', 'image', 'align_image', 'colorbar', " +
                         "'canvas', 'seiscanvas', 'line'")


def get_valid_kwargs(ktype, **kwargs):
    valid = valid_kwargs(ktype)
    outkwargs = {key: value for key, value in kwargs.items() if key in valid}
    return outkwargs
cigvis/utils/vispyutils.py
JintaoLee-Roger/cigvis
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright (c) 2023, Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC)
# -----------------------------------------------------------------------------

from .axis_aligned_image import AxisAlignedImage
from .fixed_image import FixedImage
from .volume_slices import volume_slices
from .xyz_axis import XYZAxis
from .colorbar import Colorbar
from .well_log import WellLog
from .vis_canvas import VisCanvas


cigvis/vispynodes/__init__.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.

from vispy.scene.visuals import Compound
from vispy.visuals import LineVisual
import numpy as np

int_to_axis = {0: 'x', 1: 'y', 2: 'z'}


class InterSectionLine(Compound):
    """"""

    def __init__(self, shape, pos, color='white', interval=0.1, width=1):
        if pos.count(None) == 0:
            self.nlines = 3
        elif pos.count(None) == 1:
            self.nlines = 1
        else:
            raise RuntimeError(f"No InterSection Line for pos: {pos}")

        if self.nlines == 1:
            idx = pos.index(None)
            axis = int_to_axis[idx]
            pos = [v for v in pos if v is not None]
            Compound.__init__(
                self,
                [FourLines(axis, pos, shape[idx], interval, color, width)])
        else:
            lines = []
            for i in range(self.nlines):
                tpos = [v for k, v in enumerate(pos) if k != i]
                lines.append(
                    FourLines(int_to_axis[i], tpos, shape[i], interval, color,
                              width))
            Compound.__init__(self, lines)

    def update_pos(self):
        """"""
        pass


class FourLines(LineVisual):

    def __init__(self,
                 axis,
                 pos,
                 length,
                 interval=0.1,
                 color='white',
                 width=1):
        assert len(pos) == 2
        assert axis in ['x', 'y', 'z']
        self.axis = axis
        self.len = length
        self.int = interval
        # self.antialias = True

        LineVisual.__init__(self, color=color, width=width, antialias=True)
        self._update_location(pos)

    def _update_location(self, pos):
        p = np.array([[0, pos[0] - self.int, pos[1] - self.int],
                      [0, pos[0] - self.int, pos[1] + self.int],
                      [0, pos[0] + self.int, pos[1] - self.int],
                      [0, pos[0] + self.int, pos[1] + self.int],
                      [self.len, pos[0] - self.int, pos[1] - self.int],
                      [self.len, pos[0] - self.int, pos[1] + self.int],
                      [self.len, pos[0] + self.int, pos[1] - self.int],
                      [self.len, pos[0] + self.int, pos[1] + self.int]])
        if self.axis == 'y':
            p = p[:, [1, 0, 2]]
        elif self.axis == 'z':
            p = p[:, [1, 2, 0]]

        points = np.array([
            p[0], p[1], p[3], p[2], p[0], p[4], p[5], p[7], p[6], p[4], p[5],
            p[1], p[3], p[7], p[6], p[2]
        ])
        self.set_data(pos=points)


class BoxLine(Compound):

    def __init__(self,
                 shape,
                 expand=3,
                 color='black',
                 width=1,
                 antialias=True):
        if isinstance(expand, (float, int)):
            expand = [expand] * 3
        assert len(expand) == 3
        assert len(shape) == 3
        shape = np.array(shape)
        expand = np.array(expand)
        p8 = shape + expand
        p1 = -1 * expand
        p2 = [p1[0], p1[1], p8[2]]
        p3 = [p1[0], p8[1], p1[2]]
        p4 = [p1[0], p8[1], p8[2]]
        p5 = [p8[0], p1[1], p1[2]]
        p6 = [p8[0], p1[1], p8[2]]
        p7 = [p8[0], p8[1], p1[2]]

        line1 = LineVisual(np.array([p1, p2, p4, p3, p1, p5, p6, p2]),
                           color=color,
                           width=width,
                           antialias=antialias)
        line2 = LineVisual(np.array([p6, p8, p7, p5]),
                           color=color,
                           width=width,
                           antialias=antialias)
        line3 = LineVisual(np.array([p4, p8]),
                           color=color,
                           width=width,
                           antialias=antialias)
        line4 = LineVisual(np.array([p3, p7]),
                           color=color,
                           width=width,
                           antialias=antialias)

        Compound.__init__(self, [line1, line2, line3, line4])


class Axis3D(Compound):

    def __init__(self):
        # TODO
        pass
cigvis/vispynodes/axis3d.py
JintaoLee-Roger/cigvis
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright (c) 2023, modified by Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC)
#
# Copyright (C) 2019 Yunzhi Shi @ The University of Texas at Austin.
# All rights reserved.
# Distributed under the MIT License. See LICENSE for more info.
# -----------------------------------------------------------------------------

import numpy as np
from vispy import scene
from vispy.visuals.transforms import MatrixTransform, STTransform


class AxisAlignedImage(scene.visuals.Image):
    """
    Visual subclass displaying an image that aligns to an axis.
    This image should be able to move along the perpendicular direction when
    user gives corresponding inputs.

    TODO: It will support the case where `pos` is a triplet 
    (the starting position of the slice), meaning it's not 
    a complete slice (only a portion of it).

    Parameters
    ----------
    image_func : List[array-like]
        input images
    axis : str
        one of ['x', 'y', 'z']
    pos : int
        position of the image, can be int number or Tuple,
        pos=10, axis='z' => start_pos = [0, 0, 10]
    limit : Tuple
        limit of the viewbox
    cmaps : List
        cmap for each image
    clims : List
        clim for each image
    interpolations : List
        interpolation method for each image
    method : str
        method of `Image`
    """

    def __init__(self,
                 image_funcs,
                 axis='z',
                 pos=0,
                 limit=None,
                 cmaps=['grays'],
                 clims=None,
                 interpolation=['linear'],
                 method='auto'):

        assert clims is not None, 'clim must be specified explicitly.'

        # Create an Image obj and unfreeze it so we can add more
        # attributes inside.
        # First image (from image_funcs[0])
        scene.visuals.Image.__init__(
            self,
            parent=None,  # no image func yet
            cmap=cmaps[0],
            clim=clims[0],
            interpolation=interpolation[0],
            method=method)
        self.unfreeze()

        self.ids = f'{axis}{pos}'

        self.interactive = True

        # Other images ...
        self.overlaid_images = [self]
        for i_img in range(1, len(image_funcs)):
            overlaid_image = scene.visuals.Image(
                parent=self,
                cmap=cmaps[i_img],
                clim=clims[i_img],
                interpolation=interpolation[i_img],
                method=method)
            self.overlaid_images.append(overlaid_image)

        # Set GL state. Must check depth test, otherwise weird in 3D.
        self.set_gl_state(depth_test=True,
                          depth_func='lequal',
                          blend_func=('src_alpha', 'one_minus_src_alpha'))

        # Determine the axis and position of this plane.
        self.axis = axis
        # Check if pos is within the range.
        if limit is not None:
            assert (pos>=limit[0]) and (pos<=limit[1]), \
              'pos={} is outside limit={} range.'.format(pos, limit)
        self.pos = pos
        self.limit = limit

        # Get the image_func that returns either image or image shape.
        self.image_funcs = image_funcs  # a list of functions!
        shape = self.image_funcs[0](self.pos, get_shape=True)

        # The selection highlight (a Plane visual with transparent color).
        # The plane is initialized before any rotation, on '+z' direction.
        self.highlight = scene.visuals.Plane(
            parent=self,
            width=shape[0],
            height=shape[1],
            direction='+z',
            color=(1, 1, 0, 0.1))  # transparent yellow color
        # Move the plane to align with the image.
        self.highlight.transform = STTransform(translate=(shape[0] / 2,
                                                          shape[1] / 2, 0))
        # This is to make sure we can see highlight plane through the images.
        self.highlight.set_gl_state('additive', depth_test=True)
        self.highlight.visible = False  # only show when selected

        # Set the anchor point (2D local world coordinates). The mouse will
        # drag this image by anchor point moving in the normal direction.
        self.anchor = None  # None by default
        self.offset = 0

        # Apply SRT transform according to the axis attribute.
        self.transform = MatrixTransform()
        # Move the image plane to the corresponding location.
        self._update_location()

        self.freeze()

    @property
    def axis(self):
        """
        The dimension that this image is perpendicular aligned to.
        """
        return self._axis

    @axis.setter
    def axis(self, value):
        value = value.lower()
        if value not in ('z', 'y', 'x'):
            raise ValueError('Invalid value for axis.')
        self._axis = value

    def set_anchor(self, mouse_press_event):
        """
        Set an anchor point (2D coordinate on the image plane) when left click
        in the selection mode (<Ctrl> pressed). After that, the dragging called
        in func 'drag_visual_node' will try to move along the normal direction
        and let the anchor follows user's mouse position as close as possible.
        """
        # Get the screen-to-local transform to get camera coordinates.
        tr = self.canvas.scene.node_transform(self)

        # Get click (camera) coordinate in the local world.
        click_pos = tr.map([*mouse_press_event.pos, 0, 1])
        click_pos /= click_pos[3]  # rescale to cancel out the pos.w factor
        # Get the view direction (camera-to-target) vector in the local world.
        view_vector = tr.map([*mouse_press_event.pos, 1, 1])[:3]
        view_vector /= np.linalg.norm(view_vector)  # normalize to unit vector

        # Get distance from camera to the drag anchor point on the image plane.
        # Eq 1: click_pos + distance * view_vector = anchor
        # Eq 2: anchor[2] = 0 <- intersects with the plane
        # The following equation can be derived by Eq 1 and Eq 2.
        distance = (0. - click_pos[2]) / view_vector[2]
        # only need vec2
        self.anchor = click_pos[:2] + distance * view_vector[:2]

    def drag_visual_node(self, mouse_move_event):
        """
        Drag this visual node while holding left click in the selection mode
        (<Ctrl> pressed). The plane will move in the normal direction
        perpendicular to this image, and the anchor point (set with func
        'set_anchor') will move along the normal direction to stay as close to
        the mouse as possible, so that user feels like 'dragging' the plane.
        """
        # Get the screen-to-local transform to get camera coordinates.
        tr = self.canvas.scene.node_transform(self)

        # Unlike in 'set_anchor', we now convert most coordinates to the screen
        # coordinate system, because it's more intuitive for user to do operations
        # in 2D and get 2D feedbacks, e.g. mouse leading the anchor point.
        anchor = [*self.anchor, self.pos, 1]  # 2D -> 3D
        # screen coordinates of the anchor point
        anchor_screen = tr.imap(anchor)
        anchor_screen /= anchor_screen[3]  # rescale to cancel out 'w' term
        anchor_screen = anchor_screen[:2]  # only need vec2

        # Compute the normal vector, starting from the anchor point and
        # perpendicular to the image plane.
        normal = [*self.anchor, self.pos + 1, 1]  # +[0,0,1,0] from anchor
        # screen coordinates of anchor + [0,0,1,0]
        normal_screen = tr.imap(normal)
        normal_screen /= normal_screen[3]  # rescale to cancel out 'w' term
        normal_screen = normal_screen[:2]  # only need vec2
        normal_screen -= anchor_screen  # end - start = vector
        # normalize to unit vector
        if not (normal_screen[0] == 0 and normal_screen[1] == 0):
            normal_screen /= np.linalg.norm(normal_screen)

        # Use the vector {anchor_screen -> mouse.pos} and project to the
        # normal_screen direction using dot product, we can get how far the plane
        # should be moved (on the screen!).
        drag_vector = mouse_move_event.pos[:2] - anchor_screen
        # normal_screen must be length 1
        drag = np.dot(drag_vector, normal_screen)

        # We now need to convert the move distance from screen coordinates to
        # local world coordinates. First, find where the anchor is on the screen
        # after dragging; then, convert that screen point to a local line shooting
        # across the normal vector; finally, find where the line comes directly
        # above/below the anchor point (before dragging) and get that distance as
        # the true dragging distance in local coordinates.
        new_anchor_screen = anchor_screen + normal_screen * drag
        new_anchor = tr.map([*new_anchor_screen, 0, 1])
        new_anchor /= new_anchor[3]  # rescale to cancel out the pos.w factor
        view_vector = tr.map([*new_anchor_screen, 1, 1])[:3]
        view_vector /= np.linalg.norm(view_vector)  # normalize to unit vector
        # Solve this equation:
        # new_anchor := new_anchor + view_vector * ?,
        # ^^^ describe a 3D line of possible new anchor positions
        # arg min (?) |new_anchor[:2] - anchor[:2]|
        # ^^^ find a point on that 3D line that minimize the 2D distance between
        #     new_anchor and anchor.
        numerator = anchor[:2] - new_anchor[:2]
        numerator *= view_vector[:2]  # element-wise multiplication
        numerator = np.sum(numerator)
        denominator = view_vector[0]**2 + view_vector[1]**2
        shoot_distance = numerator / denominator
        # Shoot from new_anchor to get the new intersect point. The z- coordinate
        # of this point will be our dragging offset.
        offset = new_anchor[2] + view_vector[2] * shoot_distance

        # Note: must reverse normal direction from -y direction to +y!
        if self.axis == 'y': offset = -offset
        # Limit the dragging within range.
        if self.limit is not None:
            if self.pos + offset < self.limit[0]:
                offset = self.limit[0] - self.pos
            if self.pos + offset > self.limit[1]:
                offset = self.limit[1] - self.pos
        self.offset = offset
        # Note: must reverse normal direction from +y direction to -y!
        if self.axis == 'y': offset = -offset

        self._update_location()

    def _update_location(self, pos=None):
        """
        Update the image plane to the dragged location and redraw this image.
        """
        if pos is None:
            self.pos += self.offset
            # must round to nearest integer location
            self.pos = int(np.round(self.pos))
        else:
            self.pos = pos

        # Update the transformation in order to move to new location.
        self.transform.reset()
        if self.axis == 'z':
            # 1. No rotation to do for z axis (y-x) slice. Only translate.
            self.transform.translate((0, 0, self.pos))
        elif self.axis == 'y':
            # 2. Rotation(s) for the y axis (z-x) slice, then translate:
            self.transform.rotate(90, (1, 0, 0))
            self.transform.translate((0, self.pos, 0))
        elif self.axis == 'x':
            # 3. Rotation(s) for the x axis (z-y) slice, then translate:
            self.transform.rotate(90, (1, 0, 0))
            self.transform.rotate(90, (0, 0, 1))
            self.transform.translate((self.pos, 0, 0))

        # Update image on the slice based on current position. The numpy array
        # is transposed due to a conversion from i-j to x-y axis system.
        # First image, the primary one:
        self.set_data(self.image_funcs[0](self.pos))  # remove .T
        # Other images, overlaid on the primary image:
        for i_img in range(1, len(self.image_funcs)):
            self.overlaid_images[i_img].set_data(self.image_funcs[i_img](
                self.pos))
        # Reset attributes after dragging completes.
        self.offset = 0
        self._bounds_changed()  # update the bounds with new self.pos

    def _compute_bounds(self, axis_3d, view):
        """
        Overwrite the original 2D bounds of the Image class. This will correct 
        the automatic range setting for the camera in the scene canvas. In the
        original Image class, the code assumes that the image always lies in x-y
        plane; here we generalize that to x-z and y-z plane.
        
        Parameters
        ----------
        axis_3d: int in {0, 1, 2}, represents the axis in 3D view box.
        view: the ViewBox object that connects to the parent.

        The function returns a tuple (low_bounds, high_bounds) that represents
        the spatial limits of self obj in the 3D scene.
        """
        # Note: self.size[0] is slow dim size, self.size[1] is fast dim size.
        if self.axis == 'z':
            if axis_3d == 0: return (0, self.size[0])
            elif axis_3d == 1: return (0, self.size[1])
            elif axis_3d == 2: return (self.pos, self.pos)
        elif self.axis == 'y':
            if axis_3d == 0: return (0, self.size[0])
            elif axis_3d == 1: return (self.pos, self.pos)
            elif axis_3d == 2: return (0, self.size[1])
        elif self.axis == 'x':
            if axis_3d == 0: return (self.pos, self.pos)
            elif axis_3d == 1: return (0, self.size[0])
            elif axis_3d == 2: return (0, self.size[1])

cigvis/vispynodes/axis_aligned_image.py
JintaoLee-Roger/cigvis
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright (c) 2023, modified by Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC)
#
#
# Copyright (C) 2019 Yunzhi Shi @ The University of Texas at Austin.
# All rights reserved.
# Distributed under the MIT License. See LICENSE for more info.
# ------------------------------------------------------------------------------

import io
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from pathlib import Path

from matplotlib.colors import LinearSegmentedColormap, ListedColormap
from vispy import scene
import vispy.color as viscolor
from vispy.util.dpi import get_dpi
from vispy.visuals.transforms import MatrixTransform

from cigvis import colormap


class Colorbar(scene.visuals.Image):
    """
    A colorbar visual fixed to the right side of the canvas. This is
    based on the rendering from Matplotlib, then display this rendered
    image as a scene.visuals.Image visual node on the canvas.

    Parameters
    ----------
    size : Tuple
        cbar image size: (width, hight)
    cmap : Colormap
        cmap
    clim : List
        [vmin, vmax] of the cmap
    discrete : bool
        set as discrete colorbar style if True
    disc_ticks : List
        Two elements, [values, ticklabels]
        The first element is a List which contains the avilable values,
        it will be used to calculate the colors from cmap and clim, i.e,
        get: values <--> colors. The second element is a List which contains
        the labels of all values, such as 'class A', 'class B', .... If 
        `len(disc_ticks) == 1`, use values as the second element.
    dpi_scale : float
        dpi scale when drawing, default is 1.5, true_dpi = dpi * dpi_scale
    label_str : str
        Colorbar label
    label_color : str
        colorbar label color
    label_size : int
        colorbar label size
    tick_size : int
        tick size
    border_width : float
        border size
    border_color : Color
        border color
    savedir : str
        colobar image save dir
    visable : bool
        visable when show
    parent : Any
        parent of this None
    """

    def __init__(
            self,
            size=None,  # width, hight
            cmap='gray',
            clim=None,
            discrete=False,
            disc_ticks=None,
            dpi_scale=1.5,
            label_str="Colorbar",
            label_color='black',
            label_size=None,
            tick_size=None,
            border_width=None,
            border_color='black',
            savedir=None,
            visible=True,
            parent=None):

        assert clim is not None, 'clim must be specified explicitly.'

        # Create a scene.visuals.Image (without parent by default).
        scene.visuals.Image.__init__(self,
                                     parent=parent,
                                     interpolation='bicubic',
                                     method='auto')
        self.unfreeze()
        self.savedir = savedir
        self.cbar_name = None

        self.visible = visible
        self.dpi_scale = dpi_scale

        # Record the important drawing parameters.
        self.cbar_size = size  # tuple
        self.cmap_ = cmap
        self.discrete = discrete
        if self.discrete:
            assert disc_ticks is not None
        self.disc_ticks = disc_ticks
        self.clim_ = clim  # tuple

        # Record the styling parameters.
        self.label_str = label_str
        self.label_color = label_color
        self.label_size = label_size
        self.tick_size = tick_size
        self.border_width = border_width
        self.border_color = border_color

        # Draw colorbar using Matplotlib.
        if self.cbar_size is not None:
            self.set_data(self._draw_colorbar())

        # Give a Matrix transform to self in order to move around canvas.
        self.transform = MatrixTransform()

        self.freeze()

    def update_size(self, size):
        self.unfreeze()
        self.cbar_size = size
        self.set_data(self._draw_colorbar())
        self.freeze()

    def update_params(self, **kwargs):
        self.unfreeze()
        self.savedir = kwargs.get('savedir', self.savedir)
        self.cbar_name = kwargs.get('cbar_name', self.cbar_name)
        self.cbar_size = kwargs.get('cbar_size', self.cbar_size)
        self.cmap_ = kwargs.get('cmap', self.cmap_)
        self.discrete = kwargs.get('discrete', self.discrete)
        self.disc_ticks = kwargs.get('disc_ticks', self.disc_ticks)
        self.dpi_scale = kwargs.get('dpi_scale', self.dpi_scale)
        if self.discrete:
            assert self.disc_ticks is not None
        self.clim_ = kwargs.get('clim', self.clim_)
        self.label_str = kwargs.get('label_str', self.label_str)
        self.label_color = kwargs.get('label_color', self.label_color)
        self.label_size = kwargs.get('label_size', self.label_size)
        self.tick_size = kwargs.get('tick_size', self.tick_size)
        self.border_width = kwargs.get('border_width', self.border_width)
        self.border_color = kwargs.get('border_color', self.border_color)
        self.set_data(self._draw_colorbar())
        self.freeze()

    def _draw_colorbar(self):
        """
        Draw a Matplotlib colorbar, save this figure without any boundary to a
        rendering buffer, and return this buffer as a numpy array.
        """
        assert self.cbar_size is not None
        dpi = get_dpi()
        figsize = (self.cbar_size[0] / dpi,
                   self.cbar_size[1] / dpi*0.95)

        sm, ticks = self.get_ScalarMappable()

        # Put the colorbar at proper location on the Matplotlib fig.
        fig, ax = plt.subplots(figsize=figsize)
        cb = fig.colorbar(sm, cax=ax)
        if self.discrete:
            cb.set_ticks(ticks['ticks'])
            cb.set_ticklabels(ticks['labels'])

        # Apply styling to the colorbar.
        cb.set_label(
            self.label_str,
            color=self.label_color,
            size=self.label_size,
        )
        plt.setp(plt.getp(cb.ax.axes, 'yticklabels'), color=self.label_color)
        cb.ax.yaxis.set_tick_params(
            color=self.label_color,
            labelsize=self.tick_size,
        )
        cb.outline.set_linewidth(self.border_width)
        cb.outline.set_edgecolor(self.border_color)

        plt.tight_layout()

        # Export the rendering to a numpy array in the buffer.
        buf = io.BytesIO()
        # To remove the white gap between the image and the cbar
        # set `bbox_inches='tight'`
        fig.savefig(
            buf,
            format='png',
            bbox_inches='tight',
            pad_inches=0.02,
            dpi=dpi * self.dpi_scale,
            transparent=True,
        )

        if self.cbar_name is not None:
            fig.savefig(
                Path(self.savedir) / self.cbar_name,
                format='png',
                bbox_inches='tight',
                pad_inches=0.02,
                dpi=dpi * 4,
                transparent=True,
            )

        plt.close()

        buf.seek(0)
        img = plt.imread(buf, 'png')
        buf.flush()

        return img

    def get_ScalarMappable(self):
        # To matplotlib cmap
        if isinstance(self.cmap_, viscolor.Colormap):
            rgba = self.cmap_.colors.rgba
            # Blend to white to avoid this Matplotlib rendering issue:
            # https://github.com/matplotlib/matplotlib/issues/1188
            for i in range(3):
                rgba[:, i] = (1 - rgba[:, -1]) + rgba[:, -1] * rgba[:, i]
            rgba[:, -1] = 1.
            if len(rgba) < 2:  # in special case of 'grays' cmap!
                rgba = np.array([[0, 0, 0, 1.], [1, 1, 1, 1.]])
            cmap = LinearSegmentedColormap.from_list('vispy_cmap', rgba)
        elif isinstance(self.cmap_, str):
            cmap = colormap.get_cmap_from_str(self.cmap_)
        elif isinstance(self.cmap_, mpl.colors.Colormap):
            cmap = self.cmap_
        else:
            raise RuntimeError("error type of cmap")

        if not self.discrete:
            norm = mpl.colors.Normalize(vmin=self.clim_[0], vmax=self.clim_[1])
            ticks = None
        else:
            ticks = {}
            # get the correspanding colors from cmap, and clim
            colors = colormap.get_colors_from_cmap(cmap, self.clim_,
                                                   self.disc_ticks[0])
            cmap = ListedColormap(colors)
            # norm of equal intervals
            norm = mpl.colors.BoundaryNorm(
                np.arange(0, (len(colors) + 1) * 2, 2), cmap.N)
            # set ticks in the center of boundaries
            ticks['ticks'] = np.arange(1, len(colors) * 2, 2)
            ticks['labels'] = self.disc_ticks[0]
            if len(self.disc_ticks) == 2:
                assert len(self.disc_ticks[0]) == len(self.disc_ticks[1])
                ticks['labels'] = self.disc_ticks[1]

        return plt.cm.ScalarMappable(cmap=cmap, norm=norm), ticks

cigvis/vispynodes/colorbar.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.

from typing import List, Tuple
import numpy as np
from types import SimpleNamespace
from vispy import scene
from vispy.visuals.transforms import MatrixTransform
from cigvis import is_line_first


class FixedImage(scene.visuals.Image):
    """
    fixed location image, input slices, instead of volume, disable
    drag mode

    TODO: tilted placement

    Parameters
    -----------
    imgs : array-like or List[array-like]
        input images
    axis : str
        one of ['x', 'y', 'z']
    pos : int or List or Tuple
        position of the image, can be int number or Tuple,
        pos=10, axis='z' => start_pos = [0, 0, 10],
        pos=[90, 10, 20] => start_pos = [90, 10, 20]
    cmaps : List
        cmap for each image
    clims : List
        clim for each image
    interpolations : List
        interpolation method for each image
    method : str
        method of `Image`
    """

    def __init__(self,
                 imgs,
                 axis='z',
                 pos=0,
                 cmaps=['grays'],
                 clims=None,
                 interpolations=['linear'],
                 method='auto'):
        assert clims is not None, 'clim must be specified explicitly.'
        if not isinstance(imgs, List):
            imgs = [imgs]
        if not isinstance(cmaps, List):
            cmaps = [cmaps]
        if not isinstance(interpolations, List):
            interpolations = [interpolations]
        if not isinstance(clims[0], List):
            clims = [clims]
        if is_line_first():
            imgs = [img.T for img in imgs]

        scene.visuals.Image.__init__(
            self,
            parent=None,  # no image func yet
            cmap=cmaps[0],
            clim=clims[0],
            interpolation=interpolations[0],
            method=method)

        self.unfreeze()
        self.imgs = imgs
        self.interactive = True

        # Other images ...
        self.overlaid_images = [self]
        for i_img in range(1, len(imgs)):
            overlaid_image = scene.visuals.Image(
                parent=self,
                cmap=cmaps[i_img],
                clim=clims[i_img],
                interpolation=interpolations[i_img],
                method=method)
            self.overlaid_images.append(overlaid_image)

        # Set GL state. Must check depth test, otherwise weird in 3D.
        self.set_gl_state(depth_test=True,
                          depth_func='lequal',
                          blend_func=('src_alpha', 'one_minus_src_alpha'))

        assert axis in ['x', 'y', 'z']
        self.axis = axis

        if isinstance(pos, int):
            if axis == 'x':
                self.pos = (pos, 0, 0)
            elif axis == 'y':
                self.pos = (0, pos, 0)
            else:
                self.pos = (0, 0, pos)
        elif isinstance(pos, (List, Tuple)):
            assert len(pos) == 3
            self.pos = pos
        else:
            raise TypeError("pos must be a int or 3 elements List/Tuple")

        self.highlight = SimpleNamespace()
        self.highlight.visable = False

        # Apply SRT transform according to the axis attribute.
        self.transform = MatrixTransform()
        # Move the image plane to the corresponding location.
        self._update_location()

        self.freeze()

    @property
    def axis(self):
        """
        The dimension that this image is perpendicular aligned to.
        """
        return self._axis

    @axis.setter
    def axis(self, value):
        value = value.lower()
        if value not in ('z', 'y', 'x'):
            raise ValueError('Invalid value for axis.')
        self._axis = value

    def _update_location(self):
        # self.pos = int(np.round(self.pos))

        # Update the transformation in order to move to new location.
        self.transform.reset()
        if self.axis == 'z':
            # 1. No rotation to do for z axis (y-x) slice. Only translate.
            # self.transform.translate((0, 0, self.pos))
            self.transform.translate(self.pos)
        elif self.axis == 'y':
            # 2. Rotation(s) for the y axis (z-x) slice, then translate:
            self.transform.rotate(90, (1, 0, 0))
            # self.transform.translate((0, self.pos, 0))
            self.transform.translate(self.pos)
        elif self.axis == 'x':
            # 3. Rotation(s) for the x axis (z-y) slice, then translate:
            self.transform.rotate(90, (1, 0, 0))
            self.transform.rotate(90, (0, 0, 1))
            # self.transform.translate((self.pos, 0, 0))
            self.transform.translate(self.pos)

        self.set_data(self.imgs[0])  # remove .T
        # Other images, overlaid on the primary image:
        for i_img in range(1, len(self.imgs)):
            self.overlaid_images[i_img].set_data(self.imgs[i_img])

    def _compute_bounds(self, axis_3d, view):
        """
        Overwrite the original 2D bounds of the Image class. This will correct 
        the automatic range setting for the camera in the scene canvas. In the
        original Image class, the code assumes that the image always lies in x-y
        plane; here we generalize that to x-z and y-z plane.
        
        Parameters
        ----------
        axis_3d: int in {0, 1, 2}, represents the axis in 3D view box.
        view: the ViewBox object that connects to the parent.

        The function returns a tuple (low_bounds, high_bounds) that represents
        the spatial limits of self obj in the 3D scene.
        """
        # Note: self.size[0] is slow dim size, self.size[1] is fast dim size.
        if self.axis == 'z':
            if axis_3d == 0: return (0, self.size[0])
            elif axis_3d == 1: return (0, self.size[1])
            elif axis_3d == 2: return (self.pos[2], self.pos[2])
        elif self.axis == 'y':
            if axis_3d == 0: return (0, self.size[0])
            elif axis_3d == 1: return (self.pos[1], self.pos[1])
            elif axis_3d == 2: return (0, self.size[1])
        elif self.axis == 'x':
            if axis_3d == 0: return (self.pos[0], self.pos[0])
            elif axis_3d == 1: return (0, self.size[0])
            elif axis_3d == 2: return (0, self.size[1])

cigvis/vispynodes/fixed_image.py
JintaoLee-Roger/cigvis
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright (c) 2023, modified by Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC)
#
#
# Copyright (C) 2019 Yunzhi Shi @ The University of Texas at Austin.
# All rights reserved.
# Distributed under the MIT License. See LICENSE for more info.
# -----------------------------------------------------------------------------

from typing import Dict, List, Tuple, Union
import vispy
from vispy.util import keys
from vispy.gloo.util import _screenshot
from vispy.visuals import MeshVisual, CompoundVisual
from vispy import scene

import cigvis
from .xyz_axis import XYZAxis
from .axis_aligned_image import AxisAlignedImage
from .colorbar import Colorbar


class VisCanvas(scene.SceneCanvas):
    """
    A canvas that automatically draw all contents in a 3D seismic
    visualization scene, which may include 3D seismic volume slices, axis
    legend, colorbar, etc.

    Parameters
    ----------
    size : Tuple
        canvas size
    visual_nodes : Union[List, Dict]
        nodes, can be a List like: [mesh1, mesh2, ...] (for a single widget), 
        or [[mesh1, mesh2, ...], [image1, mesh3, ...], ...] 
        (must input 'grid'). It also can be a Dict like: 
        {'0,0': [mesh1, img1, ...], '0,1': [mesh2, ...]}. Its keys represent
        the location in the grid
    grid : Tuple
        a 2D Tuple, (nrows, ncols)
    share : bool
        whether link all cameras when 'grid' is not None
    bgcolor : str or Color
        background color
    cbar_region_ratio : float
        colorbar region ration, i.e., (width*cbar_region_ratio, hight)
    scale_factor : float
        camera scale factor
    center : Tuple
        center position, default is None
    fov : float
        camera fov
    azimuth : float
        camera azimuth
    elevation : float
        camera elevation
    zoom_factor : float
        camera zoom factor
    axis_scales : Tuple
        axis scale, default is (1, 1, 1)
    auto_range : bool
        default is True
    
    savedir : str
        the dir to save sreenshot when press <s>
    title : str
        canvas title name, which is also used as the save screenshot name 
    """

    def __init__(
        self,
        size: Tuple = (800, 720),
        visual_nodes: Union[List, Dict] = [],
        grid: Tuple = None,
        share: bool = False,
        bgcolor: str = 'white',
        cbar_region_ratio: float = 0.125,

        # for camera
        scale_factor: float = None,
        center=None,
        fov: float = 45,
        azimuth: float = 50,
        elevation: float = 50,
        zoom_factor: float = 1.0,
        axis_scales: Tuple = (1.0, 1.0, 1.0),
        auto_range: bool = True,

        # for save
        savedir: str = './',
        title: str = 'Seismic3D',
    ):

        self.pngDir = savedir

        # Create a SceneCanvas obj and unfreeze it so we can add more
        # attributes inside.
        scene.SceneCanvas.__init__(self,
                                   title=title,
                                   keys='interactive',
                                   size=size,
                                   bgcolor=bgcolor)
        self.unfreeze()
        self.nodes = {}

        self.init_size = size
        self.cbar_ratio = cbar_region_ratio
        self.fov = fov
        self.azimuth = azimuth
        self.elevation = elevation
        self.scale_factor = scale_factor
        self.center = center
        self.auto_range = auto_range
        self.zoom_factor = zoom_factor
        self.share = share

        axis_scales = list(axis_scales)
        for i, r in enumerate(cigvis.is_axis_reversed()):
            axis_scales[i] *= (1 - 2 * r)
        self.axis_scales = axis_scales

        self._init_grid_input(grid, visual_nodes)

        self.add_visual_nodes()

        if self.share:
            self.link_cameras()

        # Attach a ViewBox to a grid and initiate the camera with the given
        # parameters.
        if not (auto_range or (scale_factor and center)):
            raise ValueError("scale_factor and center cannot be None" +
                             " when auto_range=False")

        # Manage the selected visual node.
        self.drag_mode = False
        self.selected = None  # no selection by default
        self.hover_on = None  # visual node that mouse hovers on, None by default
        self.selected2 = []

        if not self.share:
            for view, nodes in zip(self.view, self.nodes.values()):
                self._attach_light(view, nodes)
        else:
            # pass
            # for view in self.view:
            self._attach_light_share(self.view[-1], self.nodes)

        self.freeze()

    def on_mouse_press(self, event):
        # Hold <Alt> and click left to print position
        if keys.ALT in event.modifiers:
            # TODO
            pass

        # Hold <Ctrl> to enter drag mode or press <d> to toggle.
        if keys.CONTROL in event.modifiers or self.drag_mode:
            # Temporarily disable the interactive flag of the ViewBox because it
            # is masking all the visuals. See details at:
            # https://github.com/vispy/vispy/issues/1336
            for view in self.view:
                view.interactive = False
            hover_on = self.visual_at(event.pos)

            if event.button == 1 and self.selected is None:
                # If no previous selection, make a new selection if cilck on a valid
                # visual node, and highlight this node.
                if self._check_drag(hover_on):
                    self.selected = hover_on
                    if self.share:
                        self._get_selected2(self.selected)
                    self.selected.highlight.visible = True
                    # Set the anchor point on this node.
                    self.selected.set_anchor(event)

                # Nothing to do if the cursor is NOT on a valid visual node.

            # Reenable the ViewBox interactive flag.
            for view in self.view:
                view.interactive = True

    def on_mouse_release(self, event):
        # Hold <Ctrl> to enter drag mode or press <d> to toggle.
        if keys.CONTROL in event.modifiers or self.drag_mode:
            if self.selected is not None:
                # Erase the anchor point on this node.
                self.selected.anchor = None
                # Then, deselect any previous selection.
                self.selected = None
                self.selected2 = []

    def on_mouse_move(self, event):
        # Hold <Ctrl> to enter drag mode or press <d> to toggle.
        if keys.CONTROL in event.modifiers or self.drag_mode:
            # Temporarily disable the interactive flag of the ViewBox because it
            # is masking all the visuals. See details at:
            # https://github.com/vispy/vispy/issues/1336
            for view in self.view:
                view.interactive = False
            hover_on = self.visual_at(event.pos)

            if event.button == 1:
                # if self.selected is not None:
                if self._check_drag(self.selected):
                    self.selected.drag_visual_node(event)
                    for node in self.selected2:
                        if isinstance(node, XYZAxis):
                            node._update_axis(self.selected.loc)
                        else:
                            node._update_location(self.selected.pos)
            else:
                # If the left cilck is released, update highlight to the new visual
                # node that mouse hovers on.
                if hover_on != self.hover_on:
                    # de-highlight previous hover_on
                    if self._check_drag(self.hover_on):
                        self.hover_on.highlight.visible = False
                    self.hover_on = hover_on
                    # highlight the new hover_on
                    if self._check_drag(self.hover_on):
                        self.hover_on.highlight.visible = True

            # Reenable the ViewBox interactive flag.
            for view in self.view:
                view.interactive = True

    def on_key_press(self, event):
        # Press <Space> to reset camera.
        if event.text == ' ':
            for view in self.view:
                view.camera.fov = self.fov
                view.camera.azimuth = self.azimuth
                view.camera.elevation = self.elevation
                view.camera.set_range()
                view.camera.scale_factor = self.scale_factor
                view.camera.scale_factor /= self.zoom_factor

                view.camera._flip_factors = self.axis_scales
                view.camera._update_camera_pos()

                for child in view.children:
                    if isinstance(child, XYZAxis):
                        child._update_axis()

        # Press <s> to save a screenshot.
        if event.text == 's':
            screenshot = _screenshot()
            # screenshot = self.render()
            vispy.io.write_png(self.pngDir + self.title + '.png', screenshot)

        # Press <d> to toggle drag mode.
        if event.text == 'd':
            if not self.drag_mode:
                self.drag_mode = True
                for view in self.view:
                    view.camera.viewbox.events.mouse_move.disconnect(
                        view.camera.viewbox_mouse_event)
            else:
                self.drag_mode = False
                self._exit_drag_mode()
                for view in self.view:
                    view.camera.viewbox.events.mouse_move.connect(
                        view.camera.viewbox_mouse_event)

        # Press <a> to get the parameters of all visual nodes.
        if event.text == 'a':
            print("===== All useful parameters ====")
            # Canvas size.
            print("Canvas size = {}".format(self.size))
            # Collect camera parameters.
            print("Camera:")
            camera_state = self.view[0].camera.get_state()
            for key, value in camera_state.items():
                print(" - {} = {}".format(key, value))
            print(" - {} = {}".format('zoom factor', self.zoom_factor))

            # axis scales
            factors = list(self.view[0].camera._flip_factors)
            print(f'axes scale ratio (< 0 means axis reversed):')
            print(f' - x: {factors[0]}')
            print(f' - y: {factors[1]}')
            print(f' - z: {factors[2]}')

            # Collect slice parameters.
            print("Slices:")
            pos_dict = {'x': [], 'y': [], 'z': []}
            for node in self.view[0].scene.children:
                if self._check_drag(node):
                    pos = node.pos
                    pos_dict[node.axis].append(pos)
            for axis, pos in pos_dict.items():
                print(" - {}: {}".format(axis, pos))
            # Collect the axis legend parameters.
            for node in self.view[0].children:
                if isinstance(node, XYZAxis):
                    print("XYZAxis loc = {}".format(node.loc))

        # zoom in z axis, press <z>
        if event.text == 'z':
            for view in self.view:
                factors = list(view.camera._flip_factors)
                factors[2] += (0.2 * (1 - 2 * cigvis.is_z_reversed()))
                view.camera._flip_factors = factors
                view.camera._update_camera_pos()

                self.update()

        # zoom out z axis, press <Z>, i.e. <Shift>+<z>
        if event.text == 'Z':
            for view in self.view:
                factors = list(view.camera._flip_factors)
                factors[2] -= (0.2 * (1 - 2 * cigvis.is_z_reversed()))
                view.camera._flip_factors = factors
                view.camera._update_camera_pos()

            self.update()

        # zoom in fov, press <f>
        if event.text == 'f':
            for view in self.view:
                view.camera.fov += 5

        # zoom out fov, press <F>
        if event.text == 'F':
            for view in self.view:
                view.camera.fov -= 5

    def on_key_release(self, event):
        # Cancel selection and highlight if release <Ctrl>.
        if keys.CONTROL not in event.modifiers:
            self._exit_drag_mode()

    def _exit_drag_mode(self):
        if self._check_drag(self.hover_on):
            self.hover_on.highlight.visible = False
            self.hover_on = None
        if self._check_drag(self.selected):
            self.selected.highlight.visible = False
            self.selected.anchor = None
            self.selected = None
            self.selected2 = []

    # HACK: 直接绑定 ShadingFilter 会不会更方便
    # 使用 mesh._vshare.filters 获取 attach 的 ShadingFilter
    def _attach_light(self, view, nodes):
        light_dir = (0, -1, 0, 0)

        for node in nodes:
            if isinstance(node, MeshVisual):
                if node.shading_filter is not None:
                    node.shading_filter.light_dir = light_dir[:3]
            if isinstance(node, CompoundVisual):
                if hasattr(node, 'meshs'):
                    for mesh in node.meshs:
                        if mesh.shading_filter is not None:
                            mesh.shading_filter.light_dir = light_dir[:3]

        initial_light_dir = view.camera.transform.imap(light_dir)
        view.camera.azimuth = self.azimuth
        view.camera.elevation = self.elevation

        @view.scene.transform.changed.connect
        def on_transform_change(event):
            transform = view.camera.transform
            for node in nodes:
                if isinstance(node, MeshVisual):
                    if node.shading_filter is not None:
                        # print(transform.map(initial_light_dir))
                        node.shading_filter.light_dir = transform.map(
                            initial_light_dir)[:3]
                if isinstance(node, CompoundVisual):
                    if hasattr(node, 'meshs'):
                        for mesh in node.meshs:
                            if mesh.shading_filter is not None:
                                mesh.shading_filter.light_dir = transform.map(
                                    initial_light_dir)[:3]

    def _attach_light_share(self, view, nodess):
        light_dir = (0, -1, 0, 0)

        for nodes in nodess.values():
            for node in nodes:
                if isinstance(node, MeshVisual):
                    if node.shading_filter is not None:
                        node.shading_filter.light_dir = light_dir[:3]
                if isinstance(node, CompoundVisual):
                    if hasattr(node, 'meshs'):
                        for mesh in node.meshs:
                            if mesh.shading_filter is not None:
                                mesh.shading_filter.light_dir = light_dir[:3]
        initial_light_dir = view.camera.transform.imap(light_dir)

        view.camera.azimuth = self.azimuth
        view.camera.elevation = self.elevation

        @view.scene.transform.changed.connect
        def on_transform_change(event):
            transform = view.camera.transform
            for nodes in nodess.values():
                for node in nodes:
                    if isinstance(node, MeshVisual):
                        if node.shading_filter is not None:
                            # print(transform.map(initial_light_dir))
                            node.shading_filter.light_dir = transform.map(
                                initial_light_dir)[:3]
                    if isinstance(node, CompoundVisual):
                        if hasattr(node, 'meshs'):
                            for mesh in node.meshs:
                                if mesh.shading_filter is not None:
                                    mesh.shading_filter.light_dir = transform.map(
                                        initial_light_dir)[:3]

    def _check_drag(self, node):
        """
        Only AxisAlignedImage and XYZAxis can be drag
        """
        return isinstance(node, (AxisAlignedImage, XYZAxis))

    def _get_selected2(self, node):
        """
        uesed when self.share == true
        get the correspanding Nodes of the input node to drag
        """
        assert self._check_drag(node)
        ids = node.ids
        k = node.name[:3]

        for key in self.nodes.keys():
            if key != k:
                self.selected2 += [
                    n for n in self.nodes[key]
                    if self._check_drag(n) and n.ids == ids
                ]

    def _init_grid_input(self, grid, visual_nodes):
        """
        inital grid (get self.nrows and self.ncols),
        and convert visual_nodes to self.nodes (Dict)

        self.nodes is a dict like {'i,j': nodes}, its
        key 'i,j' represents a loctaion (i, j) in the grid
        """
        if grid is not None:
            self.nrows = grid[0]
            self.ncols = grid[1]
            assert len(visual_nodes) <= grid[0] * grid[1]
            if isinstance(visual_nodes, List):
                for i, nodes in enumerate(visual_nodes):
                    assert isinstance(nodes, List)
                    r = i // grid[1]
                    c = i - r * grid[1]
                    key = f'{r},{c}'
                    self.nodes[key] = nodes
            elif isinstance(visual_nodes, Dict):
                ks = list(visual_nodes.keys())
                for k in ks:
                    r, c = [int(i) for i in k.split(',')]
                    if not (r >= 0 and r < grid[0] and c >= 0 and c < grid[1]):
                        raise KeyError(
                            f'visual_nodes key: {ks} is out bound of grid: {grid}'
                        )
                self.nodes = visual_nodes
            else:
                raise ValueError('Invalid type of visual_nodes')
        else:
            self.nrows = 1
            self.ncols = 1
            if isinstance(visual_nodes[0], List):
                self.nodes['0,0'] = visual_nodes[0]
            elif isinstance(visual_nodes, Dict):
                self.nodes = visual_nodes
            else:
                self.nodes['0,0'] = visual_nodes

    def add_visual_nodes(self):
        """
        Add all visual nodes
        """
        # Create a Grid widget on the canvas to host separate Viewbox (e.g.,
        # 3D image on the left panel and colorbar to the right).
        grid = self.central_widget.add_grid()
        N = len(self.nodes)
        w, h = self.init_size
        self.view = []

        for i in range(self.nrows):
            for j in range(self.ncols):
                k = f'{i},{j}'
                if k not in self.nodes.keys():
                    continue

                widget = grid.add_widget(row=i,
                                         col=j,
                                         size=(w / self.ncols, h / self.nrows))
                cgrid = widget.add_grid()
                view = cgrid.add_view(row=0, col=0)
                view = self._add_nodes(view, self.nodes[k], k)
                self.view.append(view)

                # TODO: change view2.width_max when resize
                for node in self.nodes[k]:
                    if isinstance(node, Colorbar):
                        view2 = cgrid.add_view(row=0, col=1)
                        view2.width_max = self.cbar_ratio * cgrid.width
                        self._add_colorbar(view2, node)

    def _add_nodes(self, view, nodes, k: str):
        """
        add nodes to a viewbox
        """
        # set azimuth=0 and elevation=0, for convenient lighting
        # change them in self._attach_light() function
        view.camera = scene.cameras.TurntableCamera(
            # self.camera = scene.cameras.ArcballCamera(
            scale_factor=self.scale_factor,
            center=self.center,
            fov=self.fov,
            azimuth=0,
            elevation=0,
        )

        view.camera._flip_factors = self.axis_scales.copy()

        # Attach all main visual nodes (e.g. slices, meshs, volumes) to the ViewBox.
        for i, node in enumerate(nodes):
            if not isinstance(node, Colorbar):
                node.name = k + f'-{i}'
                view.add(node)

            if isinstance(node, XYZAxis):
                # Set the parent to view, instead of view.scene,
                # so that this legend will stay at its location on
                # the canvas, and won't rotate away.
                node.parent = view
                node.canvas_size = self.size
                self.events.resize.connect(node.on_resize)
                node.highlight.parent = view
                node._update_axis()
                self.events.mouse_move.connect(node.on_mouse_move)

        if self.auto_range:
            view.camera.set_range()

        if self.scale_factor is None:
            self.scale_factor = view.camera.scale_factor

        view.camera.scale_factor /= self.zoom_factor

        return view

    def _add_colorbar(self, view, colorbar):
        """
        Create a secondary ViewBox to host the Colorbar visual.
        Make it solid background, image from primary ViewBox shall be
        blocked if overlapping.
        """
        view.interactive = True  # disable so that it won't be selectable

        # use PanZoomCamera so we can draw a **high qaulity** colorbar image
        view.camera = scene.cameras.PanZoomCamera(aspect=1)
        # disable mouse drag, keep zoom in/out
        view.camera.viewbox.events.mouse_move.disconnect(
            view.camera.viewbox_mouse_event)
        colorbar.parent = view.scene
        view.camera.flip = (0, 1, 0)
        view.camera.set_range()

    def link_cameras(self):
        """
        link all cameras
        """

        for view in self.view[:-1]:
            view.camera.link(self.view[-1].camera)

cigvis/vispynodes/vis_canvas.py
JintaoLee-Roger/cigvis
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright (c) 2023, modified by Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC)
#
# Copyright (C) 2019 Yunzhi Shi @ The University of Texas at Austin.
# All rights reserved.
# Distributed under the MIT License. See LICENSE for more info.
# -----------------------------------------------------------------------------

from typing import List, Callable, Tuple
import numpy as np
from vispy.color import Colormap
from cigvis import is_line_first

from .axis_aligned_image import AxisAlignedImage


def volume_slices(volumes: np.ndarray or List,
                  x_pos: List or int = None,
                  y_pos: List or int = None,
                  z_pos: List or int = None,
                  preproc_funcs: Callable = None,
                  cmaps: str or Colormap or List = 'grays',
                  clims: List or Tuple = None,
                  interpolation: str = 'linear',
                  method: str = 'auto') -> List[AxisAlignedImage]:
    """ 
    Acquire a list of slices in the form of AxisAlignedImage.
    The list can be attached to a VisCanvas to visualize the volume
    in 3D interactively.

    Parameters
    ----------
    volumes : np.ndarray or List[np.ndarray]
        input 3D volumes
    x_pos : List or int
        x postions
    y_pos : List or int
        y postions
    z_pos : List or int
        z positions
    preproc_funcs : Callable[[np.ndarray], np.ndarray]
        A function to preprocess a slice
    cmaps : List[str or vispy.color.Colormap]
        colormaps
    clims : List or Tuple
        [vmin, vmax] for each volume
    interpolation : str
        interpolation methods for volumes
    method : str
        Selects method of rendering image in case of non-linear transforms., 
        see: https://vispy.org/api/vispy.scene.visuals.html#vispy.scene.visuals.Image

    Returns
    -------
    visual_nodes : List[AxisAlignedImage]
        return seismic_canvas's visual_nodes
    """
    # check and init
    # Check whether single volume or multiple volumes are provided.
    if isinstance(volumes, (tuple, list)):
        n_vol = len(volumes)
        if preproc_funcs is None:
            preproc_funcs = [None] * n_vol  # repeat n times ...
        else:
            assert isinstance(preproc_funcs, (tuple, list)) \
              and len(preproc_funcs) >= n_vol
        assert isinstance(cmaps, (tuple, list)) \
          and len(cmaps) >= n_vol
        assert isinstance(clims, (tuple, list)) \
          and len(clims) >= n_vol \
          and len(clims[0]) == 2 or clims[0] is None
        for vol in volumes:
            assert vol.shape == volumes[0].shape
    else:
        volumes = [volumes]
        preproc_funcs = [preproc_funcs]
        cmaps = [cmaps]
        clims = [clims]
        n_vol = 1
    if isinstance(interpolation, str):
        interpolation = [interpolation] * len(volumes)
    assert len(interpolation) == len(volumes)

    slices_list = []

    shape = volumes[0].shape
    line_first = is_line_first()
    if not line_first:
        shape = shape[::-1]

    # Automatically set clim (cmap range) if not specified.
    for i_vol in range(n_vol):
        clim = clims[i_vol]
        vol = volumes[i_vol]
        if clim is None or clim == 'auto':
            if type(vol) == np.memmap:
                from warnings import warn
                warn(
                    "cmap='auto' with np.memmap can significantly impact launching "
                    + "time, cmap=(cmin, cmax) is recommended.",
                    UserWarning,
                    stacklevel=2)
            clims[i_vol] = (vol.min(), vol.max())

    # Function that returns the limitation of slice movement.
    def limit(axis):
        if axis == 'x': return (0, shape[0] - 1)
        elif axis == 'y': return (0, shape[1] - 1)
        elif axis == 'z': return (0, shape[2] - 1)

    # Function that returns a function that provides the slice image at
    # specified slicing position.
    def get_image_func(axis, i_vol):
        """
        Parameters
        ----------
        axis : str
            'x' or 'y' or 'z'
        i_vol : int
            index of the volumes
        """

        def slicing_at_axis(pos, get_shape=False):
            if get_shape:  # just return the shape information
                if axis == 'x': return shape[1], shape[2]
                elif axis == 'y': return shape[0], shape[2]
                elif axis == 'z': return shape[0], shape[1]
            else:  # will slice the volume and return an np array image
                pos = int(np.round(pos))
                vol = volumes[i_vol]
                preproc_f = preproc_funcs[i_vol]
                if preproc_f is not None:
                    if line_first:
                        if axis == 'x': return preproc_f(vol[pos, :, :].T)
                        elif axis == 'y': return preproc_f(vol[:, pos, :].T)
                        elif axis == 'z': return preproc_f(vol[:, :, pos].T)
                    else:
                        if axis == 'x': return preproc_f(vol[:, :, pos])
                        elif axis == 'y': return preproc_f(vol[:, pos, :])
                        elif axis == 'z': return preproc_f(vol[pos, :, :])
                else:
                    if line_first:
                        if axis == 'x': return vol[pos, :, :].T
                        elif axis == 'y': return vol[:, pos, :].T
                        elif axis == 'z': return vol[:, :, pos].T
                    else:
                        if axis == 'x': return vol[:, :, pos]
                        elif axis == 'y': return vol[:, pos, :]
                        elif axis == 'z': return vol[pos, :, :]

        return slicing_at_axis

    # Organize the slice positions.
    for xyz_pos in (x_pos, y_pos, z_pos):
        if not (isinstance(xyz_pos,
                           (list, tuple, int, float)) or xyz_pos is None):
            raise ValueError(
                'Wrong type of x_pos/y_pos/z_pos={}'.format(xyz_pos))
    axis_slices = {'x': x_pos, 'y': y_pos, 'z': z_pos}

    # Create AxisAlignedImage nodes and append to the slices_list.
    for axis, pos_list in axis_slices.items():
        if pos_list is not None:
            if isinstance(pos_list, (int, float)):
                # make it iterable, even only one element
                pos_list = [pos_list]
            for pos in pos_list:
                pos = int(np.round(pos))

                # Generate a list of image funcs for each input volume.
                image_funcs = []
                for i_vol in range(n_vol):
                    image_funcs.append(get_image_func(axis, i_vol))

                # Construct the AxisAlignedImage node.
                image_node = AxisAlignedImage(image_funcs,
                                              axis=axis,
                                              pos=pos,
                                              limit=limit(axis),
                                              cmaps=cmaps,
                                              clims=clims,
                                              interpolation=interpolation,
                                              method=method)

                slices_list.append(image_node)

    return slices_list

cigvis/vispynodes/volume_slices.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
This file is modified from vispy's TubeVisual

Create a tube like well log
"""

from typing import List
from vispy.visuals.mesh import MeshVisual
from vispy.scene.visuals import Compound
import numpy as np
from numpy.linalg import norm
from vispy.util.transforms import rotate

import collections


class WellLog(Compound):
    """
    Well log Mesh, the trajectory of the well log is shown as 
    a tube (with `radius`). The first curve is shown as the color of
    the tube, and the other curves are shown as faces attach to the tube

    Parameters
    ----------
    points : array-like
        the trajectory of the well log, shape is like (N, 3)
    radius : int or List
        radius of the tube and the curves width
    colors : array-like
        shape is like (N, 4) (one curve, colors for the tube), or 
        (m, N, 4) (m curves)
    index : List
        points index of each curve attached to
    tube_points : int
        the number of points to represent a circle
    mode : str
        default is 'triangles'
    """

    def __init__(self,
                 points,
                 radius,
                 colors,
                 index=None,
                 tube_points=16,
                 mode='triangles'):
        assert tube_points > 2
        assert points.ndim == 2 and points.shape[1] == 3

        # make sure we are working with floats
        points = np.array(points).astype(float)
        radius = self._process_radius(radius, len(points))
        assert len(radius) < tube_points

        if colors.ndim == 2:
            colors = colors[np.newaxis, ...]
        assert colors.shape == (len(radius), len(points), 4)

        if index is None and len(radius) > 1:
            n = len(radius) - 1
            index = np.arange(0, tube_points, tube_points // n)
        if index is not None:
            index = np.array(index)[:len(radius) - 1]
            assert max(index) < tube_points and min(index) >= 0

        tangents, normals, binormals = _frenet_frames(points, False)

        # tube mesh
        tube_vertices, tube_indices = make_triangle_tube(
            points, radius[0], tube_points, normals, binormals)
        tube_colors = colors[0, ...]
        tube_colors = np.repeat(tube_colors, tube_points, axis=0)

        tube = MeshVisual(tube_vertices,
                          tube_indices,
                          vertex_colors=tube_colors,
                          shading='smooth',
                          mode=mode)

        line_facemesh = []
        # line face mesh
        for i in range(len(radius) - 1):
            vertices = tube_vertices[index[i]::tube_points]
            assert len(vertices) == len(points)
            line_vertices, line_indieces = make_triangle_line(
                points, radius[i + 1], tube_points, index[i], vertices,
                normals, binormals)
            line_colors = np.vstack([colors[i + 1, ...], colors[i + 1, ...]])
            # line_colors = ColorArray(line_colors)

            # don't add shading
            line_facemesh.append(
                MeshVisual(line_vertices,
                           line_indieces,
                           vertex_colors=line_colors,
                           shading=None,
                           mode=mode))

        self.meshs = [tube] + line_facemesh
        Compound.__init__(self, self.meshs)

    def _process_radius(self, radius, N):
        if not isinstance(radius, List):
            radius = [radius]

        for i in range(len(radius)):
            # if single radius, convert to list of radii
            if not isinstance(radius[i], collections.abc.Iterable):
                radius[i] = [radius[i]] * N
            elif len(radius[i]) != N:
                raise ValueError(
                    f'Length of radii list must match points. Error index: {i}'
                )

        return radius


def make_triangle_tube(points: np.ndarray,
                       radius,
                       tube_points: int,
                       normals=None,
                       binormals=None):
    """
    make triangle tube, return vertices and faces
    """
    assert points.shape[1] >= 3

    points = points[:, :3]

    if normals is None or binormals is None:
        tangents, normals, binormals = _frenet_frames(points, False)
    segments = len(points) - 1

    if not isinstance(radius, (float, int)):
        radius = np.array(radius).reshape(1, -1, 1)
    points = points[np.newaxis, ...]
    normals = normals[np.newaxis, ...]
    binormals = binormals[np.newaxis, ...]
    vertices = np.empty((tube_points, len(points), 3))
    angles = np.arange(tube_points) / tube_points * 2 * np.pi
    angles = angles.reshape(-1, 1, 1)
    vertices = points + -1. * radius * np.cos(angles) * normals + \
        radius * np.sin(angles) * binormals
    vertices = vertices.transpose(1, 0, 2)
    vertices = vertices.reshape(-1, 3)

    # construct the mesh
    indices = np.empty((segments * 2 * tube_points, 3), dtype=int)
    i, j = np.meshgrid(np.arange(segments),
                       np.arange(tube_points),
                       indexing='ij')
    i = i.flatten()
    j = j.flatten()
    jp = (j + 1) % tube_points
    indices[0::2, :] = np.c_[i * tube_points + j, (i + 1) * tube_points + j,
                             i * tube_points + jp]
    indices[1::2, :] = np.c_[(i + 1) * tube_points + j,
                             (i + 1) * tube_points + jp, i * tube_points + jp]

    # closed
    exdices = []
    for start in [0, len(vertices) - tube_points]:
        for i in range(tube_points - 2):
            j = start + i
            exdices.append([start, j + 1, j + 2])
    indices = np.vstack([indices, np.array(exdices)])

    return vertices, indices


def make_triangle_line(points,
                       radius,
                       tube_points,
                       vidx,
                       vertices,
                       normals=None,
                       binormals=None):
    """
    make triangle curve, return vertices and faces
    """

    points = points[:, :3]

    if normals is None or binormals is None:
        tangents, normals, binormals = _frenet_frames(points, False)
    segments = len(points) - 1

    # get the positions of each vertex
    radius = radius[:, np.newaxis]
    angles = vidx / tube_points * 2 * np.pi
    grid = points + -1. * radius * np.cos(angles) * normals + \
        radius * np.sin(angles) * binormals

    start = len(vertices)
    vertices = np.vstack([vertices, grid])

    # each point has two face:
    # [[start + i, i, i + 1], [start + i, i + 1, start + i + 1]]
    indices = np.empty((2 * segments, 3), dtype=int)
    indices[:, 1] = np.repeat(np.arange(0, segments), 2)
    indices[:, 0] = indices[:, 1] + start
    indices[:, 2] = indices[:, 1] + 1
    indices[1::2, 1] += 1
    indices[1::2, 2] += start

    return vertices, indices


def _frenet_frames(points, closed=False):
    """
    Calculates and returns the tangents, normals and binormals for
    the tube.
    """
    tangents = np.zeros((len(points), 3))
    normals = np.zeros((len(points), 3))

    epsilon = 0.0001

    # Compute tangent vectors for each segment
    tangents = np.roll(points, -1, axis=0) - np.roll(points, 1, axis=0)
    if not closed:
        tangents[0] = points[1] - points[0]
        tangents[-1] = points[-1] - points[-2]
    mags = np.sqrt(np.sum(tangents * tangents, axis=1))
    tangents /= mags[:, np.newaxis]

    # Get initial normal and binormal
    t = np.abs(tangents[0])

    smallest = np.argmin(t)
    normal = np.zeros(3)
    normal[smallest] = 1.

    vec = np.cross(tangents[0], normal)

    normals[0] = np.cross(tangents[0], vec)

    # Compute normal and binormal vectors along the path
    for i in range(1, len(points)):
        normals[i] = normals[i - 1]

        vec = np.cross(tangents[i - 1], tangents[i])
        if norm(vec) > epsilon:
            vec /= norm(vec)
            theta = np.arccos(np.clip(tangents[i - 1].dot(tangents[i]), -1, 1))
            normals[i] = rotate(-np.degrees(theta),
                                vec)[:3, :3].dot(normals[i])

    if closed:
        theta = np.arccos(np.clip(normals[0].dot(normals[-1]), -1, 1))
        theta /= len(points) - 1

        if tangents[0].dot(np.cross(normals[0], normals[-1])) > 0:
            theta *= -1.

        for i in range(1, len(points)):
            normals[i] = rotate(-np.degrees(theta * i),
                                tangents[i])[:3, :3].dot(normals[i])

    binormals = np.cross(tangents, normals)

    return tangents, normals, binormals

cigvis/vispynodes/well_log.py
JintaoLee-Roger/cigvis
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright (C) 2019 Yunzhi Shi @ The University of Texas at Austin.
# All rights reserved.
# Distributed under the MIT License. See LICENSE for more info.
# -----------------------------------------------------------------------------

import numpy as np
import cigvis
from vispy import scene
from vispy.visuals.transforms import MatrixTransform


class XYZAxis(scene.visuals.XYZAxis):
    """
    An XYZAxis legend visual that add some interactivity fixes to be used
    in 3D. It rotates correctly, and can be dragged around.

    Parameters
    ----------
    loc : Tuple
        2D position for the location of the axis
    size : int
        size of axis
    width : int
        axis line width
    antialias : bool
        antialias of XYZAxis
    visable : bool
        visable when show
    parent : Any
        parant of this None
    """

    def __init__(self,
                 loc=(30, 30),
                 size=60,
                 width=2,
                 antialias=True,
                 visible=True,
                 parent=None):
        # Create a scene.visuals.XYZAxis (without parent by default).
        scene.visuals.XYZAxis.__init__(self,
                                       parent=parent,
                                       width=width,
                                       antialias=antialias)
        self.interactive = True
        self.unfreeze()
        self.ids = 'xyz' # for link camera move
        self.visible = visible
        self.canvas_size = None  # will be set when parent is linked

        # Determine the size and position.
        self.loc = loc
        self.size = size

        # z-axis down seismic coordinate system, or z-axis up normal system.
        xflip = -1 if cigvis.is_x_reversed() else 1
        yflip = -1 if cigvis.is_y_reversed() else 1
        zflip = -1 if cigvis.is_z_reversed() else 1
        flips = [xflip, yflip, zflip]
        self.flips = flips

        # The selection highlight (a Ellipse visual with transparent color).
        # The circle is centered on the axis legend.
        self.highlight = scene.visuals.Ellipse(
            parent=parent,
            center=self.loc,
            radius=self.size,
            color=(1, 1, 0, 0.5))  # transparent yellow color
        self.highlight.visible = False  # only show when selected

        # Set the anchor point (2D screen coordinates). The mouse will
        # drag the axis by anchor point to move around the screen.
        self.anchor = None  # None by default
        self.offset = np.array([0, 0])

        # The axis legend is rotated to align with the parent camera. Then put
        # the legend to specified location and scale up to desired size.
        # The location is computed from the top-left corner.
        self.transform = MatrixTransform()

        self.freeze()

    def on_resize(self, event):
        # When window is resized, move the node accordingly.
        loc = np.array(self.loc).astype(np.single)
        loc *= (np.array(event.size).astype(np.single) /
                np.array(self.canvas_size).astype(np.single))
        self.loc = tuple(loc)
        self._update_location()
        # Update the canvas size.
        self.canvas_size = event.size

    def on_mouse_move(self, event):
        # Make sure the axis is rotating with the camera.
        if event.button == 1 and event.is_dragging:
            self._update_axis()

    def set_anchor(self, mouse_press_event):
        """
        Set an anchor point (2D screen coordinates) when left click
        in the selection mode (<Ctrl> pressed). After that, the dragging called
        in func 'drag_visual_node' will try to move around the screen and let the
        anchor follows user's mouse position.
        """
        # Get click coordinates on the screen.
        click_pos = mouse_press_event.pos
        # Compute the anchor coordinates by subtracting the click_pos with
        # self.loc in order to get a relative anchor position.
        self.anchor = list(np.array(click_pos[:2]) - np.array(self.loc))

    def drag_visual_node(self, mouse_move_event):
        """
        Drag this visual node while holding left click in the selection mode
        (<Ctrl> pressed). The highlighted axis will move with the mouse (the anchor
        point will be 'hooked' to the mouse).
        """
        # Compute the new axis legend center via dragging.
        new_center = mouse_move_event.pos[:2] - self.anchor
        # Get the offset from new_center to current axis center self.loc.
        self.offset = new_center - self.loc

        self.highlight.center = new_center  # move highlighte together with axis
        self._update_location()

    def _update_location(self):
        """
        Update the axis plane to the dragged location and redraw.
        """
        self.loc += self.offset
        self.loc = tuple(self.loc)
        self._update_axis()

        # Reset attributes after dragging completes.
        self.offset = np.array([0, 0])

    def _update_axis(self, loc=None):
        """
        Align the axis legend with the current camera rotation, scale up to
        desired size, then put the axis legend at pos (measuring from the
        top-left corner).
        """
        if self.parent is None:
            return  # if not connected to any parent, no need to update

        if loc is not None:
            self.loc = loc

        tr = self.transform
        tr.reset()  # remove all transforms (put back to origin)

        # Revert y and z axis in seismic coordinate system.
        # if self.seismic_coord_system:
        # #     tr.scale((1, -1, -1))
        tr.scale(self.flips)

        # Align camera rotation. This only works for turntable camera!!
        # for some reason I have to do this at first ...
        tr.rotate(90, (1, 0, 0))
        tr.rotate(self.parent.camera.azimuth, (0, 1, 0))
        tr.rotate(self.parent.camera.elevation, (1, 0, 0))
        # Scale up to desired size and put to desired location.
        tr.scale((*(self.size, ) * 2, 0.001))  # only scale in screen plane!
        tr.translate(self.loc)

        self.update()

cigvis/vispynodes/xyz_axis.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
Functions for drawing 3D seismic figure using vispy
----------------------------------------------------


Note
----
Running **not** in jupyter environment

In plotly, for a seismic volume,
- x means inline order
- y means crossline order
- z means time order

- ni means the dimension size of inline / x
- nx means the dimension size of crossline / y
- nt means the dimension size of time / depth / z

"""

from typing import List, Tuple, Dict, Union
import warnings
import os
import numpy as np
from cigvis.vispynodes import (VisCanvas, volume_slices, Colorbar, WellLog,
                               XYZAxis)

from vispy.scene.visuals import Mesh, Line
import vispy
from vispy.gloo.util import _screenshot
from scipy.ndimage import gaussian_filter

import cigvis
from cigvis import colormap
from cigvis.utils import surfaceutils
from cigvis.utils import vispyutils
import cigvis.utils as utils


def create_slices(volume: np.ndarray,
                  pos: Union[List, Dict] = None,
                  clim: List = None,
                  cmap: str = 'Petrel',
                  interpolation: str = 'cubic',
                  return_cbar: bool = False,
                  **kwargs) -> List:
    """
    create a slice node

    Parameters
    ----------
    volume : array-like
        3D array
    pos : List or Dict
        init position of the slices, can be a List or Dict, such as:
        ```
        pos = [0, 0, 200] # x: 0, y: 0, z: 200
        pos = [[0, 200], [9], []] # x: 0 and 200, y: 9, z: None
        pos = {'x': [0, 200], 'y': [1], z: []}
        ```
    clim : List
        [vmin, vmax] for plotting 
    cmap : str or Colormap
        colormap, it can be str or matplotlib's Colormap or vispy's Colormap
    interpolation : str
        interpolation method. If the values of the volume is discrete, we recommand 
        set as 'nearest'
    return_cbar : bool
        return a colorbar

    kwargs : Dict
        other kwargs for `volume_slices`

    Returns
    -------
    slices_nodes : List
        list of slice nodes
    """
    utils.check_mmap(volume)
    line_first = cigvis.is_line_first()
    if line_first:
        nt = volume.shape[2]
    else:
        nt = volume.shape[0]

    # set pos
    if pos is None:
        pos = dict(x=[0], y=[0], z=[nt - 1])
    if isinstance(pos, List):
        assert len(pos) == 3
        if isinstance(pos[0], List):
            x, y, z = pos
        else:
            x, y, z = [pos[0]], [pos[1]], [pos[2]]
        pos = {'x': x, 'y': y, 'z': z}
    assert isinstance(pos, Dict)

    if clim is None:
        clim = [volume.min(), volume.max()]
    cmap = colormap.cmap_to_vispy(cmap)

    nodes = volume_slices(volume,
                          pos['x'],
                          pos['y'],
                          pos['z'],
                          cmaps=cmap,
                          clims=clim,
                          interpolation=interpolation)

    if return_cbar:
        cbar_kwargs = vispyutils.get_valid_kwargs('colorbar', **kwargs)
        cbar = create_colorbar(cmap, clim, **cbar_kwargs)
        return nodes, cbar

    return nodes


def create_overlay(bg_volume: np.ndarray,
                   fg_volume: np.ndarray,
                   pos: Union[List, Dict] = None,
                   bg_clim: List = None,
                   fg_clim: List = None,
                   bg_cmap: str = 'Petrel',
                   fg_cmap: str = None,
                   bg_interpolation: str = 'cubic',
                   fg_interpolation: str = 'cubic',
                   return_cbar: bool = False,
                   cbar_type: str = 'fg',
                   **kwargs) -> List:
    """
    create a overlied slice node

    Parameters
    ----------
    bg_volume : array-like
        3D array, background volume
    fg_volume : array-like or List
        3D array(s), foreground volume(s)
    pos : List or Dict
        init position of the slices, can be a List or Dict, such as:
        ```
        pos = [0, 0, 200] # x: 0, y: 0, z: 200
        pos = [[0, 200], [9], []] # x: 0 and 200, y: 9, z: None
        pos = {'x': [0, 200], 'y': [1], z: []}
        ```
    bg_clim : List
        [vmin, vmax] for background slices plotting
    fg_clim : List
        [vmin, vmax] for foreground slices plotting
    bg_cmap : str or Colormap
        colormap for background slices, it can be str or matplotlib's Colormap or vispy's Colormap
    fg_cmap : str or Colormap
        colormap for foreground slices, it can be str or matplotlib's Colormap or vispy's Colormap
    bg_interpolation : str
        interpolation method for background slices. 
    fg_interpolation : str
        interpolation method. If the values of the slices is discrete, we recommand 
        set as 'nearest'
    return_cbar : bool
        return a colorbar
    cbar_type : str
        'fg' for foreground colorbar, 'bg' for background colorbar
    kwargs : Dict
        other kwargs for `volume_slices`

    Returns
    -------
    slices_nodes : List
        list of slice nodes
    """
    # check
    utils.check_mmap(bg_volume)
    if not isinstance(fg_volume, List):
        fg_volume = [fg_volume]
    for volume in fg_volume:
        assert bg_volume.shape == volume.shape
        utils.check_mmap(volume)

    line_first = cigvis.is_line_first()
    if line_first:
        nt = bg_volume.shape[2]
    else:
        nt = bg_volume.shape[0]

    # set pos
    if pos is None:
        pos = dict(x=[0], y=[0], z=[nt - 1])
    if isinstance(pos, List):
        assert len(pos) == 3
        if isinstance(pos[0], List):
            x, y, z = pos
        else:
            x, y, z = [pos[0]], [pos[1]], [pos[2]]
        pos = {'x': x, 'y': y, 'z': z}
    assert isinstance(pos, Dict)

    if bg_clim is None:
        bg_clim = [bg_volume.min(), bg_volume.max()]
    if fg_clim is None:
        fg_clim = [[v.min(), v.max()] for v in fg_volume]
    if not isinstance(fg_clim[0], (List, Tuple)):
        fg_clim = [fg_clim]

    bg_cmap = colormap.cmap_to_vispy(bg_cmap)

    if fg_cmap is None:
        raise ValueError("'fg_cmap' cannot be 'None'")
    if not isinstance(fg_cmap, List):
        fg_cmap = [fg_cmap] * len(fg_volume)
    for i in range(len(fg_cmap)):
        fg_cmap[i] = colormap.cmap_to_vispy(fg_cmap[i])

    if isinstance(fg_interpolation, str):
        fg_interpolation = [fg_interpolation] * len(fg_volume)

    nodes = volume_slices([bg_volume, *fg_volume],
                          pos['x'],
                          pos['y'],
                          pos['z'],
                          cmaps=[bg_cmap, *fg_cmap],
                          clims=[bg_clim, *fg_clim],
                          interpolation=[bg_interpolation, *fg_interpolation])

    if return_cbar:
        if cbar_type == 'fg':
            cmap = fg_cmap
            clim = fg_clim
        else:
            cmap = bg_cmap
            clim = bg_clim

        kwargs = vispyutils.get_valid_kwargs('colorbar', **kwargs)

        discrete = kwargs.get('discrete', False)
        disc_ticks = kwargs.get('disc_ticks', None)

        if discrete and disc_ticks is None:
            v = np.unique(fg_volume)
            kwargs['disc_ticks'] = [v]

        cbar = create_colorbar(cmap, clim, **kwargs)
        return nodes, cbar

    return nodes


def create_colorbar(cmap,
                    clim: List,
                    discrete: bool = False,
                    disc_ticks: List or Dict = None,
                    label_str: str = '',
                    **kwargs) -> Colorbar:
    """
    create a `Colorbar` instance. To draw colorbar, must spacify 
    `size` params or call `colorbar.update_size(size)` function.

    Parameters
    ---------- 
    cmap : str
        colormap
    clim : List
        [vmin, vmax] to norm
    discrete : bool
        draw a discrete colorbar or not
    disc_ticks : List or Dict
        contains 2 elements, [values, ticklabels] or 
        {'value': values, 'labels': labels}. values are used to get colors 
        from cmap, ticklabels are the labels of colors
    label_str : str
        colorbar label

    kwargs : Dict
        params for Colorbar
    """
    if cmap is None or clim is None:
        return None
    if isinstance(cmap, str) and cmap in colormap.list_custom_cmap():
        cmap = colormap.get_custom_cmap(cmap)

    kwargs = vispyutils.get_valid_kwargs('colorbar', **kwargs)
    cbar = Colorbar(cmap=cmap,
                    clim=clim,
                    discrete=discrete,
                    disc_ticks=disc_ticks,
                    label_str=label_str,
                    **kwargs)

    return cbar


def create_surfaces(surfs: List[np.ndarray],
                    volume: np.ndarray = None,
                    value_type: str = 'depth',
                    clim: List = None,
                    cmap: str = 'jet',
                    alpha: float = 1,
                    shape: Union[Tuple, List] = None,
                    interp: bool = False,
                    return_cbar: bool = False,
                    **kwargs) -> List:
    """
    create a surfaces node

    Parameters
    ----------
    surfs : List or array-like
        the surface position, which can be an array (one surface) or 
        List (multi-surfaces). Each surf can be a (n1, n2)/(n1, n2, 2) 
        array or (N, 3)/(N, 4) array, such as
        >>> surf.shape = (n1, n2) # surf[i, j] means z pos at x=i, y=j
        # surf[i, j, 0] means z pos at x=i, y=j
        # surf[i, j, 1] means value for plotting at pos (i, j, surf[i, j])
        >>> surf.shape = (n1, n2, 2)
        # surf[i, j, 1:] means rgb or rgba color at pos (i, j, surf[i, j])
        >>> surf.shape = (n1, n2, 4) or (n1, n2, 5)
        >>> surf.shape = (N, 3) # surf[i, :] means i-th point position
        # surf[i, :3] means i-th point position
        # surf[i, 3] means i-th point's value for plotting
        >>> surf.shape = (N, 4)
        # surf[i, 3:] means i-th point color in rgb or rgba format
        >>> surf.shape = (N, 6) or (N, 7)
    volume : array-like
        3D array, values when surf_color is 'amp'
    value_type : str
        'depth' or 'amp', show z or amplitude, amplitude can be values in volume or
        values or colors
    clim : List
        [vmin, vmax] of surface volumes
    cmap : str or Colormap
        cmap for surface
    alpha : float
        opactity of the surfaces
    shape : List or Tuple
        If surf's shape is like (N, 3) or (N, 4), shape must be specified,
        if surf's shape is like (n1, n2) or (n1, n2, 2), shape will be ignored
    cbar_params : Dict
        params to create a colorbar
    
    kwargs : Dict
        parameters for vispy.scene.visuals.Mesh
    """
    utils.check_mmap(volume)
    utils.check_mmap(surfs)
    line_first = cigvis.is_line_first()
    method = kwargs.get('method', 'cubic')
    fill = kwargs.get('fill', -1)
    anti_rot = kwargs.get('anti_rot', True)

    # add surface
    if not isinstance(surfs, List):
        surfs = [surfs]

    surfaces = []
    values = []
    colors = []
    for surf in surfs:
        if surf.ndim == 3:
            s, v, c = surfaceutils.preproc_surf_array3(surf, value_type)
        elif surf.ndim == 2:
            if surf.shape[1] > 7:
                s, v, c = surfaceutils.preproc_surf_array2(
                    surf, volume, value_type)
            else:
                assert volume is not None or shape is not None
                if shape is None:
                    shape = volume.shape[:2] if line_first else volume.shape[1:]
                s, v, c = surfaceutils.preproc_surf_pos(
                    surf, shape, volume, value_type, interp, method, fill)
        else:
            raise RuntimeError('Invalid shape')
        surfaces.append(s)
        values.append(v)
        colors.append(c)

    if value_type == 'depth':
        values = surfaces

    if clim is None and value_type == 'amp':
        vmin = min([np.nanmin(s) for s in values])
        vmax = max([np.nanmax(s) for s in values])
        clim = [vmin, vmax]
    elif clim is None and value_type == 'depth':
        vmin = min([s[s >= 0].min() for s in values])
        vmax = max([s[s >= 0].max() for s in values])
        clim = [vmin, vmax]

    cmap = colormap.cmap_to_vispy(cmap)
    if alpha < 1:
        cmap = colormap.set_alpha(cmap, alpha)

    mesh_nodes = []
    for s, v, c in zip(surfaces, values, colors):
        mask = np.logical_or(s < 0, np.isnan(s))
        vertices, faces = surfaceutils.get_vertices_and_faces(
            s, mask, anti_rot=anti_rot)
        if v is not None:
            v = v[~mask].flatten()
        if c is not None:
            channel = c.shape[-1]
            c = c[~mask].flatten().reshape(-1, channel)

        mesh_kwargs = vispyutils.get_valid_kwargs('mesh', **kwargs)

        if kwargs.get('color', None) is not None:
            v = None
            c = None
        if c is not None:
            v = None

        mesh = Mesh(vertices=vertices,
                    faces=faces,
                    vertex_values=v,
                    vertex_colors=c,
                    shading='smooth',
                    **mesh_kwargs)

        if v is not None and c is None and kwargs.get('color', None) is None:
            mesh.cmap = cmap
            mesh.clim = clim

        mesh_nodes.append(mesh)

    if return_cbar:
        kwargs = vispyutils.get_valid_kwargs('colorbar', **kwargs)
        cbar = create_colorbar(cmap, clim, **kwargs)
        return mesh_nodes, cbar
    return mesh_nodes


def create_bodys(volume: np.ndarray,
                 level: float,
                 margin: float = None,
                 color: str = 'yellow',
                 filter_sigma: Union[float, List] = None,
                 **kwargs) -> List:
    """
    using marching_cubes to find meshs (its vertices and faces), 
    and the then use vispy.scene.visuals.Mesh to show them

    Parameters
    ----------
    volume : array-like
        3D array
    level : float
        mesh value
    color : str
        color for mesh
    
    kwargs : Dict
        parameters for vispy.scene.visuals.Mesh
    """
    utils.check_mmap(volume)
    if (filter_sigma is not None) or (margin is not None):
        if isinstance(volume, np.memmap):
            assert volume.mode != 'r', \
                "margin will modify the volume, set `mode='c'` " + \
                "instead of `mode='r'` in np.memmap"

    if filter_sigma is not None:
        volume = gaussian_filter(volume, filter_sigma)

    if margin is not None:
        volume[0, :, :] = margin
        volume[:, 0, :] = margin
        volume[:, :, 0] = margin
        volume[volume.shape[0] - 1, :, :] = margin
        volume[:, volume.shape[1] - 1, :] = margin
        volume[:, :, volume.shape[2] - 1] = margin

    from skimage.measure import marching_cubes
    # marching_cubes in skimage is more faster
    # F3 demo, salt body, skimage: 3.04s, vispy: 21.44s
    verts, faces, normals, values = marching_cubes(volume, level)
    kwargs = vispyutils.get_valid_kwargs('mesh', **kwargs)
    body = Mesh(verts, faces, color=color, shading='smooth', **kwargs)

    # HACK, NOTE: use Isosurface or convert to Mesh?
    # body = Isosurface(volume, level=level, color=color, shading='smooth')
    # # must call _prepare_draw before attaching ShadingFilter
    # # see: https://github.com/vispy/vispy/issues/2254#issuecomment-967276060
    # body._prepare_draw(body)

    return [body]


def create_Line_logs(logs: List or np.ndarray,
                     value_type: str = 'depth',
                     cmap: str = 'jet',
                     clim: List = None,
                     width: float = 6.0,
                     return_cbar: bool = False,
                     **kwargs):
    """
    create Line nodes to plot logs data

    Parameters
    ----------
    logs : List or array-like
        List (multi-logs) or np.ndarray (one log). For a log,
        its shape is like (N, 3) or (N, 4) or (N, 6) or (N, 7),
        the first 3 columns are (x, y, z) coordinates. If 3 columns,
        use the third column (z) as the color value (mapped by `cmap`), 
        if 4 columns, the 4-th column is the color value (mapped by `cmap`)
        when value_type is not 'depth', if 6 or 7 columns, colors are 
        RGB or RGBA format when value_type is not 'depth'.
    value_type : str
        'depth' or 'amp', if 'depth', force the colors are mapped by 
        'depth' (z or 3th column).
    cmap : str
        colormap
    clim : List
        [vmin, vmax] for showing
    width : float
        Line width
    return_cbar : bool
        return a colorbar
    
    kwargs : Dict
        parameters for vispy.scene.visuals.Line
    """
    warnings.warn("We recommand use 'create_well_logs' instead", UserWarning)
    if isinstance(logs, np.ndarray):
        assert logs.shape[1] >= 3
        logs = [logs]

    pos = []
    values = []
    for log in logs:
        assert log.ndim == 2 and log.shape[1] >= 3
        pos.append(log[:, :3])
        if value_type == 'depth':
            values.append(log[:, 2])
        else:
            if log.shape[1] == 3:
                values.append(log[:, 2])
            elif log.shape[1] == 4:
                values.append(log[:, 3])
            elif log.shape[1] == 6 or log.shape[1] == 7:
                values.append(log[:, 3:])
            else:
                raise RuntimeError("Invalid shape")

    if clim is None:
        clim = [
            min([np.nanmin(v) for v in values]),
            max([np.nanmax(v) for v in values])
        ]

    log_nodes = []
    line_kwargs = vispyutils.get_valid_kwargs('line', **kwargs)
    for p, v in zip(pos, values):
        if v.ndim == 1:
            v = colormap.get_colors_from_cmap(cmap, clim, v)
        log_nodes.append(Line(p, width=width, color=v, **line_kwargs))

    if return_cbar:
        kwargs = vispyutils.get_valid_kwargs('colorbar', **kwargs)
        cbar = create_colorbar(cmap, clim, **kwargs)
        return log_nodes, cbar
    return log_nodes


def create_well_logs(points: np.ndarray,
                     values: np.ndarray = None,
                     cmap: Union[str, List] = 'jet',
                     cyclinder: bool = True,
                     radius_tube: Union[float, List] = 1.5,
                     radius_line: List = [2.2, 5],
                     null_value: float = None,
                     clim: List = None,
                     index: List = None,
                     tube_points: int = 16,
                     mode: str = 'triangles'):
    """
    create a well log node

    Parameters
    -----------
    points : array-like
        points positions, shape as (N, 3)
    values : array-like
        log curves, shape as (N, m), m curves
    cmap : List or str
        colormaps for each curves
    cyclinder : bool
        a cyclinder with a same radius or not 
    radius_tube : float or List
        if cyclinder, it's a float, otherwise a List: [min_radius, max_radius]
    radius_line : List
        the log curves face radius
    null_value : float
        null value of log curves
    clim : List
        [[vmin1, vmax1], [vmin2, vmax2], ...] for log curves
    index : List
        point index of each log curve attached to
    tube_points : int
        the number of points to represent a circle
    mode : str
        use 'triangles'

    Returns
    --------
    node : List
        List of a `WellLog`
    """
    assert points.ndim == 2 and points.shape[1] == 3
    if values is None:
        values = points[:, 2]
    if values.ndim == 1:
        values = values[:, np.newaxis]

    assert len(points) == len(values)
    nlogs = values.shape[1]
    if not isinstance(cmap, List):
        cmap = [cmap] * nlogs
    assert len(cmap) == nlogs

    if null_value is not None:
        values[values == null_value] = np.nan

    if clim is None:
        clim = [[np.nanmin(values[:, i]),
                 np.nanmax(values[:, i])] for i in range(nlogs)]

    mintube = radius_tube
    if not cyclinder:
        if not isinstance(radius_tube, List):
            print('radius_tube is not a List, set as default: [1, 2]')
            radius_tube = [1, 2]
        mintube = max(radius_tube)

    if values.shape[1] > 1:
        assert min(radius_line) > mintube

    colors = np.zeros((nlogs, len(points), 4), dtype=float)
    radius = []

    def _cal_radius(v, r):
        return r[0] + (v - np.nanmin(v)) / (np.nanmax(v) -
                                            np.nanmin(v)) * (r[1] - r[0])

    # tube radius and colors
    if cyclinder:
        radius.append(radius_tube)
    else:
        r = _cal_radius(values[:, 0], radius_tube)
        r[np.isnan(r)] = radius_tube[0]
        radius.append(r)

    values[np.isnan(values[:, 0]), 0] = null_value
    colors[0] = colormap.get_colors_from_cmap(cmap[0], clim[0], values[:, 0])

    # line radius and colors
    for i in range(1, nlogs):
        colors[i] = colormap.get_colors_from_cmap(cmap[i], clim[i], values[:,
                                                                           i])
        r = _cal_radius(values[:, i], radius_line)
        radius.append(r)

    node = WellLog(points, radius, colors, index, tube_points, mode)

    return [node]


def create_points(points: np.ndarray,
                  r: float = 2,
                  color: str = 'green',
                  cmap='jet',
                  clim=None,
                  **kwargs):
    """
    create a node to show points using Mesh instead of Marker

    Parameters
    ----------
    points : array-like
        points, shape is like (N, 3).
    r : float
        the radius of a point, to control the size of a point
    color : str
        color to fill
    cmap : str
        colormap to map when set `vertex_values`
    clim : List
        clim if use cmap

    kwargs : Dict
        parameters for Mesh 
    """
    points = np.array(points)
    assert points.ndim == 2 and points.shape[1] >= 3
    vertices, faces = cigvis.meshs.cube_points(points[:, :3], r)

    if color is not None:
        kwargs['vertex_values'] = None
        kwargs['vertex_colors'] = None
    else:
        vertex_values = kwargs.get('vertex_values', None)
        if vertex_values is not None:
            assert len(vertex_values) == len(points)
            vertex_values = np.array(vertex_values)
            if clim is None:
                clim = [vertex_values.min(), vertex_values.max()]
            kwargs['vertex_values'] = np.repeat(vertex_values, 8)
        vertex_colors = kwargs.get('vertex_colors', None)
        if vertex_colors is not None:
            assert len(vertex_colors) == len(points)
            kwargs['vertex_colors'] = np.repeat(vertex_colors, 8)

    mesh_kwargs = vispyutils.get_valid_kwargs('mesh', **kwargs)
    point_mesh = Mesh(vertices=vertices,
                      faces=faces,
                      color=color,
                      shading='flat',
                      **mesh_kwargs)

    if color is None and kwargs.get('vertex_values', None) is not None:
        point_mesh.cmap = cmap
        point_mesh.clim = clim

    return [point_mesh]


def create_fault_skin(skin_dir,
                      suffix='*',
                      endian='>',
                      values_type='likelihood',
                      cmap='jet',
                      clim=None,
                      **kwargs):
    """"""
    if os.path.isfile(skin_dir):
        vertices, faces, values = cigvis.io.load_one_skin(
            skin_dir, endian, values_type)
    elif os.path.isdir(skin_dir):
        vertices, faces, values = cigvis.io.load_skins(skin_dir, suffix,
                                                       endian, values_type)
    if kwargs.get('color', None) is not None:
        values = None
    if clim is None and values is not None:
        clim = [values.min(), values.max()]

    node = Mesh(vertices,
                faces,
                vertex_values=values,
                shading='smooth',
                **kwargs)
    if values is not None and kwargs.get('vertex_colors',
                                         None) is None and kwargs.get(
                                             'color', None) is None:
        node.cmap = cmap
        node.clim = clim

    return [node]


def plot3D(nodes: List,
           grid: Tuple = None,
           share: bool = False,
           xyz_axis: bool = True,
           cbar_region_ratio: float = 0.125,
           savename: str = None,
           savedir: str = './',
           save_cbar: bool = False,
           cbar_name: str = 'cbar.png',
           size: Tuple = (1000, 800),
           **kwargs):
    """
    plot nodes in a 3D canvas

    Parameters
    -----------
    nodes : List of VisualNodes
        VisualNodes
    grid : Tuple
        grid of the canvas
    share : bool
        link all cameras when grid is not None
    xyz_axis : bool
        add a xyz_axis to each canvas
    cbar_region_ratio : float
        colorbar region ration, i.e., (width*cbar_region_ratio, hight)
    savename : str
        if is not None, will save the figure when rendering
    save_cbar : bool
        save colorbar individual if there is any `Colorbar` in nodes
    cbar_name : str
        the save colorbar image name
    size : Tuple
        canvas size
    kwargs : Dict
        other parameters pass to `Colorbar` and `VisCanvas`
    
    Examples
    ----------
    >>> node1, node2 = [mesh1, mesh2, image1], [mesh3, image2, image3]
    >>> plot3D(node1) # one subcanvas in the canvas
    >>> plot3D([node1, node2], grid=(1, 2)) # two subcanvas in the canvas
    """
    if grid is None:
        w, h = size
    else:
        h = size[1] / grid[0]
        w = size[0] / grid[1]
    cbar_size = (w * cbar_region_ratio, h)
    if not save_cbar:
        cbar_name = None

    # find cbars
    cbar_kwargs = vispyutils.get_valid_kwargs('colorbar', **kwargs)
    cbar_list = []
    if isinstance(nodes, Dict):
        for k, v in nodes.items():
            cbar_list += [n for n in v if isinstance(n, Colorbar)]
            if xyz_axis:
                nodes[k].append(XYZAxis())
    elif isinstance(nodes[0], List):
        for i, v in enumerate(nodes):
            cbar_list += [n for n in v if isinstance(n, Colorbar)]
            if xyz_axis:
                nodes[i].append(XYZAxis())
    else:
        cbar_list = [n for n in nodes if isinstance(n, Colorbar)]
        if xyz_axis:
            nodes.append(XYZAxis())

    # update cbars' size
    for cbar in cbar_list:
        cbar.update_params(cbar_size=cbar_size,
                           savedir=savedir,
                           cbar_name=cbar_name,
                           **cbar_kwargs)

    kwargs = vispyutils.get_valid_kwargs('viscanvas', **kwargs)
    canvas = VisCanvas(visual_nodes=nodes,
                       grid=grid,
                       share=share,
                       cbar_region_ratio=cbar_region_ratio,
                       savedir=savedir,
                       size=size,
                       **kwargs)

    canvas.show()

    if savename is not None:
        screen_shot = _screenshot()
        vispy.io.write_png(savedir + savename, screen_shot)

    vispy.app.run()

cigvis/vispyplot.py
JintaoLee-Roger/cigvis
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html

# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information

import os, re
import sys, shutil
from glob import glob
from pathlib import Path
from git.repo import Repo

DIR = Path(__file__).parent.resolve()

sys.path.append(str(Path(".").resolve()))


def download_image():
    print('Run git to download images')
    down_path = DIR / '_static/images'
    Repo.clone_from('https://github.com/JintaoLee-Roger/images.git',
                    to_path=down_path)

    shutil.move(down_path / 'cigvis', down_path.parent)
    shutil.rmtree(down_path)


download_image()

from sphinx_gallery.scrapers import figure_rst


class PNGScraper(object):

    def __init__(self):
        self.seen = set()

    def __repr__(self):
        return 'PNGScraper'

    def __call__(self, block, block_vars, gallery_conf):
        # Find all PNG files in the directory of this example.
        path_current_example = os.path.dirname(block_vars['src_file'])
        pngs = sorted(glob(os.path.join(path_current_example, '*.png')))

        # Iterate through PNGs, copy them to the sphinx-gallery output directory
        image_names = list()
        image_path_iterator = block_vars['image_path_iterator']
        for png in pngs:
            if png not in self.seen:
                self.seen |= set(png)
                this_image_path = image_path_iterator.next()
                image_names.append(this_image_path)
                shutil.move(png, this_image_path)
        # Use the `figure_rst` helper function to generate reST for image files
        return figure_rst(image_names, gallery_conf['src_dir'])


project = 'cigvis'
copyright = '2023, Jintao Li'
author = 'Jintao Li and others'

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

extensions = [
    'sphinx.ext.autodoc',
    'sphinxcontrib.apidoc',
    'sphinx_gallery.gen_gallery',
]

apidoc_module_dir = "../cigvis"
apidoc_output_dir = "api"
apidoc_excluded_paths = ["../cigvis/cpp"]
apidoc_separate_modules = True

# """
# Sphinx Gallery
from sphinx_gallery.sorting import FileNameSortKey

# the following files are ignored from gallery processing
ignore_files = [
    'demos/*',
]
ignore_pattern_regex = [re.escape(os.sep) + f for f in ignore_files]
ignore_pattern_regex = "|".join(ignore_pattern_regex)

execute = False
sphinx_gallery_conf = {
    'examples_dirs': [
        '../examples/3Dvispy', '../examples/2D', '../examples/1D',
        '../examples/colormap', '../examples/more_demos'
    ],
    'gallery_dirs': [
        'gallery/3Dvispy', 'gallery/2D', 'gallery/1D', 'gallery/colormap',
        'gallery/more_demos'
    ],
    'filename_pattern':
    re.escape(os.sep),
    'ignore_pattern':
    ignore_pattern_regex,
    'only_warn_on_example_error':
    True,
    'image_scrapers': (PNGScraper(), ),
    'reset_modules':
    tuple(),  # remove default matplotlib/seaborn resetters
    'first_notebook_cell':
    '%gui qt',  # tell notebooks to use Qt backend
    'within_subsection_order':
    FileNameSortKey,
    'download_all_examples':
    False,
}
if not execute:
    sphinx_gallery_conf['plot_gallery'] = False

# Let vispy.app.application:Application.run know that we are generating gallery images
os.environ["_VISPY_RUNNING_GALLERY_EXAMPLES"] = "1"

templates_path = ['_templates']
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', "**.ipynb_checkpoints"]
# """

# ReadTheDocs has its own way of generating sitemaps, etc.
# if not os.environ.get("READTHEDOCS"):
#     extensions += ["sphinx_sitemap"]

#     html_baseurl = os.environ.get("SITEMAP_URL_BASE", "http://127.0.0.1:8000/")
#     sitemap_locales = [None]
#     sitemap_url_scheme = "{link}"

language = 'zh_CN'

# The suffix of source filenames.
source_suffix = '.rst'

# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

html_theme = 'pydata_sphinx_theme'
html_static_path = ['_static']

# The main toctree document.
master_doc = 'index'

html_theme_options = {
    "use_edit_page_button":
    True,
    "icon_links": [
        {
            "name": "GitHub",
            "url": "https://github.com/JintaoLee-Roger/cigvis",
            "icon": "fa-brands fa-github",
        },
        {
            "name": "PyPI",
            "url": "https://pypi.org/project/cigvis/",
            "icon": "fa-custom fa-pypi",
        },
    ]
}

html_context = {
    "github_url":
    "https://github.com/JintaoLee-Roger/cigvis",  # or your GitHub Enterprise site
    "github_user": "JintaoLee-Roger",
    "github_repo": "cigvis",
    "github_version": "main",
    "doc_path": "https://cigvis.readthedocs.io/en/latest/",
}

html_title = 'CIGVis'

html_show_sourcelink = True
html_show_sphinx = True
html_show_copyright = True

htmlhelp_basename = 'cigvisdoc'

docs/conf.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
plot a 1D trace
================

This example demonstrates how to plot a 1D trace

.. image:: ../../_static/cigvis/1D/0.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/1D/0.png'

import numpy as np
import cigvis

root = '/Users/lijintao/work/mygit/pyseisview/data/'
sxp = root + 'seis_h360x600x400.dat'
ni, nx, nt = 400, 600, 360

sx = np.fromfile(sxp, np.float32).reshape(ni, nx, nt)

trace = sx[100, 100, :]

# 垂直显示
cigvis.plot1d(trace,
              dt=0.02,
              axis_label='Time / s',
              c='skyblue',
              save='example.png',
              dpi=200)

examples/1D/0-plot_trace.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
fill 模式
=======================

This example demonstrates how to plot a 1D trace

.. image:: ../../_static/cigvis/1D/1.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/1D/1.png'

import numpy as np
import cigvis
import matplotlib.pyplot as plt

root = '/Users/lijintao/work/mygit/pyseisview/data/'
sxp = root + 'seis_h360x600x400.dat'
ni, nx, nt = 400, 600, 360

sx = np.fromfile(sxp, np.float32).reshape(ni, nx, nt)

trace = sx[100, 100, :]

# 垂直显示
# cigvis.plot1d(trace, dt=0.02, axis_label='Time / s', c='skyblue')

fig, axes = plt.subplots(3, 1, figsize=(10, 8))

# 水平显示
cigvis.plot1d(trace, orient='h', dt=0.02, ax=axes[0])

# 填充模式

# 填充波峰
cigvis.plot1d(trace,
              orient='h',
              dt=0.02,
              fill_up=0.3,
              value_label='Amplitude',
              ax=axes[1])

# 填充波谷
cigvis.plot1d(trace,
              orient='h',
              dt=0.02,
              axis_label='Time / s',
              fill_down=0.3,
              ax=axes[2])

plt.tight_layout()
plt.savefig('example.png', bbox_inches='tight', pad_inches=0.01, dpi=200)
plt.show()

examples/1D/1-plot_trace.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
显示多条曲线
==========================

.. image:: ../../_static/cigvis/1D/2.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/1D/2.png'

import numpy as np
import cigvis
import matplotlib.pyplot as plt

root = '/Users/lijintao/work/mygit/pyseisview/data/'
sxp = root + 'seis_h360x600x400.dat'
ni, nx, nt = 400, 600, 360

sx = np.fromfile(sxp, np.float32).reshape(ni, nx, nt)

traces = sx[100, 50:70, :].T

fig, axes = plt.subplots(1, 2, figsize=(10, 5))

cigvis.plot_multi_traces(traces, dt=0.02, c='black', ax=axes[0])

# fill mode
cigvis.plot_multi_traces(traces, dt=0.02, c='black', fill_up=0.2, ax=axes[1])

plt.tight_layout()
plt.savefig('example.png', bbox_inches='tight', pad_inches=0.01, dpi=200)
plt.show()

examples/1D/2-plot_multi_traces.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
地震波信号
================

绘制地震记录与台站offset

.. image:: ../../_static/cigvis/1D/3.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/1D/3.png'

import numpy as np
import cigvis
import matplotlib.pyplot as plt

data = np.load('../../data/smg/data.npy')
offset = np.load('../../data/smg/offset.npy')
offset_index = np.load('../../data/smg/offset_index.npy')

fig, axes = plt.subplots(1, 2, figsize=(12, 6))

cigvis.plot_signal_compare(data, ax=axes[0])
cigvis.plot_signal_compare(data / 2,
                           offset,
                           offset_index,
                           with_offset=True,
                           ax=axes[1])

plt.tight_layout()
plt.savefig('3.png', bbox_inches='tight', pad_inches=0.01, dpi=200)
plt.show()
examples/1D/3-plot_station.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
显示2d的切片
================

cbar 可以设置为 ``cbar=''``, 这将忽略 cbar_label

.. image:: ../../_static/cigvis/2D/01.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/2D/01.png'

import cigvis
import numpy as np

d = np.fromfile('../../data/rgt/sx.dat', np.float32).reshape(128, 128, 128)
sl = d[30, :, :]

cigvis.plot2d(sl,
              figsize=(6, 6),
              xlabel='xline',
              ylabel='time',
              title='Example',
              cbar='Amplitude',
              save='example.png',
              dpi=200)

examples/2D/01-plot2d.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
显示2d的 slice, rgt, fault
==========================

.. image:: ../../_static/cigvis/2D/02.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/2D/02.png'

import cigvis
import numpy as np
from cigvis import colormap

root = '../../'
sxp = 'data/rgt/sx.dat'
lxp = 'data/rgt/ux.dat'
fxp = 'data/rgt/fx.dat'
ni, nx, nt = 128, 128, 128

sx = np.memmap(root + sxp, np.float32, 'r', shape=(ni, nx, nt))
lx = np.memmap(root + lxp, np.float32, 'r', shape=(ni, nx, nt))
fx = np.memmap(root + fxp, np.float32, 'r', shape=(ni, nx, nt))
sx2 = sx[20, :, :]
lx2 = lx[20, :, :]
fx2 = fx[20, :, :]

fg = {}
# rgt
fg['img'] = cigvis.fg_image_args(lx2, alpha=0.5, show_cbar=False)

# fault
fx_cmap = colormap.set_alpha_except_min('jet', 1, False)
fg['img'] += cigvis.fg_image_args(fx2,
                                  fx_cmap,
                                  interpolation='nearest',
                                  show_cbar=True)

cigvis.plot2d(sx2,
              fg,
              figsize=(6, 6),
              xlabel='xline',
              ylabel='time',
              title='Example',
              cbar='fault',
              discrete=True,
              save='example.png',
              dpi=200)

examples/2D/02-plot2d_rgt_fx.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
显示2d的 slice, rgt 和 测井曲线
===============================

.. image:: ../../_static/cigvis/2D/03.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/2D/03.png'

import cigvis
import numpy as np

root = '../../'
sxp = 'data/rgt/sx.dat'
lxp = 'data/rgt/ux.dat'
fxp = 'data/rgt/fx.dat'
ni, nx, nt = 128, 128, 128

sx = np.memmap(root + sxp, np.float32, 'r', shape=(ni, nx, nt))
lx = np.memmap(root + lxp, np.float32, 'r', shape=(ni, nx, nt))
sx2 = sx[20, :, :]
lx2 = lx[20, :, :]

las = cigvis.io.load_las('../../data/cb23.las')['data'][:2000, 3]
x = 80  # assume x location is 80

fg = {}
fg['img'] = cigvis.fg_image_args(lx2, alpha=0.5)

w = 20
y = np.linspace(0, 125, len(las))
# normalize well log curve
# las -> [-0.5, 0.5]
las = (las - las.min()) / (las.max() - las.min()) - 0.5
# las -> [-0.5*w+x, 0.5*w+x]
x = las * w + x
fg['line'] = cigvis.line_args(x, y, 'black')

cigvis.plot2d(sx2,
              fg,
              figsize=(6, 6),
              xlabel='xline',
              ylabel='time',
              title='Example',
              save='example.png',
              dpi=200)

examples/2D/03-plot2d_well_logs.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
显示2d的 水平 slice 和 测井的位置
==================================

.. image:: ../../_static/cigvis/2D/04.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/2D/04.png'

import cigvis
import numpy as np

root = '../../'
sxp = 'data/rgt/sx.dat'
ni, nx, nt = 128, 128, 128

sx = np.memmap(root + sxp, np.float32, 'r', shape=(ni, nx, nt))
sx2 = sx[:, :, 40]

fg = {}

well_pos = np.random.rand(10, 2) * 100 + 10
well_name = ['well-' + str(i + 10001) for i in range(10)]
fg['marker'] = cigvis.marker_args(well_pos[:, 0],
                                  well_pos[:, 1],
                                  marker='^',
                                  c='red')
fg['annotate'] = cigvis.annotate_args(well_pos[:, 0], well_pos[:, 1],
                                      well_name)

cigvis.plot2d(sx2,
              fg,
              figsize=(6, 6),
              xlabel='inline',
              ylabel='crossline',
              title='Example',
              save='example.png',
              dpi=200)

examples/2D/04-plot2d_well_pos.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
使用 subplots 显示多个图像
===========================

.. image:: ../../_static/cigvis/2D/05.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/2D/05.png'

import cigvis
import numpy as np
import matplotlib.pyplot as plt

sx = np.fromfile('../../data/rgt/sx.dat', np.float32).reshape(128, 128, 128)
lx = np.fromfile('../../data/rgt/ux.dat', np.float32).reshape(128, 128, 128)

fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(6, 6))

fg = dict(img=cigvis.fg_image_args(lx[30, :, :], alpha=0.5))
cigvis.plot2d(sx[30, :, :], fg, title='inline=30', ax=axes[0, 0])

fg = dict(img=cigvis.fg_image_args(lx[60, :, :], alpha=0.5))
cigvis.plot2d(sx[60, :, :], fg, title='inline=60', ax=axes[0, 1])

fg = dict(img=cigvis.fg_image_args(lx[90, :, :], alpha=0.5))
cigvis.plot2d(sx[90, :, :], fg, title='inline=90', ax=axes[1, 0])

fg = dict(img=cigvis.fg_image_args(lx[120, :, :], alpha=0.5))
cigvis.plot2d(sx[120, :, :], fg, title='inline=120', ax=axes[1, 1])

plt.tight_layout()
plt.savefig('example.png', bbox_inches='tight', pad_inches=0.01, dpi=200)

plt.show()
examples/2D/05-plot2d_subplots.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
显示 2d 的 slice, rgt
================================

可以合理设置cmap来mask一些区域

.. image:: ../../_static/cigvis/2D/06.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/2D/06.png'

import cigvis
import numpy as np
import matplotlib.pyplot as plt
from cigvis import colormap

sx = np.fromfile('../../data/rgt/sx.dat', np.float32).reshape(128, 128, 128)
lx = np.fromfile('../../data/rgt/ux.dat', np.float32).reshape(128, 128, 128)

sx2 = sx[30, :, :]
lx2 = lx[30, :, :]

fg1 = {}
fg1['img'] = cigvis.fg_image_args(lx2, alpha=0.5)

fg2 = {}
fg2_cmap = colormap.set_alpha_except_max('jet', 0.5, False)
fg2['img'] = cigvis.fg_image_args(lx2,
                                  fg2_cmap,
                                  clim=[lx2.min(), lx2.max() * 0.8])

fg3 = {}
fg3_cmap = colormap.set_alpha_except_top('jet',
                                         0.5,
                                         clim=[lx2.min(), lx2.max()],
                                         segm=lx2.max() * 0.8,
                                         forvispy=False)
fg3['img'] = cigvis.fg_image_args(lx2, fg3_cmap)

fg4 = {}
fg4_cmap = colormap.set_alpha_except_ranges('jet',
                                            0.5,
                                            [lx2.min(), lx2.max()],
                                            r=[[120, 130], [140, 150]],
                                            forvispy=False)
fg4['img'] = cigvis.fg_image_args(lx2, fg4_cmap)

fig, axs = plt.subplots(2, 2, figsize=(8.5, 7))

cigvis.plot2d(sx2,
              fg1,
              ax=axs[0, 0],
              cbar='rgt',
              xlabel='xline',
              ylabel='time',
              title='overlid')
cigvis.plot2d(sx2,
              fg2,
              ax=axs[0, 1],
              cbar='rgt',
              xlabel='xline',
              ylabel='time',
              title='except top 1')
cigvis.plot2d(sx2,
              fg3,
              ax=axs[1, 0],
              cbar='rgt',
              xlabel='xline',
              ylabel='time',
              title='except top 2')
cigvis.plot2d(sx2,
              fg4,
              ax=axs[1, 1],
              cbar='rgt',
              xlabel='xline',
              ylabel='time',
              title='except range')
plt.tight_layout()
plt.savefig('example.png', bbox_inches='tight', pad_inches=0.01, dpi=200)

plt.show()

examples/2D/06-plot2d_rgt.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
显示2d的 slice, rgt fault 和 rgt 的等值线
================================================

使用 skimage.measure.find_countours 计算rgt的等值线

.. image:: ../../_static/cigvis/2D/07.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/2D/07.png'

import cigvis
import numpy as np
import matplotlib.pyplot as plt
from skimage.measure import find_contours

root = '../../'
sxp = 'data/rgt/sx.dat'
lxp = 'data/rgt/ux.dat'
ni, nx, nt = 128, 128, 128

sx = np.memmap(root + sxp, np.float32, 'r', shape=(ni, nx, nt))
lx = np.memmap(root + lxp, np.float32, 'r', shape=(ni, nx, nt))
sx2 = sx[20, :, :]
lx2 = lx[20, :, :]

fig, axes = plt.subplots(1, 2, figsize=(8, 4))

fg = {}

# rgt overlaid
fg['img'] = cigvis.fg_image_args(lx2, alpha=0.5)

# find rgt's isolines at lx2[20, 20], lx2[60, 60], and lx2[100, 100]
isos1 = find_contours(lx2, lx2[20, 20])[0]
isos2 = find_contours(lx2, lx2[60, 60])[0]
isos3 = find_contours(lx2, lx2[100, 100])[0]
fg['line'] = cigvis.line_args(isos3[:, 0], isos3[:, 1], lw=3)
fg['line'] += cigvis.line_args(isos2[:, 0], isos2[:, 1], lw=3)
fg['line'] += cigvis.line_args(isos1[:, 0], isos1[:, 1], lw=3)

# add markers at [20, 20], [60, 60], and [100, 100]
fg['marker'] = cigvis.marker_args([20, 60, 100], [20, 60, 100], c='red', s=40)

cigvis.plot2d(sx2,
              fg,
              xlabel='xline',
              ylabel='time',
              title="don't show legend",
              ax=axes[0])

fg['line'] = cigvis.line_args(isos3[:, 0],
                              isos3[:, 1],
                              lw=3,
                              label='pos=(20,20)')
fg['line'] += cigvis.line_args(isos2[:, 0],
                               isos2[:, 1],
                               lw=3,
                               label='pos=(60,60)')
fg['line'] += cigvis.line_args(isos1[:, 0],
                               isos1[:, 1],
                               lw=3,
                               label='pos=(60,60)')
cigvis.plot2d(sx2,
              fg,
              xlabel='xline',
              ylabel='time',
              title='show legend (must set `label=`)',
              show_legend=True,
              ax=axes[1])

plt.tight_layout()
plt.savefig('example.png', bbox_inches='tight', pad_inches=0.01, dpi=200)
plt.show()

examples/2D/07-plot2d_rgt_isoline.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
显示离散的 colorbar
========================================

.. image:: ../../_static/cigvis/2D/08.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/2D/08.png'

import numpy as np
import cigvis

root = '/Users/lijintao/work/mygit/pyseisview/data/'
sxp = root + 'seis_h360x600x400.dat'
lxp = root + 'label_h360x600x400.dat'
ni, nx, nt = 400, 600, 360

sx = np.memmap(sxp, np.float32, 'r', shape=(ni, nx, nt))
lx = np.memmap(lxp, np.float32, 'r', shape=(ni, nx, nt))

sx2 = sx[:, 100, :]
lx2 = lx[:, 100, :]

fg = {}

fg['img'] = cigvis.fg_image_args(lx2, alpha=0.5, interpolation='nearest')

cigvis.plot2d(
    sx2,
    fg,
    cbar='Facies',
    discrete=True,
    tick_labels=['Class A', 'Class B', 'Class C', 'Class D', 'Class E'],
    save='example.png',
    dpi=200)

examples/2D/08-plot2d_disc.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
显示离散的 colorbar, 并设置 mask
========================================

.. image:: ../../_static/cigvis/2D/09.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/2D/09.png'

import numpy as np
import cigvis
from cigvis import colormap
import matplotlib.pyplot as plt

root = '/Users/lijintao/work/mygit/pyseisview/data/'
sxp = root + 'seis_h360x600x400.dat'
lxp = root + 'label_h360x600x400.dat'
ni, nx, nt = 400, 600, 360

sx = np.memmap(sxp, np.float32, 'r', shape=(ni, nx, nt))
lx = np.memmap(lxp, np.float32, 'r', shape=(ni, nx, nt))

sx2 = sx[:, 100, :]
lx2 = lx[:, 100, :]
ticks_label = ['Class A', 'Class B', 'Class C', 'Class D', 'Class E']

fig, axes = plt.subplots(2, 2, figsize=(9, 6.5))

fg = {}
fg['img'] = cigvis.fg_image_args(lx2, alpha=0.5, interpolation='nearest')
cigvis.plot2d(sx2,
              fg,
              cbar='Facies',
              discrete=True,
              tick_labels=ticks_label,
              title='discrete colorbar',
              ax=axes[0, 0])

cmap = colormap.set_alpha_except_min('jet', 0.5, False)
fg['img'] = cigvis.fg_image_args(lx2, cmap=cmap, interpolation='nearest')
cigvis.plot2d(sx2,
              fg,
              cbar='Facies',
              discrete=True,
              tick_labels=ticks_label,
              title='remove the min value',
              ax=axes[0, 1])

cmap = colormap.set_alpha_except_min('jet', 0.5, False)
fg['img'] = cigvis.fg_image_args(lx2, cmap=cmap, interpolation='nearest')
cigvis.plot2d(sx2,
              fg,
              cbar='Facies',
              discrete=True,
              tick_labels=ticks_label,
              remove_trans=False,
              title='cbar keep the min value',
              ax=axes[1, 0])

colors = ['red', 'green', 'yellow', 'blue', (0, 0.5, 0.5)]
values = np.unique(lx2)
cmap = colormap.custom_disc_cmap(values, colors)
cmap = colormap.set_alpha_except_min(cmap, 0.5, False)
fg['img'] = cigvis.fg_image_args(lx2, cmap=cmap, interpolation='nearest')
cigvis.plot2d(sx2,
              fg,
              cbar='Facies',
              discrete=True,
              tick_labels=ticks_label,
              title='custom cmap',
              ax=axes[1, 1])

plt.tight_layout()
plt.savefig('example.png', bbox_inches='tight', pad_inches=0.01, dpi=200)

plt.show()

examples/2D/09-plot2d_disc_cbar.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
设置坐标轴范围
==================================

将y轴设置为时间轴, 起始为 1s, 间隔 2ms,
将x轴设置为x坐标, 起始为 4098, 间隔 12.5m

.. image:: ../../_static/cigvis/2D/10.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/2D/10.png'

import cigvis
import numpy as np

d = np.fromfile('../../data/rgt/sx.dat', np.float32).reshape(128, 128, 128)
sl = d[30, :, :]

cigvis.plot2d(
    sl,
    figsize=(6, 6),
    xlabel='xline/m',
    ylabel='time/s',
    title='set tick labels',
    xsample=[4098, 12.5],  # [start, step]
    ysample=[1, 0.02],  # [start, step]
    cbar='Amplitude',
    save='example.png',
    dpi=200)

examples/2D/10-plot2d_ticks.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
设置字体
================

.. image:: ../../_static/cigvis/2D/11.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/2D/11.png'

import matplotlib
import cigvis
import numpy as np
import matplotlib.pyplot as plt

# set font family
font = {'family': 'Times New Roman', 'size': 12}
matplotlib.rc('font', **font)

d = np.fromfile('../../data/rgt/sx.dat', np.float32).reshape(128, 128, 128)
sl = d[30, :, :]

fig, axes = plt.subplots(1, 2, figsize=(12, 6))

cigvis.plot2d(sl,
              xlabel='xline',
              ylabel='time',
              title='origin',
              cbar='Amplitude',
              ax=axes[0])

cigvis.plot2d(sl,
              xlabel='xline',
              ylabel='time',
              title='set font size',
              cbar='Amplitude',
              title_size=16,
              xlabel_size=14,
              ylabel_size=14,
              ticklabels_size=12,
              cbar_label_size=14,
              cbar_ticklabels_size=12,
              ax=axes[1])

plt.tight_layout()

plt.savefig('example.png', bbox_inches='tight', pad_inches=0.01, dpi=200)

plt.show()

examples/2D/11-plot2d_fonts.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
aspect 参数的意义
======================

可以将 aspect 设置为 'equal' (default), 'auto' or float:
- 'equal' : 保持img的宽高比保持不变
- 'auto' : 宽高比与设置的figsize有关, 即改变长宽比
- float number f: 宽高比保持 f = h/w

.. image:: ../../_static/cigvis/2D/12.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/2D/12.png'

import cigvis
import numpy as np
import matplotlib.pyplot as plt

d = np.fromfile('../../data/rgt/sx.dat', np.float32).reshape(128, 128, 128)
sl = d[30, :, :]

fig, axs = plt.subplots(2, 2, figsize=(10, 8))

# aspect='equal'
cigvis.plot2d(sl,
              aspect='equal',
              xlabel='xline',
              ylabel='time',
              title="Aspect='equal'",
              cbar='Amplitude',
              ax=axs[0, 0])

# aspect='auto'
cigvis.plot2d(sl,
              aspect='auto',
              xlabel='xline',
              ylabel='time',
              title="Aspect='auto'",
              cbar='Amplitude',
              ax=axs[0, 1])

# aspect=1.5  h/w=1.5
cigvis.plot2d(sl,
              aspect=1.5,
              xlabel='xline',
              ylabel='time',
              title="Aspect=1.5",
              cbar='Amplitude',
              ax=axs[1, 0])

# aspect=2/3, h/w=2/3
cigvis.plot2d(sl,
              aspect=2 / 3,
              xlabel='xline',
              ylabel='time',
              title="Aspect=2/3",
              cbar='Amplitude',
              ax=axs[1, 1])

plt.tight_layout()
plt.savefig('example.png', bbox_inches='tight', pad_inches=0.01, dpi=200)
plt.show()

examples/2D/12-plot2d_aspect.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
显示一个3D数据体 (通过选取几个切片)
========================================

.. image:: ../../_static/cigvis/3Dvispy/01.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/3Dvispy/01.png'

import numpy as np
import cigvis

seisp = '../../data/co2/sx.dat'
ni, nx, nt = 192, 192, 240
sx = np.fromfile(seisp, np.float32).reshape(ni, nx, nt)

nodes, cbar = cigvis.create_slices(sx,
                                   cmap='Petrel',
                                   return_cbar=True,
                                   label_str='Amplitude')
nodes.append(cbar)

cigvis.plot3D(nodes, size=(1000, 800), savename='example.png')

examples/3Dvispy/01-slice.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
在3D地震数据体的切片上叠加断层显示
=================================================

``create_overlay`` 的第一个参数是背景(background), 
第二个参数是叠加的前景(foreground)

.. Note::
    foreground 需要合理设置透明度和mask

.. image:: ../../_static/cigvis/3Dvispy/02.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/3Dvispy/02.png'

import numpy as np
import cigvis
from cigvis import colormap

sxp = '../../data/rgt/sx.dat'
fxp = '../../data/rgt/fx.dat'
ni, nx, nt = 128, 128, 128

sx = np.fromfile(sxp, np.float32).reshape(ni, nx, nt)
fx = np.fromfile(fxp, np.float32).reshape(ni, nx, nt)

# mask min value (0), 0 means no fault
fg_cmap = colormap.set_alpha_except_min('jet', alpha=1)

# fx is discrete data, set interpolation as 'nearest'
nodes = cigvis.create_overlay(sx,
                              fx,
                              pos=[[36], [28], [84]],
                              bg_cmap='gray',
                              fg_cmap=fg_cmap,
                              fg_interpolation='nearest')

cigvis.plot3D(nodes, size=(800, 800), savename='example.png')

examples/3Dvispy/02-overlay_fault.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
在3D地震数据体的切片上叠加RGT显示
=================================================

``create_overlay`` 的第一个参数是背景(background), 
第二个参数是叠加的前景(foreground)

.. Note::
    foreground 需要合理设置透明度和mask

.. image:: ../../_static/cigvis/3Dvispy/03.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/3Dvispy/03.png'

import numpy as np
import cigvis
from cigvis import colormap

sxp = '../../data/rgt/sx.dat'
uxp = '../../data/rgt/ux.dat'
ni, nx, nt = 128, 128, 128

sx = np.fromfile(sxp, np.float32).reshape(ni, nx, nt)
rgt = np.fromfile(uxp, np.float32).reshape(ni, nx, nt)

fg_cmap = colormap.set_alpha('jet', alpha=0.4)
nodes = cigvis.create_overlay(sx,
                              rgt,
                              pos=[[36], [28], [84]],
                              bg_cmap='gray',
                              fg_cmap=fg_cmap)

cigvis.plot3D(nodes, size=(800, 800), savename='example.png')

examples/3Dvispy/03-overlay_rgt.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
在3D地震数据体的切片上叠加多个3D数据体 (RGT, fault)
===================================================

``create_overlay`` 的第一个参数是背景(background), 
第二个参数是叠加的前景(foreground), 其可以包含多个3D数据体

.. Note::
    foreground 需要合理设置透明度和mask

.. image:: ../../_static/cigvis/3Dvispy/04.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/3Dvispy/04.png'

import numpy as np
import cigvis
from cigvis import colormap

sxp = '../../data/rgt/sx.dat'
uxp = '../../data/rgt/ux.dat'
fxp = '../../data/rgt/fx.dat'
ni, nx, nt = 128, 128, 128

sx = np.fromfile(sxp, np.float32).reshape(ni, nx, nt)
rgt = np.fromfile(uxp, np.float32).reshape(ni, nx, nt)
fx = np.fromfile(fxp, np.float32).reshape(ni, nx, nt)

rgt_cmap = colormap.set_alpha('jet', 0.4)
# mask min value (0), 0 means no fault
fx_cmap = colormap.set_alpha_except_min('jet', alpha=1)

fg_cmap = [rgt_cmap, fx_cmap]

# fx is discrete data, set interpolation as 'nearest'
nodes = cigvis.create_overlay(sx, [rgt, fx],
                              pos=[[36], [28], [84]],
                              bg_cmap='gray',
                              fg_cmap=fg_cmap,
                              fg_interpolation=['cubic', 'nearest'])

cigvis.plot3D(nodes, size=(800, 800), savename='example.png')

examples/3Dvispy/04-overlay_rgt_fault.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
显示3D地震数据和多个层位
=======================================

层位可以是一个 (n1, n2) 大小的 Z 值,
也可以是 (N, 3) 大小的 N 个点

.. image:: ../../_static/cigvis/3Dvispy/05.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/3Dvispy/05.png'

import numpy as np
import cigvis

sxp = '../../data/co2/sx.dat'
sfp1 = '../../data/co2/mh21.dat'
sfp2 = '../../data/co2/mh22.dat'
ni, nx, nt = 192, 192, 240

sx = np.fromfile(sxp, np.float32).reshape(ni, nx, nt)
sf1 = np.fromfile(sfp1, np.float32).reshape(ni, nx)
sf2 = np.fromfile(sfp2, np.float32).reshape(ni, nx)

nodes = cigvis.create_slices(sx)

# show amplitude
nodes += cigvis.create_surfaces([sf1, sf2],
                                volume=sx,
                                value_type='amp',
                                cmap='Petrel',
                                clim=[sx.min(), sx.max()])

# add two points
nodes += cigvis.create_points(np.array([[70, 50, 158], [20, 100, 80]]), r=3)

cigvis.plot3D(nodes, size=(800, 800), savename='example.png')

examples/3Dvispy/05-surfaces.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
显示3D地震数据和3D地质体 (CO2)
=======================================

.. image:: ../../_static/cigvis/3Dvispy/06.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/3Dvispy/06.png'

import numpy as np
import cigvis

sxp = '../../data/co2/sx.dat'
lxp = '../../data/co2/lx.dat'
ni, nx, nt = 192, 192, 240

sx = np.fromfile(sxp, np.float32).reshape(ni, nx, nt)
lx = np.fromfile(lxp, np.float32).reshape(ni, nx, nt)


nodes = cigvis.create_slices(sx)

nodes += cigvis.create_bodys(lx, level=0.5, margin=0, filter_sigma=1)

cigvis.plot3D(nodes, size=(800, 800), savename='example.png')

examples/3Dvispy/06-bodys.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
将测井轨迹显示为一个管子 (tube)
=======================================

.. image:: ../../_static/cigvis/3Dvispy/07.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/3Dvispy/07.png'

import numpy as np
import cigvis

sxp = '../../data/co2/sx.dat'
ni, nx, nt = 192, 192, 240

sx = np.fromfile(sxp, np.float32).reshape(ni, nx, nt)

x = np.linspace(50, 100, 5000)
y = np.linspace(50, 150, 5000)
z = np.sin((y - 50) / 200 * np.pi) * 200

points = np.c_[x, y, z]

nodes = cigvis.create_slices(sx)
nodes += cigvis.create_well_logs(points, cmap='red')

cigvis.plot3D(nodes, size=(800, 800), savename='example.png')
examples/3Dvispy/07-well_logs_tube.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
将测井轨迹显示为一个tube, 并显示多条测井曲线
===============================================

This example demonstrates how to visualize a well
log with multi traces.

The first trace is plotted as a tube, the others 
are plotted as faces.

.. image:: ../../_static/cigvis/3Dvispy/08.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/3Dvispy/08.png'

import numpy as np
import cigvis

sxp = '../../data/co2/sx.dat'
lasp = '../../data/cb23.las'
ni, nx, nt = 192, 192, 240

sx = np.fromfile(sxp, np.float32).reshape(ni, nx, nt)
las = cigvis.io.load_las(lasp)
idx = las['Well']['name'].index('NULL')
null_value = float(las['Well']['value'][3])
v = las['data'][:, 1:5]

x = np.linspace(50, 100, len(v))
y = np.linspace(50, 150, len(v))
z = np.sin((y - 50) / 200 * np.pi) * 200

points = np.c_[x, y, z]

nodes = cigvis.create_slices(sx)

# cyclinder=False: 半径和颜色都表示测井值的大小
# cyclinder=True: 井柱半径不变
nodes += cigvis.create_well_logs(
    points,
    v,
    cyclinder=False,
    null_value=null_value,
    cmap=['jet', 'seismic', 'Petrel', 'od_seismic1'])

cigvis.plot3D(nodes, zoom_factor=8, size=(800, 800), savename='example.png')

examples/3Dvispy/08-well_log_multi_traces.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
显示多种地球物理数据
================================================

.. image:: ../../_static/cigvis/3Dvispy/09.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/3Dvispy/09.png'

import numpy as np
import cigvis

sx = np.memmap('../../data/co2/sx.dat', np.float32, 'r', shape=(192, 192, 240))
lx = np.memmap('../../data/co2/lx.dat', np.float32, 'c', shape=(192, 192, 240))
sf1 = np.fromfile('../../data/co2/mh21.dat', np.float32).reshape(192, 192)
sf2 = np.fromfile('../../data/co2/mh22.dat', np.float32).reshape(192, 192)

# v = np.random.rand(100)
las = cigvis.io.load_las('../../data/cb23.las')
idx = las['Well']['name'].index('NULL')
null_value = float(las['Well']['value'][3])
v = las['data'][:, 1:5]

x = np.linspace(50, 100, len(v))
y = np.linspace(50, 100, len(v))
z = np.sin((x - 50) / 100 * np.pi) * 200

points = np.c_[x, y, z]

nodes = []
nodes += cigvis.create_slices(sx)
nodes += cigvis.create_well_logs(
    points,
    v,
    cyclinder=False,
    null_value=null_value,
    cmap=['jet', 'seismic', 'Petrel', 'od_seismic1'])
nodes += cigvis.create_points(np.array([[70, 50, 158], [20, 100, 80]]), r=3)
nodes += cigvis.create_bodys(lx, 0.5, 0)
nodes += cigvis.create_surfaces([sf1, sf2],
                                sx,
                                'amp',
                                cmap='Petrel',
                                clim=[sx.min(), sx.max()])

cigvis.plot3D(nodes, savename='example.png')

examples/3Dvispy/09-slice_surf_body_logs.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
划分多个canvas, 显示多个3D数据
========================================

.. image:: ../../_static/cigvis/3Dvispy/10.gif
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/3Dvispy/10.png'

import numpy as np
import cigvis

seisp = '../../data/co2/sx.dat'
ni, nx, nt = 192, 192, 240
sx = np.fromfile(seisp, np.float32).reshape(ni, nx, nt)

nodes1 = cigvis.create_slices(sx, cmap='Petrel')
nodes2 = cigvis.create_slices(sx, cmap='Petrel')

cigvis.plot3D(
    [nodes1, nodes2],
    grid=(1, 2),  # here, define a grid
    size=(1000, 800),
    savename='example.png')

examples/3Dvispy/10-multi_canvas.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
在多个canvas中, 共享相机参数
================================================

这个可以很方便的实现比较两个数据结果

.. image:: ../../_static/cigvis/3Dvispy/11.gif
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/3Dvispy/11.png'

import numpy as np
import cigvis

seisp = '../../data/co2/sx.dat'
ni, nx, nt = 192, 192, 240
sx = np.fromfile(seisp, np.float32).reshape(ni, nx, nt)

nodes1 = cigvis.create_slices(sx, cmap='Petrel')
nodes2 = cigvis.create_slices(sx, cmap='Petrel')

cigvis.plot3D(
    [nodes1, nodes2],
    grid=(1, 2),  # here, define a grid
    share=True,  # here, link all cameras
    size=(1000, 800),
    savename='example.png')

examples/3Dvispy/11-share_cameras.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
plot colormap
================

This example demonstrates how to plot a colormap

.. image:: ../../_static/cigvis/colormap/01.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/colormap/01.png'

from cigvis import colormap

colormap.plot_cmap('Petrel', [-5, 5], save='cmap.png')
examples/colormap/01-plot_cmap.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
plot all custom cmaps
=======================

This example demonstrates how to plot all custom cmaps

.. image:: ../../_static/cigvis/colormap/02.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/colormap/02.png'

from cigvis import colormap

colormap.plot_all_custom_cmap([-5, 5], save='all_cmap.png', dpi=200)

examples/colormap/02-plot_all_custom_cmap.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
stratum colormap
====================

一个用于显示RGT的colormap (created by Xinming Wu)

.. image:: ../../_static/cigvis/colormap/03.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/colormap/03.png'

import numpy as np
import cigvis

d = np.fromfile('../../data/rgt/ux.dat', np.float32).reshape(128, 128, 128)

nodes, cbar = cigvis.create_slices(d, [[36], [28], [84]],
                                   cmap='stratum',
                                   return_cbar=True,
                                   label_str='stratum cmap for RGT')
nodes.append(cbar)

cigvis.plot3D(nodes, savename='example.png')
examples/colormap/03-plot_stratum_cmap.py
JintaoLee-Roger/cigvis
"""
三维数据的显示
======================

.. image:: ../../_static/cigvis/more_demos/010.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/more_demos/010.png'

import numpy as np
import cigvis


def show(d):
    # show1
    nodes1 = cigvis.create_slices(d)

    # add colorbar, method1, not recommand
    nodes2, cbar = cigvis.create_slices(d, return_cbar=True)
    nodes2.append(cbar)

    # add colorbar, method2, recommand
    nodes3 = cigvis.create_slices(d, cmap='Petrel', clim=[d.min(), d.max()])
    cbar = cigvis.create_colorbar(cmap='Petrel', clim=[d.min(), d.max()])
    nodes3.append(cbar)

    cigvis.plot3D([nodes1, nodes2, nodes3],
                  grid=(1, 3),
                  size=(1500, 500),
                  cbar_region_ratio=0.18,
                  savename='example.png')


if __name__ == '__main__':
    root = '../../'
    sxp = root + 'data/co2/sx.dat'
    ni, nx, nt = 192, 192, 240

    sx = np.memmap(sxp, np.float32, 'c', shape=(ni, nx, nt))

    show(sx)

examples/more_demos/010-slices.py
JintaoLee-Roger/cigvis
"""
离散的colorbar
======================

.. image:: ../../_static/cigvis/more_demos/011.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/more_demos/011.png'

import numpy as np
import cigvis
from cigvis import colormap


def show_discrete_cbar(d):
    """
    添加离散的colorbar

    离散数据建议将interpolation设置为 'nearest'
    """
    nodes1 = cigvis.create_slices(d, cmap='jet', interpolation='nearest')
    cbar1 = cigvis.create_colorbar(
        cmap='jet',
        clim=[d.min(), d.max()],
        discrete=True,
        disc_ticks=[np.unique(d), ['CA', 'CB', 'CC', 'CD', '100']],
        label_str='Facies')
    nodes1.append(cbar1)
    """
    离散的数据, 当数据中有outline时, 会导致其他值的颜色很接近
    """
    d[d == 1] = 100
    nodes2 = cigvis.create_slices(d, cmap='jet', interpolation='nearest')
    cbar2 = cigvis.create_colorbar(cmap='jet',
                                   clim=[d.min(), d.max()],
                                   discrete=True,
                                   disc_ticks=[np.unique(d).astype(int)],
                                   label_str='Facies')
    nodes2.append(cbar2)
    """
    自定义 colormap 可以解决 cbar2 的问题
    """
    values = np.unique(d)
    colors = ['gray', 'green', '#7f6589', 'blue', (0, 0.5, 0.5)]
    cmap = colormap.custom_disc_cmap(values, colors)
    nodes3 = cigvis.create_slices(d, cmap=cmap, interpolation='nearest')
    cbar3 = cigvis.create_colorbar(cmap=cmap,
                                   clim=[d.min(), d.max()],
                                   discrete=True,
                                   disc_ticks=[values.astype(int)],
                                   label_str='Facies')
    nodes3.append(cbar3)
    """
    可以使用 get_colors_from_cmap 获取颜色

    然后自定义 colormap 解决 cbar2 的问题
    """
    values = np.unique(d)

    # 获取均匀分布的颜色
    colors = colormap.get_colors_from_cmap('jet',
                                           clim=[0, len(values) - 1],
                                           values=np.arange(0, len(values)))

    cmap = colormap.custom_disc_cmap(values, colors)
    nodes4 = cigvis.create_slices(d, cmap=cmap, interpolation='nearest')
    cbar4 = cigvis.create_colorbar(cmap=cmap,
                                   clim=[d.min(), d.max()],
                                   discrete=True,
                                   disc_ticks=[values.astype(int)],
                                   label_str='Facies')
    nodes4.append(cbar4)

    cigvis.plot3D([nodes1, nodes2, nodes3, nodes4],
                  grid=(2, 2),
                  cbar_region_ratio=0.18,
                  size=(1400, 1000),
                  savename='example.png')


if __name__ == '__main__':
    root = '/Users/lijintao/work/mygit/pyseisview/data/'
    lxp = root + 'label_h360x600x400.dat'
    ni, nx, nt = 400, 600, 360

    lx = np.memmap(lxp, np.float32, 'c', shape=(ni, nx, nt))

    show_discrete_cbar(lx)

examples/more_demos/011-disc_cbar.py
JintaoLee-Roger/cigvis
"""
三维数据的叠加显示 (离散)
==========================

注意:

我们并没有为 create_overlay 添加一个alpha参数,
需要设置透明度设置, 请使用 colormap.set_alpha 之类的
函数为 colormap 设置 alpha 值

这是因为 overylay 可以叠加多个数据, 当需要mask一些值时,
需要提供额外的信息, 会增加 overlay 函数的参数量, 让
overlay 函数更晦涩

.. image:: ../../_static/cigvis/more_demos/020.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/more_demos/020.png'

import numpy as np
import cigvis
from cigvis import colormap


def show(bg, fg):
    """
    叠加地震和类别(地震相), alpha=0.5
    """
    fg[fg == 1] = 100
    values = np.unique(fg)
    colors = ['red', 'green', 'yellow', 'blue', (0, 0.5, 0.5)]
    cmap = colormap.custom_disc_cmap(values, colors)
    cmap = colormap.set_alpha(cmap, 0.5)  # Note:

    nodes1 = cigvis.create_overlay(bg,
                                   fg,
                                   fg_cmap=cmap,
                                   fg_interpolation='nearest')

    cbar1 = cigvis.create_colorbar(cmap=cmap,
                                   clim=[fg.min(), fg.max()],
                                   discrete=True,
                                   disc_ticks=[values.astype(int)],
                                   label_str='Facies')
    nodes1.append(cbar1)
    """
    mask 值最小的一类, 但是在colorbar中显示最小的一类(白色)
    """
    values = np.unique(fg)
    colors = ['red', 'green', 'yellow', 'blue', (0, 0.5, 0.5)]
    cmap = colormap.custom_disc_cmap(values, colors)
    cmap = colormap.set_alpha_except_min(cmap, 0.5)  # Note:

    nodes2 = cigvis.create_overlay(bg,
                                   fg,
                                   fg_cmap=cmap,
                                   fg_interpolation='nearest')

    cbar2 = cigvis.create_colorbar(cmap=cmap,
                                   clim=[fg.min(), fg.max()],
                                   discrete=True,
                                   disc_ticks=[values.astype(int)],
                                   label_str='Facies')
    nodes2.append(cbar2)
    """
    mask 最小的一类, 同时 colorbar 也去除 
    """
    values = np.unique(fg)
    colors = ['red', 'green', 'yellow', 'blue', (0, 0.5, 0.5)]
    cmap = colormap.custom_disc_cmap(values, colors)
    cmap = colormap.set_alpha_except_min(cmap, 0.5)  # Note:

    nodes3 = cigvis.create_overlay(bg,
                                   fg,
                                   fg_cmap=cmap,
                                   fg_interpolation='nearest')

    values = values[1:]
    cbar3 = cigvis.create_colorbar(cmap=cmap,
                                   clim=[fg.min(), fg.max()],
                                   discrete=True,
                                   disc_ticks=[values.astype(int)],
                                   label_str='Facies')
    nodes3.append(cbar3)
    """
    mask 最大的一类
    """
    values = np.unique(fg)
    colors = ['red', 'green', 'yellow', 'blue', (0, 0.5, 0.5)]
    cmap = colormap.custom_disc_cmap(values, colors)
    cmap = colormap.set_alpha_except_max(cmap, 0.5)  # Note:

    nodes4 = cigvis.create_overlay(bg,
                                   fg,
                                   fg_cmap=cmap,
                                   fg_interpolation='nearest')

    values = values[:-1]
    cbar4 = cigvis.create_colorbar(cmap=cmap,
                                   clim=[fg.min(), fg.max()],
                                   discrete=True,
                                   disc_ticks=[values],
                                   label_str='Facies')
    nodes4.append(cbar4)
    """
    mask 特定的值, 不是最小或最大, 可以是多个值
    """
    values = np.unique(fg)
    colors = ['red', 'green', 'yellow', 'blue', (0, 0.5, 0.5)]
    cmap = colormap.custom_disc_cmap(values, colors)
    cmap = colormap.set_alpha_except_values(cmap,
                                            0.5,
                                            clim=[fg.min(), fg.max()],
                                            values=[0, 100])  # Note:

    nodes5 = cigvis.create_overlay(bg,
                                   fg,
                                   fg_cmap=cmap,
                                   fg_interpolation='nearest')

    values = values[values != 0]
    values = values[values != 100]
    cbar5 = cigvis.create_colorbar(cmap=cmap,
                                   clim=[fg.min(), fg.max()],
                                   discrete=True,
                                   disc_ticks=[values],
                                   label_str='Facies')
    nodes5.append(cbar5)

    cigvis.plot3D([nodes1, nodes2, nodes3, nodes4, nodes5],
                  grid=(2, 3),
                  size=(1300, 800),
                  cbar_region_ratio=0.24,
                  savename='example.png')


if __name__ == '__main__':
    root = '/Users/lijintao/work/mygit/pyseisview/data/'
    sxp = root + 'seis_h360x600x400.dat'
    lxp = root + 'label_h360x600x400.dat'
    ni, nx, nt = 400, 600, 360

    sx = np.memmap(sxp, np.float32, 'c', shape=(ni, nx, nt))
    lx = np.memmap(lxp, np.float32, 'c', shape=(ni, nx, nt))

    show(sx, lx)
examples/more_demos/020-overlay.py
JintaoLee-Roger/cigvis
"""
三维数据的叠加显示 (连续)
==========================

.. image:: ../../_static/cigvis/more_demos/030.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/more_demos/030.png'

import numpy as np
import cigvis
from cigvis import colormap


def show(bg, fg, fx):
    """
    seismic and RGT
    """
    cmap1 = colormap.set_alpha('jet', 0.5)
    nodes1 = cigvis.create_overlay(bg,
                                   fg,
                                   pos=[[36], [28], [84]],
                                   bg_cmap='gray',
                                   fg_cmap=cmap1)

    cbar1 = cigvis.create_colorbar(cmap='jet',
                                   clim=[fg.min(), fg.max()],
                                   label_str='RGT')
    nodes1.append(cbar1)
    """
    mask RGT 的底部 (mask fg > 120)
    """
    cmap2 = colormap.set_alpha_except_max('jet', 0.5)
    nodes2 = cigvis.create_overlay(bg,
                                   fg,
                                   pos=[[36], [28], [84]],
                                   bg_cmap='gray',
                                   fg_clim=[fg.min(), 120],
                                   fg_cmap=cmap2)

    # set vmax = 150
    cbar2 = cigvis.create_colorbar(cmap='jet',
                                   clim=[fg.min(), 120],
                                   label_str='RGT')
    nodes2.append(cbar2)
    """
    seismic and fault
    """
    # Note: set alpha = 1 is the best
    cmap3 = colormap.set_alpha_except_min('jet', 1)
    nodes3 = cigvis.create_overlay(bg,
                                   fx,
                                   pos=[[36], [28], [84]],
                                   bg_cmap='gray',
                                   fg_cmap=cmap3,
                                   fg_interpolation='nearest')

    values = np.unique(fx).astype(int)
    values = values[1:]
    labels = ['fx' + str(i) for i in values]
    cbar3 = cigvis.create_colorbar(cmap='jet',
                                   clim=[fx.min(), fx.max()],
                                   label_str='fault',
                                   discrete=True,
                                   disc_ticks=[values, labels])
    nodes3.append(cbar3)
    """
    seismic, masked RGT (mask fg > 120) and fault
    """
    cmap4 = [
        colormap.set_alpha_except_max('jet', 0.5),
        colormap.set_alpha_except_min('jet', 1)
    ]
    interp = ['cubic', 'nearest']
    fg_clim = [[fg.min(), 120], [fx.min(), fx.max()]]
    nodes4 = cigvis.create_overlay(bg, [fg, fx],
                                   pos=[[36], [28], [84]],
                                   bg_cmap='gray',
                                   fg_cmap=cmap4,
                                   fg_clim=fg_clim,
                                   fg_interpolation=interp)

    cbar4 = cigvis.create_colorbar(cmap='jet',
                                   clim=[fg.min(), 120],
                                   label_str='RGT')
    nodes4.append(cbar4)

    cigvis.plot3D([nodes1, nodes2, nodes3, nodes4],
                  grid=(2, 2),
                  size=(1300, 1000),
                  cbar_region_ratio=0.18,
                  share=True,
                  savename='example.png')


if __name__ == '__main__':
    sxp = '../../data/rgt/sx.dat'
    uxp = '../../data/rgt/ux.dat'
    fxp = '../../data/rgt/fx.dat'
    ni, nx, nt = 128, 128, 128

    sx = np.memmap(sxp, np.float32, 'c', shape=(ni, nx, nt))
    fx = np.memmap(fxp, np.float32, 'c', shape=(ni, nx, nt))
    ux = np.memmap(uxp, np.float32, 'c', shape=(ni, nx, nt))

    show(sx, ux, fx)

examples/more_demos/030-overlay2.py
JintaoLee-Roger/cigvis
"""
层位(n1, n2)显示
===================

.. image:: ../../_static/cigvis/more_demos/040.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/more_demos/040.png'

import numpy as np
import cigvis


def show(sx, sfs):
    """
    颜色为 depth, sf 里面的所有层位使用同一个 clim
    """
    nodes1 = cigvis.create_slices(sx)
    nodes1 += cigvis.create_surfaces(sfs,
                                     volume=sx,
                                     value_type='depth',
                                     clim=[0, 239])
    """
    颜色为 depth, sf 里面的每个层位单独设置 clim
    """
    nodes2 = cigvis.create_slices(sx)
    for sf in sfs:
        nodes2 += cigvis.create_surfaces(sf,
                                         volume=sx,
                                         value_type='depth',
                                         clim=[sf.min(), sf.max()])
    """
    color: amplitude
    """
    # nodes = []
    nodes3 = cigvis.create_slices(sx)
    nodes3 += cigvis.create_surfaces(sfs,
                                     volume=sx,
                                     value_type='amp',
                                     cmap='Petrel',
                                     clim=[sx.min(), sx.max()])
    """
    一个mask的层位, 将二维矩阵中小于0的部分mask
    """
    sfs[1][:120, 50:100] = -1

    nodes4 = cigvis.create_slices(sx)
    nodes4 += cigvis.create_surfaces(sfs,
                                     volume=sx,
                                     value_type='amp',
                                     cmap='Petrel',
                                     clim=[sx.min(), sx.max()])

    cigvis.plot3D([nodes1, nodes2, nodes3, nodes4],
                  grid=(2, 2),
                  share=True,
                  savename='example.png')


if __name__ == '__main__':
    sxp = '../../data/co2/sx.dat'
    lxp = '../../data/co2/lx.dat'
    sf1p = '../../data/co2/mh21.dat'
    sf2p = '../../data/co2/mh22.dat'
    ni, nx, nt = 192, 192, 240

    sx = np.memmap(sxp, np.float32, 'c', shape=(ni, nx, nt))
    lx = np.memmap(lxp, np.float32, 'c', shape=(ni, nx, nt))

    sf1 = np.fromfile(sf1p, np.float32).reshape(ni, nx)
    sf2 = np.fromfile(sf2p, np.float32).reshape(ni, nx)

    show(sx, [sf1, sf2])  # depth

examples/more_demos/040-surface1.py
JintaoLee-Roger/cigvis
"""
层位(N, 3)显示
================

.. image:: ../../_static/cigvis/more_demos/041.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/more_demos/041.png'

import numpy as np
import cigvis


def show(sx, sfs):
    """
    sf 除了可以是二维的矩阵, 也可以是 (N, 3) 的点矩阵(无序), 
    每个 sf 都是独立的, 即 sfs 里面可以同时包含
    (ni, nx) 的规则网格 和 (N, 3) 的点.
    N 可以不等于 ni * nx
    """
    sfs1 = [sfs[0].copy(), sfs[1].copy()]
    y, x = np.meshgrid(np.arange(192), np.arange(192))
    sfs1[1] = np.c_[x.flatten(), y.flatten(), sfs1[1].flatten()]

    nodes1 = cigvis.create_slices(sx)
    nodes1 += cigvis.create_surfaces(sfs1,
                                     volume=sx,
                                     value_type='amp',
                                     cmap='Petrel',
                                     clim=[sx.min(), sx.max()])
    """
    当 sf.shape = (N, 3) 时, 可以选择是否启用插值(默认不启用)
    下面是启用插值的结果
    """
    sfs2 = [sfs[0].copy(), sfs[1].copy()]
    y, x = np.meshgrid(np.arange(192), np.arange(192))
    sfs2[1] = np.c_[x.flatten(), y.flatten(), sfs2[1].flatten()]
    k = np.random.choice(np.arange(2000, len(sfs2[1])), 30000, False)
    sfs2[1] = sfs2[1][k, :]

    nodes2 = cigvis.create_slices(sx)
    nodes2 += cigvis.create_surfaces(sfs2,
                                     volume=sx,
                                     value_type='amp',
                                     cmap='Petrel',
                                     clim=[sx.min(), sx.max()],
                                     interp=True)
    """
    当 sf.shape = (N, 3) 时, 可以选择是否启用插值(默认启用)
    下面是不启用插值的结果
    """
    sfs3 = [sfs[0].copy(), sfs[1].copy()]
    y, x = np.meshgrid(np.arange(192), np.arange(192))
    sfs3[1] = np.c_[x.flatten(), y.flatten(), sfs3[1].flatten()]
    k = np.random.choice(np.arange(2000, len(sfs3[1])), 30000, False)
    sfs3[1] = sfs3[1][k, :]

    nodes3 = cigvis.create_slices(sx)
    nodes3 += cigvis.create_surfaces(sfs3,
                                     volume=sx,
                                     value_type='amp',
                                     cmap='Petrel',
                                     clim=[sx.min(), sx.max()],
                                     interp=False)
    """
    添加控制点
    """
    nodes4 = cigvis.create_slices(sx)
    nodes4 += cigvis.create_surfaces(sfs,
                                     volume=sx,
                                     value_type='amp',
                                     cmap='Petrel',
                                     clim=[sx.min(), sx.max()])

    points = [[70, 50, 158], [20, 100, 80]]
    nodes4 += cigvis.create_points(points, r=3)

    cigvis.plot3D([nodes1, nodes2, nodes3, nodes4],
                  grid=(2, 2),
                  share=True,
                  savename='example.png')


if __name__ == '__main__':
    sxp = '../../data/co2/sx.dat'
    lxp = '../../data/co2/lx.dat'
    sf1p = '../../data/co2/mh21.dat'
    sf2p = '../../data/co2/mh22.dat'
    ni, nx, nt = 192, 192, 240

    sx = np.memmap(sxp, np.float32, 'c', shape=(ni, nx, nt))
    lx = np.memmap(lxp, np.float32, 'c', shape=(ni, nx, nt))

    sf1 = np.fromfile(sf1p, np.float32).reshape(ni, nx)
    sf2 = np.fromfile(sf2p, np.float32).reshape(ni, nx)

    show(sx, [sf1, sf2])  # [x, y, z]

examples/more_demos/041-surface1.py
JintaoLee-Roger/cigvis
"""
地质体
===========

.. image:: ../../_static/cigvis/more_demos/050.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/more_demos/050.png'

import numpy as np
import cigvis


def show1(sx, lx, level):
    nodes = cigvis.create_slices(sx)
    nodes += cigvis.create_bodys(lx, level, margin=0)

    cigvis.plot3D(nodes, savename='example.png')


if __name__ == '__main__':
    sxp = '../../data/co2/sx.dat'
    lxp = '../../data/co2/lx.dat'
    ni, nx, nt = 192, 192, 240

    sx = np.memmap(sxp, np.float32, 'c', shape=(ni, nx, nt))
    lx = np.memmap(lxp, np.float32, 'c', shape=(ni, nx, nt))

    show1(sx, lx, 0.5)
examples/more_demos/050-bodys1.py
JintaoLee-Roger/cigvis
"""
测井轨迹
=============

.. image:: ../../_static/cigvis/more_demos/060.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/more_demos/060.png'

import numpy as np
import cigvis


def show(sx, points):
    """
    显示将测井显示为一条等半径的tube,
    并显示为一个颜色
    """

    nodes1 = cigvis.create_slices(sx)
    nodes1 += cigvis.create_well_logs(points, cmap='orange', radius_tube=3)
    """
    显示将测井显示为一条等半径的tube,
    颜色为深度
    """

    nodes2 = cigvis.create_slices(sx)
    nodes2 += cigvis.create_well_logs(points, cmap='jet', radius_tube=3)

    cigvis.plot3D([nodes1, nodes2],
                  grid=(1, 2),
                  zoom_factor=4,
                  savename='example.png')


if __name__ == '__main__':
    sxp = '../../data/co2/sx.dat'
    lxp = '../../data/co2/lx.dat'
    lasp = '../../data/cb23.las'
    ni, nx, nt = 192, 192, 240

    sx = np.memmap(sxp, np.float32, 'c', shape=(ni, nx, nt))
    lx = np.memmap(lxp, np.float32, 'c', shape=(ni, nx, nt))

    # create a well log
    las = cigvis.io.load_las(lasp)
    idx = las['Well']['name'].index('NULL')
    null_value = float(las['Well']['value'][3])
    lasdata = las['data'][:, 1:]

    x = np.linspace(50, 100, len(lasdata))
    y = np.linspace(50, 150, len(lasdata))
    z = np.sin((y - 50) / 200 * np.pi) * 200
    points = np.c_[x, y, z]

    show(sx, points)

examples/more_demos/060-well_logs.py
JintaoLee-Roger/cigvis
"""
测井轨迹和测井曲线
===================

.. image:: ../../_static/cigvis/more_demos/061.png
    :alt: image
    :align: center

"""

# sphinx_gallery_thumbnail_path = '_static/cigvis/more_demos/061.png'

import numpy as np
import cigvis


def show(sx, points, values, null_value):
    """
    显示将测井显示为一条等半径的tube,
    颜色为一条测井曲线的值
    """

    v = values[:, 0]

    nodes1 = cigvis.create_slices(sx)
    nodes1 += cigvis.create_well_logs(points,
                                      v,
                                      cmap='jet',
                                      radius_tube=2,
                                      null_value=null_value)
    """
    显示将测井显示为一条不同半径的tube,
    颜色为一条测井曲线的值, 半径的大小也表示测井曲线值的大小
    """

    nodes2 = cigvis.create_slices(sx)
    nodes2 += cigvis.create_well_logs(points,
                                      v,
                                      cmap='jet',
                                      cyclinder=False,
                                      radius_tube=[1, 2],
                                      null_value=null_value)
    """
    显示多条测井曲线

    显示将测井显示为一条不同半径的tube,
    颜色为第一条测井曲线的值, 半径的大小也表示第一条测井曲线值的大小

    其他的测井曲线显示为附着在tube表面的面
    """

    cmaps = ['jet', 'seismic', 'Petrel', 'od_seismic1']

    nodes3 = cigvis.create_slices(sx)
    nodes3 += cigvis.create_well_logs(points,
                                      values,
                                      cmap=cmaps,
                                      cyclinder=False,
                                      radius_tube=[1, 1.7],
                                      radius_line=[2.2, 5],
                                      null_value=null_value)

    cigvis.plot3D([nodes1, nodes2, nodes3],
                  grid=(1, 3),
                  zoom_factor=16,
                  share=True,
                  savename='example.png')


if __name__ == '__main__':
    sxp = '../../data/co2/sx.dat'
    lxp = '../../data/co2/lx.dat'
    lasp = '../../data/cb23.las'
    ni, nx, nt = 192, 192, 240

    sx = np.memmap(sxp, np.float32, 'c', shape=(ni, nx, nt))
    lx = np.memmap(lxp, np.float32, 'c', shape=(ni, nx, nt))

    # create a well log
    las = cigvis.io.load_las(lasp)
    idx = las['Well']['name'].index('NULL')
    null_value = float(las['Well']['value'][3])
    lasdata = las['data'][:, 1:]

    x = np.linspace(50, 100, len(lasdata))
    y = np.linspace(50, 150, len(lasdata))
    z = np.sin((y - 50) / 200 * np.pi) * 200
    points = np.c_[x, y, z]

    show(sx, points, lasdata[:, 1:5], null_value)
examples/more_demos/061-well_logs.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
F3 demo
===========

F3 demo, `https://dataunderground.org/dataset/f3 <https://dataunderground.org/dataset/f3>`_.

Interpretation data is provided by Xinming Wu, all data is big endian.

CIG's profile photo (`https://github.com/USTC-CIG <https://github.com/USTC-CIG>`_)

.. image:: ../../_static/cigvis/more_demos/070.png
    :alt: image
    :align: center
"""
# sphinx_gallery_thumbnail_path = '_static/cigvis/more_demos/070.png'

import numpy as np
import cigvis


def load_wellLog(p):
    nlog = 4
    npoints = 2121
    x = [259, 619, 339, 141]
    y = [33, 545, 704, 84]
    z = np.arange(0, 0.2 * npoints, 0.2)
    v = np.fromfile(p, '>f4').reshape(nlog, npoints)
    v = 0.5 * np.log(v)

    nodes = []
    for i in range(nlog):
        points = np.c_[np.ones(npoints) * x[i], np.ones(npoints) * y[i], z]
        nodes += cigvis.create_well_logs(points,
                                         v[i],
                                         cyclinder=False,
                                         radius_tube=[2, 5],
                                         null_value=-999.25)

    return nodes


if __name__ == '__main__':

    root = '/Users/lijintao/Downloads/F3/f3d/data/'
    seisp = root + 'seis.dat'
    saltp = root + 'salt.dat'
    hz1p = root + 'hz1.dat'
    hz2p = root + 'hz2.dat'
    uncp = root + 'unc.dat'
    unc1p = root + 'unc1.dat'
    unc2p = root + 'unc2.dat'
    ni, nx, nt = 591, 951, 362
    shape = (ni, nx, nt)

    # seismic slices
    seis = np.memmap(seisp, '>f4', 'c', shape=shape)
    nodes = cigvis.create_slices(seis,
                                 pos=[ni - 2, 25, nt - 2],
                                 cmap='gray',
                                 clim=[-2.0, 1.5])

    # salt (geologic body)
    salt = np.memmap(saltp, '>f4', 'c', shape=shape)
    nodes += cigvis.create_bodys(salt, 0.0, 0.0, color='cyan')

    # hrizon (surface)
    hz2 = np.fromfile(hz2p, '>f4').reshape(ni, nx)
    nodes += cigvis.create_surfaces([hz2], color='yellow')

    # displacement field of unconformity (volume)
    unc = np.fromfile(uncp, '>f4').reshape(shape).astype(np.float32)
    # unconformity (surface)
    unc2 = np.fromfile(unc2p, '>f4').reshape(ni, nx).astype(np.float32)
    nodes += cigvis.create_surfaces([unc2], volume=unc, value_type='amp')

    # well logs
    nodes += load_wellLog(root + 'logs.dat')

    # fault skin
    nodes += cigvis.create_fault_skin(root + 'skins/',
                                      endian='>',
                                      values_type='likelihood')

    cigvis.plot3D(nodes,
                  azimuth=-65.0,
                  elevation=22.0,
                  fov=15.0,
                  axis_scales=(1, 1, 1.7),
                  zoom_factor=1.4)

examples/more_demos/070-f3.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.
"""
F3 demo2
===========

F3 demo, `https://dataunderground.org/dataset/f3 <https://dataunderground.org/dataset/f3>`_.

Interpretation data is provided by Xinming Wu, all data is big endian

.. image:: ../../_static/cigvis/more_demos/071.png
    :alt: image
    :align: center
"""
# sphinx_gallery_thumbnail_path = '_static/cigvis/more_demos/071.png'

import numpy as np
import cigvis
from cigvis import colormap


def load_wellLog(p):
    nlog = 4
    npoints = 2121
    x = [259, 619, 339, 141]
    y = [33, 545, 704, 84]
    z = np.arange(0, 0.2 * npoints, 0.2)
    v = np.fromfile(p, '>f4').reshape(nlog, npoints)
    v = 0.5 * np.log(v)

    nodes = []
    for i in range(nlog):
        points = np.c_[np.ones(npoints) * x[i], np.ones(npoints) * y[i], z]
        nodes += cigvis.create_well_logs(points,
                                         v[i],
                                         cyclinder=False,
                                         radius_tube=[2, 5],
                                         null_value=-999.25)

    return nodes


if __name__ == '__main__':

    root = '/Users/lijintao/Downloads/F3/f3d/data/'
    seisp = root + 'seis.dat'
    saltp = root + 'salt.dat'
    hz1p = root + 'hz1.dat'
    hz2p = root + 'hz2.dat'
    unc1p = root + 'unc1.dat'
    unc2p = root + 'unc2.dat'
    ni, nx, nt = 591, 951, 362
    shape = (ni, nx, nt)

    # seismic
    seis = np.memmap(seisp, '>f4', 'c', shape=shape)
    # overlay
    inter = np.memmap(root + 'intp.dat', '>f4', 'c', shape=shape)

    fg_cmap = colormap.set_alpha('jet', 0.6)
    fg_clim = [inter.max() * 0.15, inter.max() * 0.5]
    nodes = cigvis.create_overlay(seis,
                                  inter,
                                  pos=[ni - 2, 25, nt - 2],
                                  bg_cmap='gray',
                                  bg_clim=[-2.0, 1.5],
                                  fg_cmap=fg_cmap,
                                  fg_clim=fg_clim,
                                  fg_interpolation='nearest')

    salt = np.memmap(saltp, '>f4', 'c', shape=shape)
    nodes += cigvis.create_bodys(salt, 0.0, 0.0, color='cyan')

    # hz1 = np.fromfile(hz1p, '>f4').reshape(ni, nx)
    hz2 = np.fromfile(hz2p, '>f4').reshape(ni, nx)
    nodes += cigvis.create_surfaces([hz2], color='yellow')

    unc = np.fromfile(root + 'unc.dat',
                      '>f4').reshape(shape).astype(np.float32)
    # unc1 = np.fromfile(unc1p, '>f4').reshape(ni, nx).astype(np.float32)
    unc2 = np.fromfile(unc2p, '>f4').reshape(ni, nx).astype(np.float32)
    nodes += cigvis.create_surfaces([unc2], volume=unc, value_type='amp')

    nodes += load_wellLog(root + 'logs.dat')

    nodes += cigvis.create_fault_skin(root + 'skins/')

    cigvis.plot3D(nodes,
                  azimuth=-65.0,
                  elevation=22.0,
                  fov=15.0,
                  axis_scales=(1, 1, 1.7),
                  zoom_factor=1.4)

examples/more_demos/071-f3.py
JintaoLee-Roger/cigvis
# Copyright (c) 2023 Jintao Li.
# Computational and Interpretation Group (CIG),
# University of Science and Technology of China (USTC).
# All rights reserved.

import sys
import os
import re
from pathlib import Path

from setuptools import setup, find_packages

if sys.version_info[0] < 3:
    raise Exception("CIGVis only supports Python 3")

version = ""
dir_path = os.path.dirname(os.path.realpath(__file__))
with open(dir_path + "/VERSION.txt", "r") as file:
    for line in file:
        version = line.strip()
        break

if not version:
    print("Fatal error: Failed to parse version from VERSION")
    exit(1)

package_name = "cigvis"

description = "CIGVis is a tool for geophysical data visualization, " + \
      "which developed by Computational Interpretation Group (CIG)"

setup(name=package_name,
      version=version,
      author="Jintao Li, and others",
      url="https://github.com/JintaoLee-Roger/cigvis",
      license='MIT',
      description=description,
      long_description=Path("README.md").read_text(encoding="utf-8"),
      long_description_content_type="text/markdown",
      python_requires=">=3.7",
      packages=find_packages())

setup.py
JintaoLee-Roger/cigvis