Source code for pyrocko.gui.sparrow.elements.table

# https://pyrocko.org - GPLv3
#
# The Pyrocko Developers, 21st Century
# ---|P------/S----------~Lg----------

import copy

import numpy as num

from pyrocko.guts import Bool, Float, String, StringChoice
from pyrocko.gui import util as gui_util
from pyrocko.gui.vtk_util import ScatterPipe, BeachballPipe
from pyrocko.gui.qt_compat import qw, qc

from . import base
from .. import common

guts_prefix = 'sparrow'
km = 1e3


def inormalize(x, imin, imax, discrete=True):
    if x.size == 0:
        return num.full(x.shape, imin, dtype=int)

    xmin = num.nanmin(x)
    xmax = num.nanmax(x)
    if xmin == xmax:
        xmin -= 0.5
        xmax += 0.5

    rmin = imin - 0.5
    rmax = imax + 0.5

    if discrete:
        return num.clip(
            num.round(
                rmin + (x - xmin) * (
                    (rmax-rmin) / (xmax - xmin))).astype(num.int64),
            imin, imax)
    else:
        return num.clip(
            rmin + (x - xmin) * ((rmax-rmin) / (xmax - xmin)),
            imin, imax)


def string_to_sorted_idx(values):
    val_sort = num.sort(values, axis=-1, kind='mergesort')
    val_sort_unique = num.unique(val_sort)

    val_to_idx = dict([
        (val_sort_unique[i], i)
        for i in range(val_sort_unique.shape[0])])

    return num.array([val_to_idx[val] for val in values])


