1# https://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
6import logging
7import numpy as num
9from pyrocko.guts import Bool, String, load, Float
10from pyrocko.geometry import arr_vertices, arr_faces
11from pyrocko.gui.qt_compat import qw, qc
12from pyrocko.gui.vtk_util import TrimeshPipe, OutlinesPipe, Color
13from pyrocko.orthodrome import geographic_midpoint
15from pyrocko.model import Geometry
17from . import base
18from .. import common
19from ..state import state_bind_combobox, state_bind_slider, \
20 state_bind_combobox_color
23logger = logging.getLogger('geometry')
25guts_prefix = 'sparrow'
27km = 1e3
30class GeometryState(base.ElementState):
31 opacity = Float.T(default=1.0)
32 visible = Bool.T(default=True)
33 geometry = Geometry.T(default=None, optional=True)
34 display_parameter = String.T(default="")
35 time = Float.T(default=0., optional=True)
36 cpt = base.CPTState.T(default=base.CPTState.D())
37 color = Color.T(default=Color.D('white'))
38 line_width = Float.T(default=1.0)
40 def create(self):
41 element = GeometryElement()
42 return element
45class GeometryElement(base.Element):
47 def __init__(self):
48 base.Element.__init__(self)
49 self._parent = None
50 self._state = None
51 self._controls = None
53 self._pipe = None
54 self._cbar_pipe = None
55 self._outlines_pipe = []
57 self.cpt_handler = base.CPTHandler()
59 def remove(self):
60 if self._parent and self._state:
61 self._parent.state.elements.remove(self._state)
63 def init_pipeslots(self):
64 if not self._pipe:
65 self._pipe.append([])
67 def remove_pipes(self):
68 if self._pipe is not None:
69 self._parent.remove_actor(self._pipe.actor)
71 if len(self._outlines_pipe) > 0:
72 for pipe in self._outlines_pipe:
73 self._parent.remove_actor(pipe.actor)
75 self._pipe = None
76 self._outlines_pipe = []
78 def set_parent(self, parent):
79 self._parent = parent
80 self._parent.add_panel(
81 self.get_title_label(),
82 self._get_controls(),
83 visible=True,
84 title_controls=[
85 self.get_title_control_remove(),
86 self.get_title_control_visible()])
88 self.talkie_connect(
89 self._parent.state,
90 ['tmin', 'tmax', 'lat', 'lon', 'tmax_effective'],
91 self.update)
93 self.update()
95 def unset_parent(self):
96 self.unbind_state()
97 if self._parent:
98 if self._pipe or self._cbar_pipe or self._outlines_pipe:
99 self.remove_pipes()
101 if self._controls:
102 self._parent.remove_panel(self._controls)
103 self._controls = None
105 self._parent.update_view()
106 self._parent = None
108 def bind_state(self, state):
109 base.Element.bind_state(self, state)
111 self.talkie_connect(
112 state,
113 ['visible', 'geometry', 'display_parameter', 'time',
114 'opacity', 'color', 'line_width'],
115 self.update)
117 self.cpt_handler.bind_state(state.cpt, self.update)
119 def unbind_state(self):
120 self.cpt_handler.unbind_state()
121 base.Element.unbind_state(self)
123 def update_cpt(self, state):
125 if len(state.display_parameter) != 0:
126 values = state.geometry.get_property(state.display_parameter)
127 # TODO Check
128 # if values.ndim == 2:
129 # values = values.sum(1)
131 self.cpt_handler._values = values
132 self.cpt_handler.update_cpt(mask_zeros=True)
133 self.cpt_handler.update_cbar(state.display_parameter)
135 def get_name(self):
136 return 'Geometry'
138 def open_file_load_dialog(self):
139 caption = 'Select one file containing a geometry to open'
140 fns, _ = qw.QFileDialog.getOpenFileNames(
141 self._parent, caption, options=common.qfiledialog_options)
143 if fns:
144 self.load_file(str(fns[0]))
145 else:
146 return
148 def load_file(self, path):
150 loaded_geometry = load(filename=path)
151 props = loaded_geometry.properties.get_col_names(sub_headers=False)
153 if props:
154 if self._state.display_parameter not in props:
155 self._state.display_parameter = props[0]
157 self._parent.remove_panel(self._controls)
158 self._controls = None
159 self._state.geometry = loaded_geometry
161 self._parent.add_panel(
162 self.get_title_label(),
163 self._get_controls(),
164 visible=True,
165 title_controls=[
166 self.get_title_control_remove(),
167 self.get_title_control_visible()])
169 self.update()
171 def get_values(self, geom):
172 values = geom.get_property(self._state.display_parameter)
174 if geom.event is not None:
175 ref_time = geom.event.time
176 else:
177 ref_time = 0.
179 if len(values.shape) == 2:
180 tmin = self._parent.state.tmin
181 tmax = self._parent.state.tmax_effective
183 if tmin is not None:
184 ref_tmin = tmin - ref_time
185 ref_idx_min = geom.time2idx(ref_tmin)
186 else:
187 ref_idx_min = geom.time2idx(self._state.time)
189 if tmax is not None:
190 ref_tmax = tmax - ref_time
191 ref_idx_max = geom.time2idx(ref_tmax)
192 else:
193 ref_idx_max = geom.time2idx(self._state.time)
195 if ref_idx_min == ref_idx_max:
196 out = values[:, ref_idx_min]
197 elif ref_idx_min > ref_idx_max:
198 out = values[:, ref_idx_min]
199 elif ref_idx_max < ref_idx_min:
200 out = values[:, ref_idx_max]
201 else:
202 # for cumulative display
203 out = values[:, ref_idx_min:ref_idx_max].sum(1)
204 # out = values[:, ref_idx_max]
205 else:
206 out = values.ravel()
207 return out
209 def update_view(self, *args):
210 pstate = self._parent.state
211 geom = self._state.geometry
213 if geom.no_faces() > 0:
214 latlon = geom.get_vertices('latlon')
215 pstate.lat, pstate.lon = geographic_midpoint(
216 latlon[:, 0],
217 latlon[:, 1])
218 elif geom.outlines:
219 latlon = num.concatenate([
220 outline.get_col('latlon') for outline in geom.outlines
221 ])
222 pstate.lat, pstate.lon = geographic_midpoint(
223 latlon[:, 0],
224 latlon[:, 1])
225 elif geom.event:
226 pstate.lat = geom.event.lat
227 pstate.lon = geom.event.lon
228 else:
229 raise ValueError('Geometry Element has no location information.')
231 self.update()
233 def clear(self):
234 self._parent.remove_panel(self._controls)
235 self._controls = None
236 self._state.geometry = None
238 self._parent.add_panel(
239 self.get_title_label(),
240 self._get_controls(),
241 visible=True,
242 title_controls=[
243 self.get_title_control_remove(),
244 self.get_title_control_visible()])
246 self.update()
248 def update_outlines(self, geo):
249 state = self._state
250 if len(self._outlines_pipe) == 0:
251 for cs in ['latlondepth']:
252 outline_pipe = OutlinesPipe(
253 geo, color=state.color, cs=cs)
254 outline_pipe.set_line_width(state.line_width)
255 self._outlines_pipe.append(outline_pipe)
256 self._parent.add_actor(
257 self._outlines_pipe[-1].actor)
259 else:
260 for outline_pipe in self._outlines_pipe:
261 outline_pipe.set_color(state.color)
262 outline_pipe.set_line_width(state.line_width)
264 def update(self, *args):
266 state = self._state
268 if state.geometry and self._controls:
269 self._update_controls()
270 self.update_cpt(state)
272 if state.visible:
273 geo = state.geometry
274 lut = self.cpt_handler._lookuptable
275 no_faces = geo.no_faces()
276 if no_faces:
277 values = self.get_values(geo)
278 if not isinstance(self._pipe, TrimeshPipe):
279 vertices = arr_vertices(geo.get_vertices('xyz'))
280 faces = arr_faces(geo.get_faces())
281 self._pipe = TrimeshPipe(
282 vertices, faces,
283 values=values,
284 lut=lut,
285 backface_culling=False)
286 self._parent.add_actor(self._pipe.actor)
287 else:
288 self._pipe.set_values(values)
289 self._pipe.set_lookuptable(lut)
290 self._pipe.set_opacity(self._state.opacity)
292 if geo.outlines:
293 self.update_outlines(geo)
294 else:
295 self.remove_pipes()
297 else:
298 self.remove_pipes()
300 self._parent.update_view()
302 def _get_controls(self):
303 state = self._state
304 if not self._controls:
306 frame = qw.QFrame()
307 layout = qw.QGridLayout()
308 layout.setAlignment(qc.Qt.AlignTop)
309 frame.setLayout(layout)
311 # load geometry
312 il = 0
313 if not state.geometry:
314 pb = qw.QPushButton('Load')
315 layout.addWidget(pb, il, 0)
316 pb.clicked.connect(self.open_file_load_dialog)
318 # property choice
319 else:
320 props = []
321 for prop in state.geometry.properties.get_col_names(
322 sub_headers=False):
323 props.append(prop)
325 layout.addWidget(qw.QLabel('Display Parameter'), il, 0)
326 cb = qw.QComboBox()
328 unique_props = list(set(props))
329 for i, s in enumerate(unique_props):
330 cb.insertItem(i, s)
332 layout.addWidget(cb, il, 1)
333 state_bind_combobox(self, state, 'display_parameter', cb)
335 if state.geometry.no_faces != 0:
336 # color maps
337 self.cpt_handler.cpt_controls(
338 self._parent, self._state.cpt, layout)
340 il += 1
341 layout.addWidget(qw.QFrame(), il, 0, 1, 3)
343 self.cpt_handler._update_cpt_combobox()
344 self.cpt_handler._update_cptscale_lineedit()
346 # times slider
347 if state.geometry.times is not None:
348 il = layout.rowCount() + 1
349 slider = qw.QSlider(qc.Qt.Horizontal)
350 slider.setSizePolicy(
351 qw.QSizePolicy(
352 qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed))
354 def iround(x):
355 return int(round(x))
357 slider.setMinimum(iround(state.geometry.times.min()))
358 slider.setMaximum(iround(state.geometry.times.max()))
359 slider.setSingleStep(iround(state.geometry.deltat))
360 slider.setPageStep(iround(state.geometry.deltat))
362 time_label = qw.QLabel('Time')
363 layout.addWidget(time_label, il, 0)
364 layout.addWidget(slider, il, 1)
366 state_bind_slider(
367 self, state, 'time', slider, dtype=int)
369 self._time_label = time_label
370 self._time_slider = slider
372 il = layout.rowCount() + 1
373 slider_opacity = qw.QSlider(qc.Qt.Horizontal)
374 slider_opacity.setSizePolicy(
375 qw.QSizePolicy(
376 qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed))
377 slider_opacity.setMinimum(0)
378 slider_opacity.setMaximum(1000)
380 opacity_label = qw.QLabel('Opacity')
381 layout.addWidget(opacity_label, il, 0)
382 layout.addWidget(slider_opacity, il, 1)
384 state_bind_slider(
385 self, state, 'opacity', slider_opacity, factor=0.001)
387 self._opacity_label = opacity_label
388 self._opacity_slider = slider_opacity
390 # color
391 il += 1
392 layout.addWidget(qw.QLabel('Color'), il, 0)
394 cb = common.strings_to_combobox(
395 ['black', 'white', 'blue', 'red'])
397 layout.addWidget(cb, il, 1)
398 state_bind_combobox_color(self, state, 'color', cb)
400 # linewidth outline
401 il += 1
402 layout.addWidget(qw.QLabel('Line Width'), il, 0)
404 slider = qw.QSlider(qc.Qt.Horizontal)
405 slider.setSizePolicy(
406 qw.QSizePolicy(
407 qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed))
408 slider.setMinimum(0)
409 slider.setMaximum(100)
410 layout.addWidget(slider, il, 1)
411 state_bind_slider(
412 self, state, 'line_width', slider, factor=0.1)
414 # Clear scene
415 il += 1
416 pb = qw.QPushButton('Clear')
417 layout.addWidget(pb, il, 1)
418 pb.clicked.connect(self.clear)
420 # Change view to source
421 pb = qw.QPushButton('Move To')
422 layout.addWidget(pb, il, 2)
423 pb.clicked.connect(self.update_view)
425 self._controls = frame
427 self._update_controls()
429 return self._controls
431 def _update_controls(self):
432 state = self._state
433 if state.geometry:
434 if len(state.display_parameter) != 0:
435 values = state.geometry.get_property(state.display_parameter)
437 if values.ndim == 2:
438 self._time_label.setVisible(True)
439 self._time_slider.setVisible(True)
440 self._opacity_label.setVisible(True)
441 self._opacity_slider.setVisible(True)
442 else:
443 self._time_label.setVisible(False)
444 self._time_slider.setVisible(False)
445 self._opacity_label.setVisible(False)
446 self._opacity_slider.setVisible(False)
449__all__ = [
450 'GeometryElement',
451 'GeometryState'
452]