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

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

import os
import base64

import numpy as num

from pyrocko.plot import automap, mpl_get_cmap_names, mpl_get_cmap
from pyrocko.guts import String, Float, StringChoice, Bool, Int
from pyrocko.plot import AutoScaler, AutoScaleMode
from pyrocko.dataset import topo

from pyrocko.gui.talkie import (TalkieRoot, TalkieConnectionOwner,
                                has_computed, computed)

from pyrocko.gui.qt_compat import qc, qw
from pyrocko.gui.vtk_util import cpt_to_vtk_lookuptable, ColorbarPipe


from .. import common
from ..state import \
    state_bind_combobox, state_bind, state_bind_checkbox, state_bind_slider, \
    state_bind_lineedit


mpl_cmap_blacklist = [
    "prism", "flag",
    "Accent", "Dark2",
    "Paired", "Pastel1", "Pastel2",
    "Set1", "Set2", "Set3",
    "tab10", "tab20", "tab20b", "tab20c"
]


def get_mpl_cmap_choices():
    names = mpl_get_cmap_names()
    for cmap_name in mpl_cmap_blacklist:
        try:
            names.remove(cmap_name)
            names.remove("%s_r" % cmap_name)
        except ValueError:
            pass

    return names


def random_id():
    return base64.urlsafe_b64encode(os.urandom(16)).decode('ascii')


