1# https://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

4# ---|P------/S----------~Lg---------- 

5 

6import logging 

7import numpy as num 

8 

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 

14 

15from pyrocko.model import Geometry 

16 

17from . import base 

18from .. import common 

19from ..state import state_bind_combobox, state_bind_slider, \ 

20 state_bind_combobox_color 

21 

22 

23logger = logging.getLogger('geometry') 

24 

25guts_prefix = 'sparrow' 

26 

27km = 1e3 

28 

29 

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) 

39 

40 def create(self): 

41 element = GeometryElement() 

42 return element 

43 

44 

45class GeometryElement(base.Element): 

46 

47 def __init__(self): 

48 base.Element.__init__(self) 

49 self._parent = None 

50 self._state = None 

51 self._controls = None 

52 

53 self._pipe = None 

54 self._cbar_pipe = None 

55 self._outlines_pipe = [] 

56 

57 self.cpt_handler = base.CPTHandler() 

58 

59 def remove(self): 

60 if self._parent and self._state: 

61 self._parent.state.elements.remove(self._state) 

62 

63 def init_pipeslots(self): 

64 if not self._pipe: 

65 self._pipe.append([]) 

66 

67 def remove_pipes(self): 

68 if self._pipe is not None: 

69 self._parent.remove_actor(self._pipe.actor) 

70 

71 if len(self._outlines_pipe) > 0: 

72 for pipe in self._outlines_pipe: 

73 self._parent.remove_actor(pipe.actor) 

74 

75 self._pipe = None 

76 self._outlines_pipe = [] 

77 

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()]) 

87 

88 self.talkie_connect( 

89 self._parent.state, 

90 ['tmin', 'tmax', 'lat', 'lon', 'tmax_effective'], 

91 self.update) 

92 

93 self.update() 

94 

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() 

100 

101 if self._controls: 

102 self._parent.remove_panel(self._controls) 

103 self._controls = None 

104 

105 self._parent.update_view() 

106 self._parent = None 

107 

108 def bind_state(self, state): 

109 base.Element.bind_state(self, state) 

110 

111 self.talkie_connect( 

112 state, 

113 ['visible', 'geometry', 'display_parameter', 'time', 

114 'opacity', 'color', 'line_width'], 

115 self.update) 

116 

117 self.cpt_handler.bind_state(state.cpt, self.update) 

118 

119 def unbind_state(self): 

120 self.cpt_handler.unbind_state() 

121 base.Element.unbind_state(self) 

122 

123 def update_cpt(self, state): 

124 

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) 

130 

131 self.cpt_handler._values = values 

132 self.cpt_handler.update_cpt(mask_zeros=True) 

133 self.cpt_handler.update_cbar(state.display_parameter) 

134 

135 def get_name(self): 

136 return 'Geometry' 

137 

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) 

142 

143 if fns: 

144 self.load_file(str(fns[0])) 

145 else: 

146 return 

147 

148 def load_file(self, path): 

149 

150 loaded_geometry = load(filename=path) 

151 props = loaded_geometry.properties.get_col_names(sub_headers=False) 

152 

153 if props: 

154 if self._state.display_parameter not in props: 

155 self._state.display_parameter = props[0] 

156 

157 self._parent.remove_panel(self._controls) 

158 self._controls = None 

159 self._state.geometry = loaded_geometry 

160 

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()]) 

168 

169 self.update() 

170 

171 def get_values(self, geom): 

172 values = geom.get_property(self._state.display_parameter) 

173 

174 if geom.event is not None: 

175 ref_time = geom.event.time 

176 else: 

177 ref_time = 0. 

178 

179 if len(values.shape) == 2: 

180 tmin = self._parent.state.tmin 

181 tmax = self._parent.state.tmax_effective 

182 

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) 

188 

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) 

194 

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 

208 

209 def update_view(self, *args): 

210 pstate = self._parent.state 

211 geom = self._state.geometry 

212 

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.') 

230 

231 self.update() 

232 

233 def clear(self): 

234 self._parent.remove_panel(self._controls) 

235 self._controls = None 

236 self._state.geometry = None 

237 

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()]) 

245 

246 self.update() 

247 

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) 

258 

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) 

263 

264 def update(self, *args): 

265 

266 state = self._state 

267 

268 if state.geometry and self._controls: 

269 self._update_controls() 

270 self.update_cpt(state) 

271 

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) 

291 

292 if geo.outlines: 

293 self.update_outlines(geo) 

294 else: 

295 self.remove_pipes() 

296 

297 else: 

298 self.remove_pipes() 

299 

300 self._parent.update_view() 

301 

302 def _get_controls(self): 

303 state = self._state 

304 if not self._controls: 

305 

306 frame = qw.QFrame() 

307 layout = qw.QGridLayout() 

308 layout.setAlignment(qc.Qt.AlignTop) 

309 frame.setLayout(layout) 

310 

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) 

317 

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) 

324 

325 layout.addWidget(qw.QLabel('Display Parameter'), il, 0) 

326 cb = qw.QComboBox() 

327 

328 unique_props = list(set(props)) 

329 for i, s in enumerate(unique_props): 

330 cb.insertItem(i, s) 

331 

332 layout.addWidget(cb, il, 1) 

333 state_bind_combobox(self, state, 'display_parameter', cb) 

334 

335 if state.geometry.no_faces != 0: 

336 # color maps 

337 self.cpt_handler.cpt_controls( 

338 self._parent, self._state.cpt, layout) 

339 

340 il += 1 

341 layout.addWidget(qw.QFrame(), il, 0, 1, 3) 

342 

343 self.cpt_handler._update_cpt_combobox() 

344 self.cpt_handler._update_cptscale_lineedit() 

345 

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)) 

353 

354 def iround(x): 

355 return int(round(x)) 

356 

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)) 

361 

362 time_label = qw.QLabel('Time') 

363 layout.addWidget(time_label, il, 0) 

364 layout.addWidget(slider, il, 1) 

365 

366 state_bind_slider( 

367 self, state, 'time', slider, dtype=int) 

368 

369 self._time_label = time_label 

370 self._time_slider = slider 

371 

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) 

379 

380 opacity_label = qw.QLabel('Opacity') 

381 layout.addWidget(opacity_label, il, 0) 

382 layout.addWidget(slider_opacity, il, 1) 

383 

384 state_bind_slider( 

385 self, state, 'opacity', slider_opacity, factor=0.001) 

386 

387 self._opacity_label = opacity_label 

388 self._opacity_slider = slider_opacity 

389 

390 # color 

391 il += 1 

392 layout.addWidget(qw.QLabel('Color'), il, 0) 

393 

394 cb = common.strings_to_combobox( 

395 ['black', 'white', 'blue', 'red']) 

396 

397 layout.addWidget(cb, il, 1) 

398 state_bind_combobox_color(self, state, 'color', cb) 

399 

400 # linewidth outline 

401 il += 1 

402 layout.addWidget(qw.QLabel('Line Width'), il, 0) 

403 

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) 

413 

414 # Clear scene 

415 il += 1 

416 pb = qw.QPushButton('Clear') 

417 layout.addWidget(pb, il, 1) 

418 pb.clicked.connect(self.clear) 

419 

420 # Change view to source 

421 pb = qw.QPushButton('Move To') 

422 layout.addWidget(pb, il, 2) 

423 pb.clicked.connect(self.update_view) 

424 

425 self._controls = frame 

426 

427 self._update_controls() 

428 

429 return self._controls 

430 

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) 

436 

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) 

447 

448 

449__all__ = [ 

450 'GeometryElement', 

451 'GeometryState' 

452]