LoopStructural 1.0.9__cp38-cp38-win32.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. LoopStructural/__init__.py +36 -0
  2. LoopStructural/analysis/__init__.py +2 -0
  3. LoopStructural/analysis/_fault_displacement.py +67 -0
  4. LoopStructural/analysis/_fault_intersection.py +36 -0
  5. LoopStructural/analysis/_plane_fit.py +43 -0
  6. LoopStructural/datasets/__init__.py +12 -0
  7. LoopStructural/datasets/_base.py +65 -0
  8. LoopStructural/datasets/data/claudius.csv +21049 -0
  9. LoopStructural/datasets/data/claudiusbb.txt +2 -0
  10. LoopStructural/datasets/data/duplex.csv +126 -0
  11. LoopStructural/datasets/data/duplexbb.txt +2 -0
  12. LoopStructural/datasets/data/intrusion.csv +1017 -0
  13. LoopStructural/datasets/data/intrusionbb.txt +2 -0
  14. LoopStructural/datasets/data/onefoldbb.txt +2 -0
  15. LoopStructural/datasets/data/onefolddata.csv +2226 -0
  16. LoopStructural/datasets/data/refolded_bb.txt +2 -0
  17. LoopStructural/datasets/data/refolded_fold.csv +2126 -0
  18. LoopStructural/interpolators/__init__.py +34 -0
  19. LoopStructural/interpolators/base_structured_3d_support.py +101 -0
  20. LoopStructural/interpolators/cython/__init__.py +0 -0
  21. LoopStructural/interpolators/cython/dsi_helper.c +31046 -0
  22. LoopStructural/interpolators/cython/dsi_helper.cp38-win32.pyd +0 -0
  23. LoopStructural/interpolators/discrete_fold_interpolator.py +202 -0
  24. LoopStructural/interpolators/discrete_interpolator.py +584 -0
  25. LoopStructural/interpolators/finite_difference_interpolator.py +399 -0
  26. LoopStructural/interpolators/geological_interpolator.py +197 -0
  27. LoopStructural/interpolators/operator.py +61 -0
  28. LoopStructural/interpolators/piecewiselinear_interpolator.py +437 -0
  29. LoopStructural/interpolators/structured_grid.py +421 -0
  30. LoopStructural/interpolators/structured_tetra.py +681 -0
  31. LoopStructural/interpolators/surfe_wrapper.py +118 -0
  32. LoopStructural/modelling/__init__.py +46 -0
  33. LoopStructural/modelling/core/__init__.py +0 -0
  34. LoopStructural/modelling/core/geological_model.py +1513 -0
  35. LoopStructural/modelling/core/geological_model_graph.py +880 -0
  36. LoopStructural/modelling/core/stratigraphic_column.py +5 -0
  37. LoopStructural/modelling/fault/__init__.py +4 -0
  38. LoopStructural/modelling/fault/fault_builder.py +143 -0
  39. LoopStructural/modelling/fault/fault_function.py +188 -0
  40. LoopStructural/modelling/fault/fault_function_feature.py +76 -0
  41. LoopStructural/modelling/fault/fault_segment.py +298 -0
  42. LoopStructural/modelling/features/__init__.py +8 -0
  43. LoopStructural/modelling/features/cross_product_geological_feature.py +90 -0
  44. LoopStructural/modelling/features/geological_feature.py +252 -0
  45. LoopStructural/modelling/features/geological_feature_builder.py +414 -0
  46. LoopStructural/modelling/features/lambda_geological_feature.py +36 -0
  47. LoopStructural/modelling/features/region_feature.py +34 -0
  48. LoopStructural/modelling/features/structural_frame.py +133 -0
  49. LoopStructural/modelling/features/structural_frame_builder.py +206 -0
  50. LoopStructural/modelling/features/unconformity_feature.py +74 -0
  51. LoopStructural/modelling/fold/__init__.py +8 -0
  52. LoopStructural/modelling/fold/fold.py +143 -0
  53. LoopStructural/modelling/fold/fold_rotation_angle.py +133 -0
  54. LoopStructural/modelling/fold/fold_rotation_angle_feature.py +58 -0
  55. LoopStructural/modelling/fold/foldframe.py +193 -0
  56. LoopStructural/modelling/fold/svariogram.py +180 -0
  57. LoopStructural/utils/__init__.py +18 -0
  58. LoopStructural/utils/bounding_box.py +21 -0
  59. LoopStructural/utils/exceptions.py +10 -0
  60. LoopStructural/utils/helper.py +382 -0
  61. LoopStructural/utils/logging.py +60 -0
  62. LoopStructural/utils/map2loop.py +353 -0
  63. LoopStructural/utils/regions.py +11 -0
  64. LoopStructural/utils/utils.py +69 -0
  65. LoopStructural/visualisation/__init__.py +31 -0
  66. LoopStructural/visualisation/map_viewer.py +322 -0
  67. LoopStructural/visualisation/model_plotter.py +17 -0
  68. LoopStructural/visualisation/model_visualisation.py +1113 -0
  69. LoopStructural/visualisation/rotation_angle_plotter.py +83 -0
  70. LoopStructural/visualisation/sphinx_scraper.py +34 -0
  71. LoopStructural/visualisation/stratigraphic_column.py +60 -0
  72. LoopStructural-1.0.9.dist-info/LICENSE +21 -0
  73. LoopStructural-1.0.9.dist-info/METADATA +19 -0
  74. LoopStructural-1.0.9.dist-info/RECORD +77 -0
  75. LoopStructural-1.0.9.dist-info/WHEEL +5 -0
  76. LoopStructural-1.0.9.dist-info/top_level.txt +2 -0
  77. tests/__init__.py +0 -0
