# https://pyrocko.org - GPLv3
#
# The Pyrocko Developers, 21st Century
# ---|P------/S----------~Lg----------
import logging
import numpy as num
from pyrocko import util
from pyrocko.guts import StringChoice, Float, List, Bool, Timestamp, Tuple, \
Duration, Object, get_elements, set_elements, path_to_str, clone
from pyrocko.color import Color, interpolate as interpolate_color
from pyrocko.gui import talkie
from pyrocko.gui import util as gui_util
from . import common, light
guts_prefix = 'sparrow'
logger = logging.getLogger('pyrocko.gui.sparrow.state')
[docs]class FocalPointChoice(StringChoice):
choices = ['center', 'target']
[docs]class ShadingChoice(StringChoice):
choices = ['flat', 'gouraud', 'phong', 'pbr']
[docs]class LightingChoice(StringChoice):
choices = light.get_lighting_theme_names()
[docs]class ViewerGuiState(talkie.TalkieRoot):
panels_visible = Bool.T(default=True)
size = Tuple.T(2, Float.T(), default=(100., 100.))
fixed_size = Tuple.T(2, Float.T(), optional=True)
focal_point = FocalPointChoice.T(default='center')
detached = Bool.T(default=False)
tcursor = Timestamp.T(optional=True)
def next_focal_point(self):
choices = FocalPointChoice.choices
ii = choices.index(self.focal_point)
self.focal_point = choices[(ii+1) % len(choices)]
[docs]class Background(Object):
color = Color.T(default=Color.D('black'))
def vtk_apply(self, ren):
ren.GradientBackgroundOff()
ren.SetBackground(*self.color.rgb)
def __str__(self):
return str(self.color)
@property
def color_top(self):
return self.color
@property
def color_bottom(self):
return self.color
# def __eq__(self, other):
# print('in==', self.color.rgb, other.color.rgb)
# return type(self) is type(other) and self.color == other.color
[docs]class BackgroundGradient(Background):
color_top = Color.T(default=Color.D('skyblue1'))
color_bottom = Color.T(default=Color.D('white'))
def vtk_apply(self, ren):
ren.GradientBackgroundOn()
ren.SetBackground(*self.color_bottom.rgb)
ren.SetBackground2(*self.color_top.rgb)
def __str__(self):
return '%s - %s' % (self.color_top, self.color_bottom)
# def __eq__(self, other):
# return type(self) is type(other) and \
# self.color_top == other.color_top and \
# self.color_bottom == other.color_bottom
def interpolate_background(a, b, blend):
if type(a) is Background and type(b) is Background:
return Background(color=interpolate_color(a.color, b.color, blend))
else:
return BackgroundGradient(
color_top=interpolate_color(
a.color_top, b.color_top, blend),
color_bottom=interpolate_color(
a.color_bottom, b.color_bottom, blend))
[docs]@talkie.has_computed
class ViewerState(talkie.TalkieRoot):
lat = Float.T(default=0.0)
lon = Float.T(default=0.0)
depth = Float.T(default=0.0)
strike = Float.T(default=90.0)
dip = Float.T(default=0.0)
distance = Float.T(default=3.0)
elements = List.T(talkie.Talkie.T())
tmin = Timestamp.T(optional=True)
tmax = Timestamp.T(optional=True)
tduration = Duration.T(optional=True)
tposition = Float.T(default=0.0)
lighting = LightingChoice.T(default=LightingChoice.choices[0])
background = Background.T(default=Background.D(color=Color('black')))
@talkie.computed(['tmin', 'tmax', 'tduration', 'tposition'])
def tmin_effective(self):
return common.tmin_effective(
self.tmin, self.tmax, self.tduration, self.tposition)
@talkie.computed(['tmin', 'tmax', 'tduration', 'tposition'])
def tmax_effective(self):
return common.tmax_effective(
self.tmin, self.tmax, self.tduration, self.tposition)
def sort_elements(self):
self.elements.sort(key=lambda el: el.element_id)
def state_bind(
owner, state, paths, update_state,
widget, signals, update_widget, attribute=None):
def make_wrappers(widget):
def wrap_update_widget(*args):
if attribute:
update_widget(state, attribute, widget)
else:
update_widget(state, widget)
common.de_errorize(widget)
def wrap_update_state(*args):
try:
if attribute:
update_state(widget, state, attribute)
else:
update_state(widget, state)
common.de_errorize(widget)
except Exception as e:
logger.warn('Caught exception: %s' % e)
common.errorize(widget)
return wrap_update_widget, wrap_update_state
wrap_update_widget, wrap_update_state = make_wrappers(widget)
for sig in signals:
sig.connect(wrap_update_state)
for path in paths:
owner.talkie_connect(state, path, wrap_update_widget)
wrap_update_widget()
def state_bind_slider(
owner, state, path, widget, factor=1.,
dtype=float,
min_is_none=False,
max_is_none=False):
viewer = common.get_viewer()
widget.sliderPressed.connect(viewer.disable_capture)
widget.sliderReleased.connect(viewer.enable_capture)
def make_funcs():
def update_state(widget, state):
val = widget.value()
if (min_is_none and val == widget.minimum()) \
or (max_is_none and val == widget.maximum()):
state.set(path, None)
else:
viewer.status('%g' % (val * factor))
state.set(path, dtype(val * factor))
def update_widget(state, widget):
val = state.get(path)
widget.blockSignals(True)
if min_is_none and val is None:
widget.setValue(widget.minimum())
elif max_is_none and val is None:
widget.setValue(widget.maximum())
else:
widget.setValue(int(state.get(path) * 1. / factor))
widget.blockSignals(False)
return update_state, update_widget
update_state, update_widget = make_funcs()
state_bind(
owner, state, [path], update_state, widget, [widget.valueChanged],
update_widget)
def state_bind_slider_float(
owner, state, path, widget,
min_is_none=False,
max_is_none=False):
assert isinstance(widget, gui_util.QSliderFloat)
viewer = common.get_viewer()
widget.sliderPressed.connect(viewer.disable_capture)
widget.sliderReleased.connect(viewer.enable_capture)
def make_funcs():
def update_state(widget, state):
val = widget.valueFloat()
if (min_is_none and val == widget.minimumFloat()) \
or (max_is_none and val == widget.maximumFloat()):
state.set(path, None)
else:
viewer.status('%g' % (val))
state.set(path, val)
def update_widget(state, widget):
val = state.get(path)
widget.blockSignals(True)
if min_is_none and val is None:
widget.setValueFloat(widget.minimumFloat())
elif max_is_none and val is None:
widget.setValueFloat(widget.maximumFloat())
else:
widget.setValueFloat(state.get(path))
widget.blockSignals(False)
return update_state, update_widget
update_state, update_widget = make_funcs()
state_bind(
owner, state, [path], update_state, widget, [widget.valueChanged],
update_widget)
def state_bind_spinbox(owner, state, path, widget, factor=1., dtype=float):
return state_bind_slider(owner, state, path, widget, factor, dtype)
def state_bind_combobox(owner, state, path, widget):
def make_funcs():
def update_state(widget, state):
state.set(path, str(widget.currentText()))
def update_widget(state, widget):
widget.blockSignals(True)
val = state.get(path)
for i in range(widget.count()):
if str(widget.itemText(i)) == val:
widget.setCurrentIndex(i)
widget.blockSignals(False)
return update_state, update_widget
update_state, update_widget = make_funcs()
state_bind(
owner, state, [path], update_state, widget, [widget.activated],
update_widget)
def state_bind_combobox_background(owner, state, path, widget):
def make_funcs():
def update_state(widget, state):
values = str(widget.currentText()).split(' - ')
if len(values) == 1:
state.set(
path,
Background(color=Color(values[0])))
elif len(values) == 2:
state.set(
path,
BackgroundGradient(
color_top=Color(values[0]),
color_bottom=Color(values[1])))
def update_widget(state, widget):
widget.blockSignals(True)
val = str(state.get(path))
for i in range(widget.count()):
if str(widget.itemText(i)) == val:
widget.setCurrentIndex(i)
widget.blockSignals(False)
return update_state, update_widget
update_state, update_widget = make_funcs()
state_bind(
owner, state, [path], update_state, widget, [widget.activated],
update_widget)
def state_bind_combobox_color(owner, state, path, widget):
def make_funcs():
def update_state(widget, state):
value = str(widget.currentText())
state.set(path, Color(value))
def update_widget(state, widget):
widget.blockSignals(True)
val = str(state.get(path))
for i in range(widget.count()):
if str(widget.itemText(i)) == val:
widget.setCurrentIndex(i)
widget.blockSignals(False)
return update_state, update_widget
update_state, update_widget = make_funcs()
state_bind(
owner, state, [path], update_state, widget, [widget.activated],
update_widget)
def state_bind_checkbox(owner, state, path, widget):
def make_funcs():
def update_state(widget, state):
state.set(path, bool(widget.isChecked()))
def update_widget(state, widget):
widget.blockSignals(True)
widget.setChecked(state.get(path))
widget.blockSignals(False)
return update_state, update_widget
update_state, update_widget = make_funcs()
state_bind(
owner, state, [path], update_state, widget, [widget.toggled],
update_widget)
def state_bind_lineedit(
owner, state, path, widget, from_string=str, to_string=str):
def make_funcs():
def update_state(widget, state):
state.set(path, from_string(widget.text()))
def update_widget(state, widget):
widget.blockSignals(True)
widget.setText(to_string(state.get(path)))
widget.blockSignals(False)
return update_state, update_widget
update_state, update_widget = make_funcs()
state_bind(
owner,
state, [path], update_state,
widget, [widget.editingFinished, widget.returnPressed], update_widget)
def interpolateables(state_a, state_b):
animate = []
for tag, path, values in state_b.diff(state_a):
if tag == 'set':
ypath = path_to_str(path)
v_new = get_elements(state_b, ypath)[0]
v_old = values
for type in [float, Color, Background]:
if isinstance(v_old, type) and isinstance(v_new, type):
animate.append((ypath, v_old, v_new))
return animate
def interpolate(times, states, times_inter):
assert len(times) == len(states)
states_inter = []
for i in range(len(times) - 1):
state_a = states[i]
state_b = states[i+1]
time_a = times[i]
time_b = times[i+1]
animate = interpolateables(state_a, state_b)
if i == 0:
times_inter_this = times_inter[num.logical_and(
time_a <= times_inter, times_inter <= time_b)]
else:
times_inter_this = times_inter[num.logical_and(
time_a < times_inter, times_inter <= time_b)]
for time_inter in times_inter_this:
state = clone(state_b)
if time_b == time_a:
blend = 0.
else:
blend = (time_inter - time_a) / (time_b - time_a)
for ypath, v_old, v_new in animate:
if isinstance(v_old, float) and isinstance(v_new, float):
if ypath == 'strike':
if v_new - v_old > 180.:
v_new -= 360.
elif v_new - v_old < -180.:
v_new += 360.
if ypath != 'distance':
v_inter = v_old + blend * (v_new - v_old)
else:
v_old = num.log(v_old)
v_new = num.log(v_new)
v_inter = v_old + blend * (v_new - v_old)
v_inter = num.exp(v_inter)
set_elements(state, ypath, v_inter)
else:
set_elements(state, ypath, v_new)
states_inter.append(state)
return states_inter
class Interpolator(object):
def __init__(self, times, states, fps=25.):
assert len(times) == len(states)
self.dt = 1.0 / fps
self.tmin = times[0]
self.tmax = times[-1]
times_inter = util.arange2(
self.tmin, self.tmax, self.dt, error='floor')
times_inter[-1] = times[-1]
if times_inter.size == 1:
self._states_inter = [clone(states[-1])]
return
states_inter = []
for i in range(len(times) - 1):
state_a = states[i]
state_b = states[i+1]
time_a = times[i]
time_b = times[i+1]
animate = interpolateables(state_a, state_b)
if i == 0:
times_inter_this = times_inter[num.logical_and(
time_a <= times_inter, times_inter <= time_b)]
else:
times_inter_this = times_inter[num.logical_and(
time_a < times_inter, times_inter <= time_b)]
for time_inter in times_inter_this:
state = clone(state_b)
if time_b == time_a:
blend = 0.
else:
blend = (time_inter - time_a) / (time_b - time_a)
for ypath, v_old, v_new in animate:
if isinstance(v_old, float) and isinstance(v_new, float):
if ypath in ('lon', 'strike'):
if v_new - v_old > 180.:
v_new -= 360.
elif v_new - v_old < -180.:
v_new += 360.
if ypath != 'distance':
v_inter = v_old + blend * (v_new - v_old)
else:
v_old = num.log(v_old)
v_new = num.log(v_new)
v_inter = v_old + blend * (v_new - v_old)
v_inter = num.exp(v_inter)
set_elements(state, ypath, v_inter)
elif isinstance(v_old, Color) and isinstance(v_new, Color):
v_inter = interpolate_color(v_old, v_new, blend)
set_elements(state, ypath, v_inter)
elif isinstance(v_old, Background) \
and isinstance(v_new, Background):
v_inter = interpolate_background(v_old, v_new, blend)
set_elements(state, ypath, v_inter)
else:
set_elements(state, ypath, v_new)
states_inter.append(state)
self._states_inter = states_inter
def __call__(self, t):
itime = int(round((t - self.tmin) / self.dt))
itime = min(max(0, itime), len(self._states_inter)-1)
return self._states_inter[itime]