[docs]class SymbolChoice(StringChoice): choices = ['point', 'sphere', 'beachball']
[docs]class MaskingShapeChoice(StringChoice): choices = ['rect', 'linear', 'quadratic']
[docs]class MaskingModeChoice(StringChoice): choices = ['past + future', 'past', 'future'] @classmethod def get_factors(cls, mode, value_low): return { 'past + future': (value_low, 1.0, value_low), 'past': (value_low, 1.0, 0.0), 'future': (0.0, 1.0, value_low)}[mode]
[docs]class TableState(base.ElementState): visible = Bool.T(default=True) size = Float.T(default=3.0) color_parameter = String.T(optional=True) cpt = base.CPTState.T(default=base.CPTState.D()) size_parameter = String.T(optional=True) depth_min = Float.T(optional=True) depth_max = Float.T(optional=True) depth_offset = Float.T(default=0.0) symbol = SymbolChoice.T(default='sphere') time_masking_opacity = Float.T(default=0.0) time_masking_shape = MaskingShapeChoice.T(default='rect') time_masking_mode = MaskingModeChoice.T(default='past + future')
class TableElement(base.Element): def __init__(self): base.Element.__init__(self) self._parent = None self._table = None self._istate = 0 self._istate_view = 0 self._controls = None self._color_combobox = None self._size_combobox = None self._pipes = None self._pipe_maps = None self._isize_min = 1 self._isize_max = 6 self.cpt_handler = base.CPTHandler() def bind_state(self, state): base.Element.bind_state(self, state) self.talkie_connect(state, ['visible', 'size'], self.update) self.talkie_connect( state, ['depth_min', 'depth_max', 'time_masking_shape', 'time_masking_mode', 'time_masking_opacity'], self.update_alpha) self.cpt_handler.bind_state(state.cpt, self.update) self.talkie_connect( state, ['symbol', 'size_parameter', 'color_parameter'], self.update_sizes) def unbind_state(self): self.cpt_handler.unbind_state() base.Element.unbind_state(self) def get_name(self): return 'Table' def set_parent(self, parent): self._parent = parent self._parent.add_panel( self.get_title_label(), self._get_controls(), visible=True, title_controls=[ self.get_title_control_remove(), self.get_title_control_visible()]) for var in ['tmin', 'tmax', 'tduration', 'tposition']: self.talkie_connect( self._parent.state, var, self.update_alpha) self._parent.register_data_provider(self) self.update() def iter_data(self, name): if self._table and self._table.has_col(name): yield self._table.get_col(name) def set_table(self, table): self._table = table self._istate += 1 if self._pipes is not None and self._istate != self._istate_view: self._clear_pipes() self._update_controls() def get_size_parameter_extra_entries(self): return [] def get_color_parameter_extra_entries(self): return [] def update_sizes(self, *args): self._istate += 1 self.update() def unset_parent(self): self.unbind_state() if self._parent: self._parent.unregister_data_provider(self) self._clear_pipes() if self._controls: self._parent.remove_panel(self._controls) self._controls = None self._parent.update_view() self._parent = None def _clear_pipes(self): if self._pipes is not None: for p in self._pipes: self._parent.remove_actor(p.actor) self._pipes = None if self._pipe_maps is not None: self._pipe_maps = None def _init_pipes_scatter(self): state = self._state points = self._table.get_col('xyz') self._pipes = [] self._pipe_maps = [] if state.size_parameter: sizes = self._table.get_col(state.size_parameter) isizes = inormalize( sizes, self._isize_min, self._isize_max) for i in range(self._isize_min, self._isize_max+1): b = isizes == i p = ScatterPipe(points[b].copy()) self._pipes.append(p) self._pipe_maps.append(b) else: self._pipes.append( ScatterPipe(points)) self._pipe_maps.append( num.ones(points.shape[0], dtype=bool)) def _init_pipes_beachball(self): state = self._state self._pipes = [] tab = self._table positions = tab.get_col('xyz') if tab.has_col('m6'): m6s = tab.get_col('m6') else: m6s = num.zeros((tab.get_nrows(), 6)) m6s[:, 3] = 1.0 if state.size_parameter: sizes = tab.get_col(state.size_parameter) else: sizes = num.ones(tab.get_nrows()) if state.color_parameter: values = self._table.get_col(state.color_parameter) else: values = num.zeros(tab.get_nrows()) rsizes = inormalize( sizes, self._isize_min, self._isize_max, discrete=False) * 0.005 pipe = BeachballPipe(positions, m6s, rsizes, values, self._parent.ren) self._pipes = [pipe] def _update_pipes_scatter(self): state = self._state for i, p in enumerate(self._pipes): self._parent.add_actor(p.actor) p.set_size(state.size * (self._isize_min + i)**1.3) if state.color_parameter: values = self._table.get_col(state.color_parameter) if num.issubdtype(values.dtype, num.string_): values = string_to_sorted_idx(values) self.cpt_handler._values = values self.cpt_handler.update_cpt() cpt = copy.deepcopy( self.cpt_handler._cpts[self._state.cpt.effective_cpt_name]) colors2 = cpt(values) colors2 = colors2 / 255. for m, p in zip(self._pipe_maps, self._pipes): p.set_colors(colors2[m, :]) for p in self._pipes: p.set_symbol(state.symbol) def _update_pipes_beachball(self): state = self._state p = self._pipes[0] self._parent.add_actor(p.actor) p.set_size_factor(state.size * 0.005) def _init_pipes(self): if self._state.symbol == 'beachball': self._init_pipes_beachball() else: self._init_pipes_scatter() def _update_pipes(self): if self._state.symbol == 'beachball': self._update_pipes_beachball() else: self._update_pipes_scatter() def update(self, *args): state = self._state if self._pipes is not None and self._istate != self._istate_view: self._clear_pipes() if not state.visible: if self._pipes is not None: for p in self._pipes: self._parent.remove_actor(p.actor) else: if self._istate != self._istate_view and self._table: self._init_pipes() self._istate_view = self._istate if self._pipes is not None: self._update_pipes() self.update_alpha() # TODO: only if needed? self._parent.update_view() def update_alpha(self, *args, mask=None): if self._state.symbol == 'beachball': return if self._pipes is None: return time = self._table.get_col('time') depth = self._table.get_col('depth') depth_mask = num.ones(time.size, dtype=bool) if self._state.depth_min is not None: depth_mask &= depth >= self._state.depth_min if self._state.depth_max is not None: depth_mask &= depth <= self._state.depth_max tmin = self._parent.state.tmin_effective tmax = self._parent.state.tmax_effective if tmin is not None: m1 = time < tmin else: m1 = num.zeros(time.size, dtype=bool) if tmax is not None: m3 = tmax < time else: m3 = num.zeros(time.size, dtype=bool) m2 = num.logical_not(num.logical_or(m1, m3)) value_low = self._state.time_masking_opacity f1, f2, f3 = MaskingModeChoice.get_factors( self._state.time_masking_mode, value_low) amp = num.ones(time.size, dtype=num.float64) amp[m1] = f1 amp[m3] = f3 if None in (tmin, tmax): amp[m2] = 1.0 else: if self._state.time_masking_shape == 'rect': amp[m2] == 1.0 elif self._state.time_masking_shape == 'linear': amp[m2] = time[m2] amp[m2] -= tmin amp[m2] /= (tmax - tmin) elif self._state.time_masking_shape == 'quadratic': amp[m2] = time[m2] amp[m2] -= tmin amp[m2] /= (tmax - tmin) amp[m2] **= 2 if f1 != 0.0: amp[m2] *= (1.0 - value_low) amp[m2] += value_low amp *= depth_mask for m, p in zip(self._pipe_maps, self._pipes): p.set_alpha(amp[m]) self._parent.update_view() def _get_table_widgets_start(self): return 0 def _get_controls(self): if self._controls is None: from ..state import state_bind_slider, state_bind_slider_float, \ state_bind_combobox, state_bind_lineedit frame = qw.QFrame() layout = qw.QGridLayout() frame.setLayout(layout) iy = self._get_table_widgets_start() layout.addWidget(qw.QLabel('Size'), iy, 0) slider = qw.QSlider(qc.Qt.Horizontal) slider.setSizePolicy( qw.QSizePolicy( qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed)) slider.setMinimum(0) slider.setMaximum(100) layout.addWidget(slider, iy, 1) state_bind_slider(self, self._state, 'size', slider, factor=0.1) iy += 1 layout.addWidget(qw.QLabel('Size Scaling'), iy, 0) cb = qw.QComboBox() layout.addWidget(cb, iy, 1) state_bind_combobox( self, self._state, 'size_parameter', cb) self._size_combobox = cb iy += 1 layout.addWidget(qw.QLabel('Color'), iy, 0) cb = qw.QComboBox() layout.addWidget(cb, iy, 1) state_bind_combobox( self, self._state, 'color_parameter', cb) self._color_combobox = cb self.cpt_handler.cpt_controls( self._parent, self._state.cpt, layout) iy = layout.rowCount() + 1 layout.addWidget(qw.QLabel('Symbol'), iy, 0) cb = common.string_choices_to_combobox(SymbolChoice) layout.addWidget(cb, iy, 1) state_bind_combobox( self, self._state, 'symbol', cb) iy += 1 layout.addWidget(qw.QLabel('Depth Min [km]'), iy, 0) slider = gui_util.QSliderFloat(qc.Qt.Horizontal) slider.setSizePolicy( qw.QSizePolicy( qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed)) slider.setMinimumFloat(-60*km) slider.setMaximumFloat(700*km) layout.addWidget(slider, iy, 1) state_bind_slider_float( self, self._state, 'depth_min', slider, min_is_none=True) self._depth_min_slider = slider le = qw.QLineEdit() layout.addWidget(le, iy, 2) state_bind_lineedit( self, self._state, 'depth_min', le, from_string=lambda s: None if s == 'off' else float(s)*1000., to_string=lambda v: 'off' if v is None else str(v/1000.)) self._depth_min_lineedit = le iy += 1 layout.addWidget(qw.QLabel('Depth Max [km]'), iy, 0) slider = gui_util.QSliderFloat(qc.Qt.Horizontal) slider.setSizePolicy( qw.QSizePolicy( qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed)) slider.setMinimumFloat(-60*km) slider.setMaximumFloat(700*km) layout.addWidget(slider, iy, 1) state_bind_slider_float( self, self._state, 'depth_max', slider, max_is_none=True) self._depth_max_slider = slider le = qw.QLineEdit() layout.addWidget(le, iy, 2) state_bind_lineedit( self, self._state, 'depth_max', le, from_string=lambda s: None if s == 'off' else float(s)*1000., to_string=lambda v: 'off' if v is None else str(v/1000.)) self._depth_max_lineedit = le iy += 1 layout.addWidget(qw.QLabel('Time Masking Opacity'), iy, 0) slider = qw.QSlider(qc.Qt.Horizontal) slider.setSizePolicy( qw.QSizePolicy( qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed)) slider.setMinimum(0) slider.setMaximum(100) layout.addWidget(slider, iy, 1) state_bind_slider( self, self._state, 'time_masking_opacity', slider, factor=0.01) iy += 1 layout.addWidget(qw.QLabel('Time Masking Shape'), iy, 0) cb = common.string_choices_to_combobox(MaskingShapeChoice) layout.addWidget(cb, iy, 1) state_bind_combobox(self, self._state, 'time_masking_shape', cb) iy += 1 layout.addWidget(qw.QLabel('Time Masking Mode'), iy, 0) cb = common.string_choices_to_combobox(MaskingModeChoice) layout.addWidget(cb, iy, 1) state_bind_combobox(self, self._state, 'time_masking_mode', cb) iy += 1 layout.addWidget(qw.QFrame(), iy, 0, 1, 3) self._controls = frame self._update_controls() return self._controls def _update_controls(self): for (cb, get_extra_entries) in [ (self._color_combobox, self.get_color_parameter_extra_entries), (self._size_combobox, self.get_size_parameter_extra_entries)]: if cb is not None: cb.clear() have = set() for s in get_extra_entries(): if s not in have: cb.insertItem(len(have), s) have.add(s) if self._table is not None: for s in self._table.get_col_names(): h = self._table.get_header(s) if h.get_ncols() == 1 and s not in have: cb.insertItem(len(have), s) have.add(s) self.cpt_handler._update_cpt_combobox() self.cpt_handler._update_cptscale_lineedit() if self._table is not None and self._table.has_col('depth'): depth = self._table.get_col('depth') if depth.size > 0: depth_min = depth.min() depth_max = depth.max() for wdg in (self._depth_min_slider, self._depth_max_slider): wdg.setMinimumFloat(depth_min) wdg.setMaximumFloat(depth_max) __all__ = [ 'TableElement', 'TableState', ]