[docs]class ElementState(TalkieRoot): element_id = String.T() def __init__(self, **kwargs): if 'element_id' not in kwargs: kwargs['element_id'] = random_id() TalkieRoot.__init__(self, **kwargs)
class Element(TalkieConnectionOwner): def __init__(self): TalkieConnectionOwner.__init__(self) self._parent = None self._state = None def remove(self): if self._parent and self._state: self._parent.state.elements.remove(self._state) def set_parent(self, parent): self._parent = parent def unset_parent(self): print(self) raise NotImplementedError def bind_state(self, state): self._state = state def unbind_state(self): self.talkie_disconnect_all() self._state = None def update_visibility(self, visible): self._state.visible = visible def get_title_label(self): title_label = common.MyDockWidgetTitleBarLabel(self.get_name()) def update_label(*args): title_label.set_slug(self._state.element_id) self.talkie_connect( self._state, 'element_id', update_label) update_label() return title_label def get_title_control_remove(self): button = common.MyDockWidgetTitleBarButton('\u2716') button.setStatusTip('Remove Element') button.clicked.connect(self.remove) return button def get_title_control_visible(self): assert hasattr(self._state, 'visible') button = common.MyDockWidgetTitleBarButtonToggle('\u2b53', '\u2b54') button.setStatusTip('Toggle Element Visibility') button.toggled.connect(self.update_visibility) def set_button_checked(*args): button.blockSignals(True) button.set_checked(self._state.visible) button.blockSignals(False) set_button_checked() self.talkie_connect( self._state, 'visible', set_button_checked) return button
[docs]class CPTChoice(StringChoice): choices = ['slip_colors'] + get_mpl_cmap_choices()
[docs]class ColorBarPositionChoice(StringChoice): choices = ['bottom-left', 'bottom-right', 'top-left', 'top-right']
[docs]@has_computed class CPTState(ElementState): cpt_name = String.T(default=CPTChoice.choices[0]) cpt_mode = String.T(default=AutoScaleMode.choices[1]) cpt_scale_min = Float.T(optional=True) cpt_scale_max = Float.T(optional=True) cpt_revert = Bool.T(default=False) cbar_show = Bool.T(default=True) cbar_position = ColorBarPositionChoice.T(default='bottom-right') cbar_annotation_lightness = Float.T(default=1.0) cbar_annotation_fontsize = Float.T(default=0.03) cbar_annotation_ndigits = Int.T(default=2) cbar_annotation_nlabels = Int.T(default=5) cbar_height = Float.T(default=1.) cbar_width = Float.T(default=1.) @computed(['cpt_name', 'cpt_revert']) def effective_cpt_name(self): if self.cpt_revert: return '%s_r' % self.cpt_name else: return self.cpt_name
class CPTHandler(Element): def __init__(self): Element.__init__(self) self._cpts = {} self._autoscaler = None self._lookuptable = None self._cpt_combobox = None self._values = None self._state = None self._cpt_scale_lineedit = None self._cbar_pipe = None def bind_state(self, cpt_state, update_function): for state_attr in [ 'effective_cpt_name', 'cpt_mode', 'cpt_scale_min', 'cpt_scale_max', 'cbar_show', 'cbar_position', 'cbar_annotation_lightness', 'cbar_annotation_fontsize', 'cbar_height', 'cbar_width']: self.talkie_connect( cpt_state, state_attr, update_function) self._state = cpt_state def unbind_state(self): Element.unbind_state(self) self._cpts = {} self._lookuptable = None self._values = None self._autoscaler = None def open_cpt_load_dialog(self): caption = 'Select one *.cpt file to open' fns, _ = qw.QFileDialog.getOpenFileNames( self._parent, caption) if fns: self.load_cpt_file(fns[0]) def load_cpt_file(self, path): cpt_name = 'USR' + os.path.basename(path).split('.')[0] self._cpts.update([(cpt_name, automap.read_cpt(path))]) self._state.cpt_name = cpt_name self._update_cpt_combobox() self.update_cpt() def _update_cpt_combobox(self): from pyrocko import config conf = config.config() if self._cpt_combobox is None: raise ValueError('CPT combobox needs init before updating!') cb = self._cpt_combobox if cb is not None: cb.clear() for s in CPTChoice.choices: if s not in self._cpts: try: cpt = automap.read_cpt(topo.cpt(s)) except Exception: cmap = mpl_get_cmap(s) cpt = automap.CPT.from_numpy(cmap(range(256))[:, :-1]) self._cpts.update([(s, cpt)]) cpt_dir = conf.colortables_dir if os.path.isdir(cpt_dir): for f in [ f for f in os.listdir(cpt_dir) if f.lower().endswith('.cpt')]: s = 'USR' + os.path.basename(f).split('.')[0] self._cpts.update( [(s, automap.read_cpt(os.path.join(cpt_dir, f)))]) for i, (s, cpt) in enumerate(self._cpts.items()): if s[-2::] != "_r": cb.insertItem(i, s, qc.QVariant(self._cpts[s])) cb.setItemData(i, qc.QVariant(s), qc.Qt.ToolTipRole) cb.setCurrentIndex(cb.findText(self._state.effective_cpt_name)) def _update_cptscale_lineedit(self): le = self._cpt_scale_lineedit if le is not None: le.clear() self._cptscale_to_lineedit(self._state, le) def _cptscale_to_lineedit(self, state, widget): # sel = widget.selectedText() == widget.text() crange = (None, None) if self._lookuptable is not None: crange = self._lookuptable.GetRange() if state.cpt_scale_min is not None and state.cpt_scale_max is not None: crange = state.cpt_scale_min, state.cpt_scale_max fmt = ', '.join(['%s' if item is None else '%g' for item in crange]) widget.setText(fmt % crange) # if sel: # widget.selectAll() def update_cpt(self, mask_zeros=False): state = self._state if self._autoscaler is None: self._autoscaler = AutoScaler() if self._cpt_scale_lineedit: if state.cpt_mode == 'off': self._cpt_scale_lineedit.setEnabled(True) else: self._cpt_scale_lineedit.setEnabled(False) if state.cpt_scale_min is not None: state.cpt_scale_min = None if state.cpt_scale_max is not None: state.cpt_scale_max = None if state.effective_cpt_name is not None and self._values is not None: if self._values.size == 0: vscale = (0., 1.) else: vscale = (num.nanmin(self._values), num.nanmax(self._values)) vmin, vmax = None, None if None not in (state.cpt_scale_min, state.cpt_scale_max): vmin, vmax = state.cpt_scale_min, state.cpt_scale_max else: vmin, vmax, _ = self._autoscaler.make_scale( vscale, override_mode=state.cpt_mode) self._cpts[state.effective_cpt_name].scale(vmin, vmax) cpt = self._cpts[state.effective_cpt_name] vtk_lut = cpt_to_vtk_lookuptable(cpt, mask_zeros=mask_zeros) vtk_lut.SetNanColor(0.0, 0.0, 0.0, 0.0) self._lookuptable = vtk_lut self._update_cptscale_lineedit() elif state.effective_cpt_name and self._values is None: raise ValueError('No values passed to colormapper!') def update_cbar(self, display_parameter): state = self._state lut = self._lookuptable if state.cbar_show and lut: sx, sy = 1, 1 off = 0.08 * sy pos = { 'top-left': (off, sy/2 + off, 0, 2), 'top-right': (sx - off, sy/2 + off, 2, 2), 'bottom-left': (off, off, 0, 0), 'bottom-right': (sx - off, off, 2, 0)} x, y, _, _ = pos[state.cbar_position] if not isinstance(self._cbar_pipe, ColorbarPipe): self._cbar_pipe = ColorbarPipe( parent_pipe=self._parent, lut=lut, cbar_title=display_parameter, position=(x, y)) self._parent.add_actor(self._cbar_pipe.actor) else: self._cbar_pipe.set_lookuptable(lut) self._cbar_pipe.set_title(display_parameter) self._cbar_pipe._set_position(x, y) sx, sy = self._parent.gui_state.size fontsize = round(state.cbar_annotation_fontsize*sy) lightness = 0.9 * state.cbar_annotation_lightness self._cbar_pipe._format_text( lightness=lightness, fontsize=fontsize) height_px = int(round(sy / 3 * state.cbar_height)) width_px = int(round(50 * state.cbar_width)) self._cbar_pipe._format_size(height_px, width_px) ndigits = state.cbar_annotation_ndigits nlabels = state.cbar_annotation_nlabels self._cbar_pipe.set_labels(nlabels, ndigits) else: self.remove_cbar_pipe() def remove_cbar_pipe(self): if self._cbar_pipe is not None: self._parent.remove_actor(self._cbar_pipe.actor) self._cbar_pipe = None def cpt_controls(self, parent, state, layout): self._parent = parent iy = layout.rowCount() + 1 layout.addWidget(qw.QLabel('Color Map'), iy, 0) cb = common.CPTComboBox() layout.addWidget(cb, iy, 1) state_bind_combobox( self, state, 'cpt_name', cb) self._cpt_combobox = cb pb = qw.QPushButton('Load CPT') layout.addWidget(pb, iy, 2) pb.clicked.connect(self.open_cpt_load_dialog) iy += 1 layout.addWidget(qw.QLabel('Color Scaling'), iy, 0) cb = common.string_choices_to_combobox(AutoScaleMode) layout.addWidget(cb, iy, 1) state_bind_combobox( self, state, 'cpt_mode', cb) lescale = qw.QLineEdit() lescale.setEnabled(False) layout.addWidget(lescale, iy, 2) state_bind( self, state, ['cpt_scale_min', 'cpt_scale_max'], _lineedit_to_cptscale, lescale, [lescale.editingFinished, lescale.returnPressed], self._cptscale_to_lineedit) self._cpt_scale_lineedit = lescale iy += 1 cb = qw.QCheckBox('Revert') layout.addWidget(cb, iy, 1) state_bind_checkbox(self, state, 'cpt_revert', cb) # color bar iy += 1 layout.addWidget(qw.QLabel('Color Bar'), iy, 0) chb = qw.QCheckBox('show') layout.addWidget(chb, iy, 1) state_bind_checkbox(self, self._state, 'cbar_show', chb) cb = common.string_choices_to_combobox( ColorBarPositionChoice) layout.addWidget(cb, iy, 2) state_bind_combobox( self, self._state, 'cbar_position', cb) # cbar text iy += 1 layout.addWidget(qw.QLabel('Lightness'), iy, 1) slider = qw.QSlider(qc.Qt.Horizontal) slider.setSizePolicy( qw.QSizePolicy( qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed)) slider.setMinimum(0) slider.setMaximum(1000) layout.addWidget(slider, iy, 2) state_bind_slider( self, self._state, 'cbar_annotation_lightness', slider, factor=0.001) iy += 1 layout.addWidget(qw.QLabel('Fontsize'), iy, 1) 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, 2) state_bind_slider( self, self._state, 'cbar_annotation_fontsize', slider, factor=0.001) # cbar size iy += 1 layout.addWidget(qw.QLabel('Height'), iy, 1) slider = qw.QSlider(qc.Qt.Horizontal) slider.setSizePolicy( qw.QSizePolicy( qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed)) slider.setMinimum(1) slider.setMaximum(200) layout.addWidget(slider, iy, 2) state_bind_slider( self, state, 'cbar_height', slider, factor=0.01) iy += 1 layout.addWidget(qw.QLabel('Width'), iy, 1) slider = qw.QSlider(qc.Qt.Horizontal) slider.setSizePolicy( qw.QSizePolicy( qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed)) slider.setMinimum(1) slider.setMaximum(200) layout.addWidget(slider, iy, 2) state_bind_slider( self, state, 'cbar_width', slider, factor=0.01) # labels if False: # Does not work yet, state binding is broken iy += 1 layout.addWidget(qw.QLabel('NDigits'), iy, 1) le = qw.QLineEdit() layout.addWidget(le, iy, 2) state_bind_lineedit( self, state, 'cbar_annotation_ndigits', le) iy += 1 layout.addWidget(qw.QLabel('NLabels'), iy, 1) le = qw.QLineEdit() layout.addWidget(le, iy, 2) state_bind_lineedit( self, state, 'cbar_annotation_nlabels', le) def _lineedit_to_cptscale(widget, cpt_state): s = str(widget.text()) s = s.replace(',', ' ') crange = tuple((float(i) for i in s.split())) crange = tuple(( crange[0], crange[0]+0.01 if crange[0] >= crange[1] else crange[1])) try: cpt_state.cpt_scale_min, cpt_state.cpt_scale_max = crange except Exception: raise ValueError( 'need two numerical values: <vmin>, <vmax>') __all__ = [ 'Element', 'ElementState', 'random_id', ]