@@ -0,0 +1,1113 @@
1
+ """
2
+ A wrapper for lavavu
3
+
4
+ """
5
+
6
+ import logging
7
+ from LoopStructural.utils import getLogger
8
+ logger = getLogger(__name__)
9
+
10
+ try:
11
+ import lavavu
12
+ from lavavu.vutils import is_notebook
13
+ except ImportError:
14
+ logger.error("Please install lavavu: pip install lavavu")
15
+ import numpy as np
16
+ try:
17
+ from skimage.measure import marching_cubes
18
+ except ImportError:
19
+ logger.warning("Using depreciated version of scikit-image")
20
+ from skimage.measure import marching_cubes_lewiner as marching_cubes
21
+ from LoopStructural.modelling.features import GeologicalFeature
22
+ from LoopStructural.utils.helper import create_surface, get_vectors, create_box
23
+
24
+ # adapted/copied from pyvista for sphinx scraper
25
+ _OPEN_VIEWERS = {}
26
+
27
+ def close_all():
28
+ _OPEN_VIEWERS.clear()
29
+ return True
30
+ # for key, v in _OPEN_VIEWERS.items():
31
+ # if not v._closed:
32
+ # v.close()
33
+ # v.deep_clean()
34
+ # _OPEN_VIEWERS.clear()
35
+ # return True
36
+ ##
37
+ class LavaVuModelViewer:
38
+ """
39
+
40
+ Short description
41
+
42
+ """
43
+ def __init__(self, model=None, bounding_box=None, nsteps=None, vertical_exaggeration=1., **kwargs):
44
+ """
45
+ A wrapper to plot LoopStructural object with lavavu
46
+
47
+ Parameters
48
+ ----------
49
+ **kwargs : lavavu viewer kwargs
50
+
51
+
52
+ objects : dictionary of objects that have been plotted
53
+ """
54
+ # copied from pyvista
55
+ if lavavu is None:
56
+ logger.error("Lavavu isn't installed: pip install lavavu")
57
+ return
58
+ self._id_name = "{}-{}".format(str(hex(id(self))), len(_OPEN_VIEWERS))
59
+ _OPEN_VIEWERS[self._id_name] = self
60
+ #
61
+ self.lv = lavavu.Viewer(**kwargs)
62
+ self.lv['orthographic'] = True
63
+ self.lv.modelscale([1,1,vertical_exaggeration])
64
+ self.objects = {}
65
+ self.bounding_box = bounding_box
66
+ self.nsteps = nsteps
67
+ if model is not None:
68
+ self.bounding_box = model.bounding_box
69
+ self.nsteps = model.nsteps
70
+ logger.debug("Using bounding box from model")
71
+ if self.bounding_box is None or self.nsteps is None:
72
+ logger.error("Plot area has not been defined.")
73
+ self.bounding_box = np.array(self.bounding_box)
74
+ self.nsteps = np.array(self.nsteps)
75
+ self._model = model
76
+ # prerotate to a nice view
77
+ # self.lv.rotate([-57.657936096191406, -13.939384460449219, -6.758780479431152])
78
+ def close(self):
79
+ pass
80
+
81
+ @property
82
+ def model(self):
83
+ return self._model
84
+
85
+ @model.setter
86
+ def model(self, model):
87
+ if model is not None:
88
+ self.bounding_box = np.array(model.bounding_box)
89
+ self.nsteps = np.array(model.nsteps)
90
+ self._model = model
91
+ self._nelements = self.nsteps[0]*self.nsteps[1]*self.nsteps[2]
92
+ logger.debug("Using bounding box from model")
93
+ @property
94
+ def nelements(self):
95
+ """The number of elements to use for evaluating the isosurface
96
+
97
+ Returns
98
+ -------
99
+ nelements : int
100
+ number of elements to use for isosurfacing
101
+ """
102
+ return self._nelements
103
+
104
+ @nelements.setter
105
+ def nelements(self, nelements : int):
106
+ """Setter for nelements, automatically caculates the number of equally sized elements
107
+ to isosurface. Better than specifying step distance manually
108
+
109
+ Parameters
110
+ ----------
111
+ nelements : int
112
+ [description]
113
+ """
114
+ box_vol = (self.bounding_box[1, 0]-self.bounding_box[0, 0]) * (self.bounding_box[1, 1]-self.bounding_box[0, 1]) * (self.bounding_box[1, 2]-self.bounding_box[0, 2])
115
+ ele_vol = box_vol / nelements
116
+ # calculate the step vector of a regular cube
117
+ step_vector = np.zeros(3)
118
+ step_vector[:] = ele_vol ** (1. / 3.)
119
+ # step_vector /= np.array([1,1,2])
120
+ # number of steps is the length of the box / step vector
121
+ nsteps = np.ceil((self.bounding_box[1, :] - self.bounding_box[0, :]) / step_vector).astype(int)
122
+ self.nsteps = nsteps
123
+ logger.info("Using grid with dimensions {} {} {}".format(nsteps[0],nsteps[1],nsteps[2]))
124
+
125
+ def deep_clean(self):
126
+ """[summary]
127
+
128
+ [extended_summary]
129
+ """
130
+ self.lv.clear()
131
+ self.lv.cleardata()
132
+ pass
133
+
134
+ def add_section(self, geological_feature=None, axis='x', value=None, **kwargs):
135
+ """
136
+
137
+ Plot a section/map thru the model and paint with a geological feature
138
+
139
+ Parameters
140
+ ----------
141
+ geological_feature : Geological feature
142
+ The feature to paint the section with
143
+ axis : string
144
+ which axis, x,y,z
145
+ value : float
146
+ Where to make the section
147
+ kwargs
148
+ additional kwargs passes to lavavu for colourmaps etc
149
+
150
+ Returns
151
+ -------
152
+
153
+ """
154
+ if axis == 'x':
155
+ tri, yy, zz = create_surface(self.bounding_box[:, [1, 2]], self.nsteps[[1, 2]])
156
+ xx = np.zeros(zz.shape)
157
+ if value is None:
158
+ value = np.nanmean(self.bounding_box[:, 0])
159
+ xx[:] = value
160
+ if axis == 'y':
161
+ tri, xx, zz = create_surface(self.bounding_box[:, [0, 2]], self.nsteps[[0, 2]])
162
+ yy = np.zeros(xx.shape)
163
+ if value is None:
164
+ value = np.nanmean(self.bounding_box[:, 1])
165
+ yy[:] = value
166
+ if axis == 'z':
167
+ tri, xx, yy = create_surface(self.bounding_box[:, 0:2], self.nsteps[0:2])
168
+ zz = np.zeros(xx.shape)
169
+ if value is None:
170
+ value = np.nanmean(self.bounding_box[:, 2])
171
+ zz[:] = value
172
+
173
+ name = kwargs.get('name', geological_feature.name)
174
+ name = '{}_section_at_{}_of_{}'.format(axis,value,name)
175
+ colour = kwargs.get('colour', 'red')
176
+
177
+ # create an array to evaluate the feature on for the section
178
+ points = np.zeros((len(xx), 3)) #
179
+ points[:, 0] = xx
180
+ points[:, 1] = yy
181
+ points[:, 2] = zz
182
+
183
+ surf = self.lv.triangles(name)
184
+ surf.vertices(self.model.rescale(points,inplace=False))
185
+ surf.indices(tri)
186
+ logger.info("Adding %s section at %f" % (axis, value))
187
+ if geological_feature is None:
188
+ surf.colours(colour)
189
+
190
+ if geological_feature is not None and type(geological_feature) == GeologicalFeature:
191
+ if 'norm' in kwargs:
192
+ surf.values(np.linalg.norm(
193
+ geological_feature.evaluate_gradient(points), axis=1),
194
+ geological_feature.name)
195
+ else:
196
+ surf.values(geological_feature.evaluate_value(points),
197
+ geological_feature.name)
198
+ surf["colourby"] = geological_feature.name
199
+ cmap = lavavu.cubehelix(100)
200
+ if 'cmap' in kwargs:
201
+ cmap = kwargs['cmap']
202
+ logger.info("Colouring section with %s min: %f, max: %f" % (
203
+ geological_feature.name, geological_feature.min(), geological_feature.max()))
204
+ surf.colourmap(cmap, range=[geological_feature.min(), geological_feature.max()])
205
+ if geological_feature == 'model' and self.model is not None:
206
+ name = kwargs.get('name','model_section')
207
+ surf.values(self.model.evaluate_model(points,scale=True),
208
+ name)
209
+ surf["colourby"] = name
210
+ cmap = lavavu.cubehelix(100)
211
+ if 'cmap' in kwargs:
212
+ cmap = kwargs['cmap']
213
+
214
+
215
+ def add_isosurface(self, geological_feature, value = None, isovalue=None,
216
+ paint_with=None, slices=None, colour='red', nslices=None,
217
+ cmap=None, filename=None, names=None, colours=None,**kwargs):
218
+ """ Plot the surface of a geological feature
219
+
220
+ [extended_summary]
221
+
222
+ Parameters
223
+ ----------
224
+ geological_feature : GeologicalFeature
225
+ [description]
226
+ value : float, optional
227
+
228
+ isovalue : [type], optional
229
+ [description], by default None
230
+ paint_with : [type], optional
231
+ [description], by default None
232
+ slices : [type], optional
233
+ [description], by default None
234
+ colour : [type], optional
235
+ [description], by default None
236
+ nslices : [type], optional
237
+ [description], by default None
238
+ cmap : [type], optional
239
+ [description], by default None
240
+ filename: string, optional
241
+ filename for exporting
242
+ names: list, optional
243
+ list of names same length as slices
244
+ colours: list, optional
245
+ list of colours same length as slices
246
+
247
+ Returns
248
+ -------
249
+ [type]
250
+ [description]
251
+ """
252
+ if geological_feature is None:
253
+ logger.error("Cannot add isosurface GeologicalFeature does not exist")
254
+ # update the feature to make sure its current
255
+
256
+
257
+
258
+ # do isosurfacing of support using marching tetras/cubes
259
+ x = np.linspace(self.bounding_box[0, 0], self.bounding_box[1, 0], self.nsteps[0])
260
+ y = np.linspace(self.bounding_box[0, 1], self.bounding_box[1, 1], self.nsteps[1])
261
+ z = np.linspace(self.bounding_box[1, 2], self.bounding_box[0, 2], self.nsteps[2])
262
+ xx, yy, zz = np.meshgrid(x, y, z, indexing='ij')
263
+ points = np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T
264
+ val = geological_feature.evaluate_value(points)
265
+ # get the stats to check what we are plotting
266
+ mean_property_val = np.nanmean(val)#geological_feature.mean()
267
+ min_property_val = np.nanmin(val)#geological_feature.min()
268
+ max_property_val = np.nanmax(val)#geological_feature.max()
269
+ # set default parameters
270
+ slices_ = [mean_property_val]
271
+ painter = None
272
+ voxet = None
273
+ tris = None
274
+ nodes = None
275
+ # parse kwargs for parameters
276
+ if isovalue is not None:
277
+ slices_ = [isovalue]
278
+ if value is not None:
279
+ slices_ = [value]
280
+ if slices is not None:
281
+ slices_ = slices
282
+ if nslices is not None:
283
+ var = max_property_val - min_property_val
284
+ # buffer slices by 5%
285
+ slices_ = np.linspace(min_property_val + var * 0.05,
286
+ max_property_val - var * 0.05,
287
+ nslices)
288
+
289
+ if paint_with is not None:
290
+ painter = paint_with
291
+
292
+ region = kwargs.get('region', None)
293
+
294
+
295
+ if region is not None:
296
+ val[~region(np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T)] = np.nan
297
+ step_vector = np.array([x[1] - x[0], y[1] - y[0], z[1] - z[0]])
298
+ for i, isovalue in enumerate(slices_):
299
+ logger.info("Creating isosurface of %s at %f" % (geological_feature.name, isovalue))
300
+
301
+ if isovalue > np.nanmax(val) or isovalue < np.nanmin(val):
302
+ logger.warning("Isovalue doesn't exist inside bounding box")
303
+ continue # return np.zeros((3, 1)).astype(int), np.zeros((3, 1))
304
+ try:
305
+ verts, faces, normals, values = marching_cubes(
306
+ val.reshape(self.nsteps, order='C'),
307
+ isovalue,
308
+ spacing=step_vector)
309
+ verts += np.array([self.bounding_box[0, 0], self.bounding_box[0, 1], self.bounding_box[1, 2]])
310
+ self.model.rescale(verts)
311
+
312
+ except (ValueError, RuntimeError) as e:
313
+ print(e)
314
+ logger.warning("Cannot isosurface {} at {}, skipping".format(geological_feature.name,isovalue))
315
+ continue
316
+
317
+
318
+ name = geological_feature.name
319
+ name = kwargs.get('name', name)
320
+ name += '_iso_%f' % isovalue
321
+ if names is not None and len(names) == len(slices_):
322
+ name = names[i]
323
+ if name in self.lv.objects:
324
+ ii = 0
325
+ newname = name+"_{}".format(ii)
326
+ while newname in self.lv.objects:
327
+ ii+=1
328
+ newname = name+"_{}".format(ii)
329
+ name = newname
330
+
331
+ if colours is not None and len(colours) == len(slices_):
332
+ colour=colours[i]
333
+ if filename is not None:
334
+ svalues = None
335
+ # svalues[:] = np.nan
336
+ try:
337
+ import meshio
338
+ meshio.write_points_cells(filename.format(name),
339
+ verts,
340
+ [("triangle", faces)]
341
+ )
342
+ except ImportError:
343
+ logger.error("Could not save surfaces, meshio is not installed")
344
+
345
+ surf = self.lv.triangles(name)
346
+ surf.vertices(verts)
347
+ surf.indices(faces)
348
+ if painter is None:
349
+ surf.colours(colour)
350
+ if painter is not None:
351
+ # add a property to the surface nodes for visualisation
352
+ # calculate the mode value, just to get the most common value
353
+ surfaceval = np.zeros(verts.shape[0])
354
+ surfaceval[:] = painter.evaluate_value(self.model.scale(verts))
355
+ if painter.name is geological_feature.name:
356
+ logger.info("Setting surface value to %f"%isovalue)
357
+ surfaceval[:] = isovalue
358
+ surf.values(surfaceval, painter.name)
359
+ surf["colourby"] = painter.name
360
+ vmin = kwargs.get('vmin', min_property_val)
361
+ vmax = kwargs.get('vmax', max_property_val)
362
+ surf.colourmap(cmap, range=(vmin, vmax)) # nodes.shape[0]))
363
+
364
+ def add_scalar_field(self, geological_feature, name=None, cmap='rainbow', vmin=None, vmax = None, **kwargs):
365
+ """Add a block the size of the model area painted with the scalar field value
366
+
367
+ Parameters
368
+ ----------
369
+ geological_feature : GeologicalFeature
370
+ the geological feature to colour the scalar field by
371
+ name : string, optional
372
+ Name of the object for lavavu, needs to be unique for the viewer object, by default uses feature name
373
+ cmap : str, optional
374
+ mpl colourmap reference, by default 'rainbow'
375
+ vmin : double, optional
376
+ minimum value of the colourmap, by default None
377
+ vmax : double, optional
378
+ maximum value of the colourmap, by default None
379
+ """
380
+ if name == None:
381
+ if geological_feature is None:
382
+ name = 'unnamed scalar field'
383
+ else:
384
+ name = geological_feature.name + '_scalar_field'
385
+
386
+ points, tri = create_box(self.bounding_box,self.nsteps)
387
+
388
+ surf = self.lv.triangles(name)
389
+ surf.vertices(self.model.rescale(points))
390
+ surf.indices(tri)
391
+ val =geological_feature.evaluate_value(self.model.scale(points))
392
+ surf.values(val, geological_feature.name)
393
+ surf["colourby"] = geological_feature.name
394
+ logger.info("Adding scalar field of %s to viewer. Min: %f, max: %f" % (geological_feature.name,
395
+ geological_feature.min(),
396
+ geological_feature.max()))
397
+ if vmin == None:
398
+ vmin =np.nanmin(val)
399
+ if vmax == None:
400
+ vmax = np.nanmax(val)
401
+ surf.colourmap(cmap, range=(vmin, vmax))
402
+
403
+ def add_box(self,bounding_box,name,colour='red'):
404
+ points, tri = create_box(bounding_box,self.nsteps)
405
+
406
+ surf = self.lv.triangles(name)
407
+ surf.vertices(self.model.rescale(points))
408
+ surf.indices(tri)
409
+ surf.colours(colour)
410
+
411
+ def add_model(self, cmap = None, **kwargs):
412
+ """Add a block model painted by stratigraphic id to the viewer
413
+
414
+ Calls self.model.evaluate_model() for a cube surrounding the model.
415
+
416
+ Parameters
417
+ ----------
418
+ cmap : matplotlib cmap, optional
419
+ colourmap name or object from mpl
420
+
421
+ Notes
422
+ ------
423
+ It is sensible to increase the viewer step sizes before running this function to
424
+ increase the resolution of the model as its not possible to interpolate a discrete
425
+ colourmap and this causes the model to look like a lego block.
426
+ You can update the model resolution by changing the attribute nsteps
427
+ >>> viewer.nsteps = np.array([100,100,100])
428
+
429
+ """
430
+ name = kwargs.get('name', 'geological_model')
431
+ points, tri = create_box(self.bounding_box, self.nsteps)
432
+
433
+ surf = self.lv.triangles(name)
434
+ surf.vertices(self.model.rescale(points))
435
+ surf.indices(tri)
436
+ val = self.model.evaluate_model(points,scale=True)
437
+ surf.values(val, 'model')
438
+ surf["colourby"] = 'model'
439
+
440
+ if cmap is None:
441
+ try:
442
+ import matplotlib.colors as colors
443
+ except ImportError:
444
+ logger.warning("Cannot use predefined colours as I can't import matplotlib")
445
+ cmap = 'tab20'
446
+ colours = []
447
+ boundaries = []
448
+ data = []
449
+ for g in self.model.stratigraphic_column.keys():
450
+ if g == 'faults':
451
+ continue
452
+ for u, v in self.model.stratigraphic_column[g].items():
453
+ data.append((v['id'],v['colour']))
454
+ colours.append(v['colour'])
455
+ boundaries.append(v['id'])#print(u,v)
456
+ cmap = colors.ListedColormap(colours).colors
457
+ # else:
458
+ # cmap = cm.get_cmap(cmap,n_units)
459
+
460
+
461
+ # logger.info("Adding scalar field of %s to viewer. Min: %f, max: %f" % (geological_feature.name,
462
+ # geological_feature.min(),
463
+ # geological_feature.max()))
464
+ vmin = kwargs.get('vmin', np.nanmin(val))
465
+ vmax = kwargs.get('vmax', np.nanmax(val))
466
+ surf.colourmap(cmap, range=(vmin, vmax))
467
+
468
+ def add_fault_displacements(self, cmap = 'rainbow', **kwargs):
469
+ """Add a block model painted by the fault displacement magnitude
470
+
471
+ Calls fault.displacementfeature.evaluate_value(points) for all faults
472
+
473
+ Parameters
474
+ ----------
475
+ cmap : matplotlib cmap, optional
476
+ colourmap name or object from mpl
477
+
478
+ Notes
479
+ ------
480
+ It is sensible to increase the viewer step sizes before running this function to
481
+ increase the resolution of the model as its not possible to interpolate a discrete
482
+ colourmap and this causes the model to look like a lego block.
483
+ You can update the model resolution by changing the attribute nsteps
484
+ >>> viewer.nsteps = np.array([100,100,100])
485
+
486
+ """
487
+
488
+ name = kwargs.get('name', 'fault_displacements')
489
+ points, tri = create_box(self.bounding_box, self.nsteps)
490
+
491
+ surf = self.lv.triangles(name)
492
+ surf.vertices(self.model.rescale(points))
493
+ surf.indices(tri)
494
+ vals = self.model.evaluate_fault_displacements(points)
495
+ surf.values(vals, 'displacement')
496
+ surf["colourby"] = 'displacement'
497
+
498
+ vmin = kwargs.get('vmin', np.nanmin(vals))
499
+ vmax = kwargs.get('vmax', np.nanmax(vals))
500
+ surf.colourmap(cmap, range=(vmin, vmax))
501
+
502
+ def add_fault(self,fault,step=100):
503
+ self.add_isosurface(fault,value=0,name=fault.name)
504
+ self.add_vector_field(fault,locations=self.model.regular_grid()[::step])
505
+
506
+ def unfault_grid(self,feature,grid=None):
507
+ if grid is None:
508
+ grid = self.model.regular_grid()
509
+ # apply all faults associated with a feature to a regular grid
510
+ self.add_value_data(self.model.rescale(grid,inplace=False),grid[:,2],name='Regular grid before faults',pointsize=10,)
511
+
512
+ for f in feature.faults:
513
+ grid = f.apply_to_points(grid)
514
+ self.add_value_data(self.model.rescale(grid,inplace=False),grid[:,2],name='Regular grid after faults',pointsize=10,)
515
+
516
+ def add_model_surfaces(self, strati=True, faults = True, cmap=None, fault_colour='black',**kwargs):
517
+ """Add surfaces for all of the interfaces in the model
518
+
519
+
520
+ Parameters
521
+ ----------
522
+ faults : bool, optional
523
+ whether to draw faults, by default True
524
+ cmap : string
525
+ matplotlib cmap
526
+ Notes
527
+ ------
528
+ Other parameters are passed to self.add_isosurface()
529
+
530
+ """
531
+ import time
532
+ from tqdm.auto import tqdm
533
+ start = time.time()
534
+ n_units = 0 #count how many discrete colours
535
+ name_suffix = kwargs.pop('name','')
536
+ for g in self.model.stratigraphic_column.keys():
537
+ if g in self.model.feature_name_index:
538
+ for u in self.model.stratigraphic_column[g].keys():
539
+ n_units+=1
540
+ n_faults = 0
541
+ for f in self.model.features:
542
+ if f.type=='fault':
543
+ n_faults+=1
544
+
545
+ if cmap is None:
546
+ try:
547
+ from matplotlib import cm
548
+ from matplotlib import colors
549
+ except ImportError:
550
+ logger.warning("Cannot use predefined colours, matplotlib is not installed. \n")
551
+ cmap = 'tab20'
552
+ colours = []
553
+ boundaries = []
554
+ data = []
555
+ for g in self.model.stratigraphic_column.keys():
556
+ if g == 'faults':
557
+ # skip anything saved in faults here
558
+ continue
559
+ for u, v in self.model.stratigraphic_column[g].items():
560
+ data.append((v['id'],v['colour']))
561
+ colours.append(v['colour'])
562
+ boundaries.append(v['id'])
563
+ cmap = colors.ListedColormap(colours)
564
+ else:
565
+ cmap = cm.get_cmap(cmap,n_units)
566
+ ci = 0
567
+ cmap_colours = colors.to_rgba_array(cmap.colors)
568
+ n_surfaces = 0
569
+ if strati:
570
+ n_surfaces+=n_units
571
+ if faults:
572
+ n_surfaces+=n_faults
573
+ with tqdm(total=n_surfaces) as pbar:
574
+
575
+ if strati:
576
+ for g in self.model.stratigraphic_column.keys():
577
+ if g in self.model.feature_name_index:
578
+ feature = self.model.features[self.model.feature_name_index[g]]
579
+ names = []
580
+ values = []
581
+ colours = []
582
+ for u, vals in self.model.stratigraphic_column[g].items():
583
+ names.append(u+name_suffix)
584
+ values.append(vals['min'])
585
+ colours.append(cmap_colours[ci,:])
586
+ ci+=1
587
+ pbar.set_description('Isosurfacing {}'.format(feature.name))
588
+ self.add_isosurface(feature, slices=values,names=names,colours=colours,**kwargs)
589
+ pbar.update(len(values))
590
+
591
+
592
+ if faults:
593
+ for f in self.model.features:
594
+ if f.type == 'fault':
595
+ def mask(x):
596
+ val = f.displacementfeature.evaluate_value(x)
597
+ val[np.isnan(val)] = 0
598
+ maskv = np.zeros(val.shape).astype(bool)
599
+ maskv[np.abs(val) > 0.001] = 1
600
+ return maskv
601
+ if f.name in self.model.stratigraphic_column['faults']:
602
+ fault_colour = self.model.stratigraphic_column['faults'][f.name].get('colour',['red'])
603
+ pbar.set_description('Isosurfacing {}'.format(f.name))
604
+
605
+ region = kwargs.pop('region',None)
606
+ self.add_isosurface(f,isovalue=0,region=mask,colour=fault_colour[0],name=f.name+name_suffix,**kwargs)
607
+ pbar.update(1)
608
+ print("Adding surfaces took {} seconds".format(time.time()-start))
609
+ def add_vector_field(self, geological_feature, **kwargs):
610
+ """
611
+
612
+ Plot the gradient of a geological feature at given locations
613
+
614
+ Parameters
615
+ ----------
616
+ geological_feature : Geological Feature to evaluate gradient
617
+ locations : ((N,3)) array of evaluation locations
618
+ kwargs : kwargs for lavavu vector
619
+
620
+ Returns
621
+ -------
622
+
623
+ """
624
+ logger.info("Adding vector field for %s " % (geological_feature.name))
625
+ locations = kwargs.get('locations', None)
626
+ if locations is None:
627
+ x = np.linspace(self.bounding_box[0, 0], self.bounding_box[1, 0], self.nsteps[0])
628
+ y = np.linspace(self.bounding_box[0, 1], self.bounding_box[1, 1], self.nsteps[1])
629
+ z = np.linspace(self.bounding_box[1, 2], self.bounding_box[0, 2], self.nsteps[2])
630
+ xx, yy, zz = np.meshgrid(x, y, z, indexing='ij')
631
+ locations = np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T
632
+ vector = geological_feature.evaluate_gradient(locations)
633
+ # normalise
634
+ mask = ~np.any(np.isnan(vector), axis=1)
635
+ vector[mask, :] /= np.linalg.norm(vector[mask, :], axis=1)[:, None]
636
+ vectorfield = self.lv.vectors(geological_feature.name + "_grad",
637
+ **kwargs)
638
+ vectorfield.vertices(self.model.rescale(locations[mask, :],inplace=False))
639
+ vectorfield.vectors(vector[mask, :])
640
+ return
641
+
642
+ def add_data(self, feature, disks=True, vectors = False,**kwargs):
643
+ """
644
+
645
+ Plot the data linked to the feature, can choose whether to plot all data types
646
+ using value and grad kwargs
647
+
648
+ Parameters
649
+ ----------
650
+ feature
651
+ kwargs
652
+
653
+ Returns
654
+ -------
655
+
656
+ """
657
+ name = feature.name
658
+ add_grad = True
659
+ add_value = True
660
+ add_tang = True
661
+ add_interface = True
662
+ if 'name' in kwargs:
663
+ name = kwargs['name']
664
+ del kwargs['name']
665
+ if 'grad' in kwargs:
666
+ add_grad = kwargs['grad']
667
+ if 'value' in kwargs:
668
+ add_value = kwargs['value']
669
+ if 'tang' in kwargs:
670
+ add_tang = kwargs['tang']
671
+ if 'interface' in kwargs:
672
+ add_interface = kwargs['interface']
673
+ grad = feature.builder.get_gradient_constraints()
674
+ norm = feature.builder.get_norm_constraints()
675
+ value = feature.builder.get_value_constraints()
676
+ tang = feature.builder.get_tangent_constraints()
677
+ interface = feature.builder.get_interface_constraints()
678
+
679
+ if grad.shape[0] > 0 and add_grad:
680
+ if disks:
681
+ self.add_orientation_disks(self.model.rescale(grad[:, :3],inplace=False), grad[:, 3:6], name + "_grad_cp",
682
+ **kwargs)
683
+ if vectors:
684
+ self.add_vector_data(self.model.rescale(grad[:, :3],inplace=False), grad[:, 3:6], name + "_grad_cp",
685
+ **kwargs)
686
+
687
+ if norm.shape[0] > 0 and add_grad:
688
+ if disks:
689
+ self.add_orientation_disks(self.model.rescale(norm[:, :3],inplace=False), norm[:, 3:6], name + "_norm_cp",
690
+ **kwargs)
691
+ if vectors:
692
+ self.add_vector_data(self.model.rescale(norm[:, :3],inplace=False), norm[:, 3:6], name + "_norm_cp",
693
+ **kwargs)
694
+ if value.shape[0] > 0 and add_value:
695
+ kwargs['range'] = [feature.min(), feature.max()]
696
+ self.add_value_data(self.model.rescale(value[:, :3],inplace=False), value[:, 3], name + "_value_cp",
697
+ **kwargs)
698
+ if tang.shape[0] > 0 and add_tang:
699
+ self.add_vector_data(self.model.rescale(tang[:, :3],inplace=False), tang[:, 3:6], name + "_tang_cp",
700
+ **kwargs)
701
+ if interface.shape[0] > 0 and add_interface:
702
+ self.add_points(self.model.rescale(interface[:,:3],inplace=False), name + "_interface_cp")
703
+
704
+ def add_intersection_lineation(self, feature, **kwargs):
705
+ name = feature.name
706
+ if 'name' in kwargs:
707
+ name = kwargs['name']
708
+ del kwargs['name']
709
+ intersection = feature.fold.foldframe.calculate_intersection_lineation(
710
+ feature.builder)
711
+ gpoints = feature.builder.interpolator.get_gradient_constraints()[:,:6]
712
+ npoints = feature.builder.interpolator.get_norm_constraints()[:,:6]
713
+ points = []
714
+ if gpoints.shape[0] > 0:
715
+ points.append(gpoints)
716
+ if npoints.shape[0] > 0:
717
+ points.append(npoints)
718
+ points = np.vstack(points)
719
+ if intersection.shape[0] > 0:
720
+ self.add_vector_data(self.model.rescale(points[:,:3],inplace=False), intersection, name + "_intersection")
721
+
722
+ def add_points(self, points, name, **kwargs):
723
+ """
724
+
725
+ Plot points location in the lavavu viewer
726
+
727
+ Parameters
728
+ ----------
729
+ points : numpy array of the points locations
730
+ name : string name of the object for lavavu
731
+ **kwargs : lavavu points kwargs
732
+
733
+ Returns
734
+ -------
735
+
736
+ """
737
+ p = self.lv.points(name, **kwargs)
738
+ p.vertices(points)
739
+
740
+ def add_vector_data(self, position, vector, name, **kwargs):
741
+ """
742
+
743
+ Plot point data with a vector component into the lavavu viewer
744
+
745
+ Parameters
746
+ ----------
747
+ position : numpy array N,3 for xyz locations
748
+ vector : numpy array of vector N,3
749
+ name : string name for the object in lavavu
750
+ kwargs to pass to lavavu
751
+
752
+ Returns
753
+ -------
754
+
755
+ """
756
+ if 'colour' not in kwargs:
757
+ kwargs['colour'] = 'black'
758
+ # normalise
759
+ if position.shape[0] > 0:
760
+ vector /= np.linalg.norm(vector, axis=1)[:, None]
761
+ vectorfield = self.lv.vectors(name, **kwargs)
762
+ vectorfield.vertices(position)
763
+ vectorfield.vectors(vector)
764
+ return
765
+ def add_orientation_disks(self,position,vector,name,symb_scale=1.,scaleshapes=None,shapelength=0,**kwargs):
766
+ if 'colour' not in kwargs:
767
+ kwargs['colour'] = 'black'
768
+ # normalise
769
+ if scaleshapes is None:
770
+ scaleshapes = np.max(self.model.maximum-self.model.origin)*0.014*symb_scale
771
+ if position.shape[0] > 0:
772
+ vector /= np.linalg.norm(vector, axis=1)[:, None]
773
+ vectorfield = self.lv.shapes(name, scaleshapes=scaleshapes,shapelength=shapelength,**kwargs)
774
+ vectorfield.vertices(position)
775
+ vectorfield.vectors(vector)
776
+ return
777
+
778
+ def add_value_data(self, position, value, name, **kwargs):
779
+ """
780
+
781
+ Plot points data with a value component
782
+
783
+ Parameters
784
+ ----------
785
+ position : numpy array N,3 for xyz locations
786
+ value : N array of values
787
+ name : string name of the object for lavavu
788
+ kwargs : kwargs to pass to lavavu
789
+
790
+ Returns
791
+ -------
792
+
793
+ """
794
+ if "pointtype" not in kwargs:
795
+ kwargs["pointtype"] = "sphere"
796
+ if "pointsize" not in kwargs:
797
+ kwargs["pointsize"] = 4
798
+ # set the colour map to diverge unless user decides otherwise
799
+ cmap = kwargs.get('cmap', "rainbow")
800
+ p = self.lv.points(name, **kwargs)
801
+ p.vertices(position)
802
+ p.values(value, "v")
803
+ p["colourby"] = "v"
804
+
805
+ if 'vmin' in kwargs and 'vmax' in kwargs:
806
+ logger.info('vmin {} and vmax {}'.format(kwargs['vmin'],kwargs['vmax']))
807
+ p.colourmap(cmap, range=(kwargs['vmin'],kwargs['vmax']))
808
+ else:
809
+ p.colourmap(cmap, range=(np.nanmin(value),np.nanmax(value)))
810
+
811
+ def add_fold(self, fold, **kwargs):
812
+ """
813
+ Draw the vector components of the fold at the locations
814
+
815
+ Parameters
816
+ ----------
817
+ fold - fold object
818
+ locations - numpy array of xyz
819
+
820
+ Returns
821
+ -------
822
+
823
+ """
824
+ locations = kwargs.get('locations', None)
825
+ if locations is None:
826
+ x = np.linspace(self.bounding_box[0, 0], self.bounding_box[1, 0], self.nsteps[0])
827
+ y = np.linspace(self.bounding_box[0, 1], self.bounding_box[1, 1], self.nsteps[1])
828
+ z = np.linspace(self.bounding_box[1, 2], self.bounding_box[0, 2], self.nsteps[2])
829
+ xx, yy, zz = np.meshgrid(x, y, z, indexing='ij')
830
+ locations = np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T
831
+ r2r, fold_axis, dgz = fold.get_deformed_orientation(locations)
832
+ locations = self.model.rescale(locations,inplace=False)
833
+ self.add_vector_data(locations, r2r, fold.name + '_direction', colour='red')
834
+ self.add_vector_data(locations, fold_axis, fold.name + '_axis', colour='black')
835
+ self.add_vector_data(locations, dgz, fold.name + '_norm', colour='green')
836
+
837
+ def interactive(self, popout=False):
838
+ """
839
+ Runs the lavavu viewer as either a jupyter notebook
840
+ inline interactive viewer or as a separate window
841
+
842
+ Returns
843
+ -------
844
+
845
+ """
846
+ if is_notebook() and popout is False:
847
+ self.lv.control.Panel()
848
+ self.lv.control.ObjectList()
849
+ self.lv.control.show()
850
+ if not is_notebook() or popout:
851
+ self.lv.control.Panel()
852
+ self.lv.control.ObjectList()
853
+ self.lv.interactive()
854
+
855
+ def add_support_box(self,geological_feature, paint=False, **kwargs):
856
+ name = kwargs.get('name', geological_feature.name + '_support')
857
+ box = np.vstack([geological_feature.interpolator.support.origin,geological_feature.interpolator.support.maximum])
858
+ points, tri = create_box(box,self.nsteps)
859
+
860
+ surf = self.lv.triangles(name)
861
+ surf.vertices(self.model.rescale(points))
862
+ surf.indices(tri)
863
+ if paint:
864
+ val =geological_feature.evaluate_value(self.model.scale(points))
865
+ surf.values(val, geological_feature.name)
866
+ surf["colourby"] = geological_feature.name
867
+ cmap = kwargs.get('cmap',lavavu.cubehelix(100))
868
+
869
+ logger.info("Adding scalar field of %s to viewer. Min: %f, max: %f" % (geological_feature.name,
870
+ geological_feature.min(),
871
+ geological_feature.max()))
872
+ vmin = kwargs.get('vmin', np.nanmin(val))
873
+ vmax = kwargs.get('vmax', np.nanmax(val))
874
+ surf.colourmap(cmap, range=(vmin, vmax))
875
+ def set_zscale(self,zscale):
876
+ """ Set the vertical scale for lavavu
877
+
878
+ just a simple wrapper for lavavu modelscale([xscale,yscale,zscale])
879
+
880
+ Parameters
881
+ ----------
882
+ zscale : float
883
+ vertical scale
884
+ """
885
+ self.lv.modelscale([1,1,zscale])
886
+
887
+ def set_viewer_rotation(self, rotation):
888
+ """
889
+ Set the viewer rotation given a list of rotations x,y,z
890
+
891
+ Parameters
892
+ ----------
893
+ rotation numpy array of 3 rotation
894
+
895
+ Returns
896
+ -------
897
+
898
+ """
899
+ self.lv.rotate(rotation)
900
+
901
+ def save(self, fname, **kwargs):
902
+ """
903
+ Calls lavavu.Viewer.image to save the viewer current state as an image
904
+
905
+ Parameters
906
+ ----------
907
+ fname - file name string including relative path
908
+ kwargs - optional kwargs to give to lavavu e.g. transparent, resolution
909
+
910
+ Returns
911
+ -------
912
+
913
+ """
914
+ self.lv.image(fname, **kwargs)
915
+
916
+ def export_to_webgl(self,fname, **kwargs ):
917
+
918
+ self.lv.webgl(fname,**kwargs)
919
+ def display(self, fname=None, **kwargs):
920
+ """
921
+ Calls the lv object display function. Shows a static image of the viewer inline.
922
+
923
+ Returns
924
+ -------
925
+
926
+ """
927
+ if fname:
928
+ self.lv.image(fname, **kwargs)
929
+
930
+ self.lv.display()
931
+
932
+ def image(self, name, **kwargs):
933
+ """
934
+ Calls the lv object image function to save the display state
935
+
936
+ Parameters
937
+ ----------
938
+ name : string
939
+ name of the image file to save
940
+ kwargs
941
+
942
+ Returns
943
+ -------
944
+
945
+ """
946
+ self.lv.image(name)
947
+
948
+ def image_array(self, **kwargs):
949
+ """Return the current viewer image image data as a numpy array
950
+
951
+ Returns
952
+ -------
953
+ image : np.array
954
+ image as a numpy array
955
+ """
956
+ return self.lv.rawimage(**kwargs).data
957
+
958
+ def rotatex(self, r):
959
+ """
960
+ Rotate the viewer in the x plane
961
+
962
+ Parameters
963
+ ----------
964
+ r : double
965
+ degrees to rotate, can be +ve or -ve
966
+
967
+ Returns
968
+ -------
969
+
970
+ """
971
+ self.lv.rotatex(r)
972
+
973
+ def rotatey(self, r):
974
+ """
975
+ Rotate the viewer in the Y plane
976
+
977
+ Parameters
978
+ ----------
979
+ r : double
980
+ degrees to rotate, can be +ve or -ve
981
+
982
+ Returns
983
+ -------
984
+
985
+ """
986
+ self.lv.rotatey(r)
987
+
988
+ def rotatez(self, r):
989
+ """
990
+ Rotate the viewer in the z plane
991
+
992
+ Parameters
993
+ ----------
994
+ r : double
995
+ degrees to rotate, can be +ve or -ve
996
+
997
+ Returns
998
+ -------
999
+
1000
+ """
1001
+ self.lv.rotatez(r)
1002
+
1003
+ def rotate(self, r):
1004
+ """
1005
+ Rotate by a vector of rotation angles
1006
+
1007
+ Parameters
1008
+ ----------
1009
+ r : list/numpy array
1010
+ a vector of rotations
1011
+
1012
+ Returns
1013
+ -------
1014
+
1015
+ """
1016
+ self.lv.rotate(r)
1017
+
1018
+ @property
1019
+ def rotation(self):
1020
+ """Accessor for the viewer rotation
1021
+ Returns
1022
+ -------
1023
+ list
1024
+ x,y,z rotations
1025
+ """
1026
+ return self.lv['xyzrotate']
1027
+
1028
+ @rotation.setter
1029
+ def rotation(self,xyz):
1030
+ """Set the rotation of the viewer
1031
+
1032
+ Parameters
1033
+ ----------
1034
+ xyz : list like
1035
+ x y z rotations
1036
+ """
1037
+ self.lv.rotation(xyz)
1038
+
1039
+ @property
1040
+ def border(self):
1041
+ """The width of the border around the model area
1042
+
1043
+ Returns
1044
+ -------
1045
+ border : double
1046
+ [description]
1047
+ """
1048
+ return self.lv['border']
1049
+
1050
+ @border.setter
1051
+ def border(self, border):
1052
+ """Setter for the border
1053
+
1054
+ Parameters
1055
+ ----------
1056
+ border : double
1057
+ set the thickness of the border around objects
1058
+ """
1059
+ self.lv['border'] = border
1060
+
1061
+ def clear(self):
1062
+ """Remove all objects from the viewer
1063
+ """
1064
+ self.lv.clear()
1065
+
1066
+ @property
1067
+ def xmin(self):
1068
+ return self.lv['xmin']
1069
+
1070
+ @xmin.setter
1071
+ def xmin(self, xmin):
1072
+ self.lv['xmin'] = xmin
1073
+
1074
+ @property
1075
+ def xmax(self):
1076
+ return self.lv['xmax']
1077
+
1078
+ @xmax.setter
1079
+ def xmax(self, xmax):
1080
+ self.lv['xmax'] = xmax
1081
+
1082
+ @property
1083
+ def ymin(self):
1084
+ return self.lv['ymin']
1085
+
1086
+ @ymin.setter
1087
+ def ymin(self, ymin):
1088
+ self.lv['ymin'] = ymin
1089
+
1090
+ @property
1091
+ def ymax(self):
1092
+ return self.lv['ymax']
1093
+
1094
+ @ymax.setter
1095
+ def ymax(self, ymax):
1096
+ self.lv['ymax'] = ymax
1097
+
1098
+ @property
1099
+ def zmin(self):
1100
+ return self.lv['zmax']
1101
+
1102
+ @zmin.setter
1103
+ def zmin(self, zmin):
1104
+ self.lv['zmin'] = zmin
1105
+
1106
+ @property
1107
+ def zmax(self):
1108
+ return self.lv['zmax']
1109
+
1110
+ @zmax.setter
1111
+ def zmax(self, zmax):
1112
+ self.lv['zmax'] = zmax
1113
+