Coverage for /usr/local/lib/python3.11/dist-packages/pyrocko/plot/beachball.py: 87%
431 statements
« prev ^ index » next coverage.py v6.5.0, created at 2024-03-07 11:54 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2024-03-07 11:54 +0000
1# http://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
6from math import pi as PI
7import logging
8import numpy as num
10from matplotlib.collections import PatchCollection
11from matplotlib.patches import Polygon
12from matplotlib.transforms import Transform
13from matplotlib.colors import LinearSegmentedColormap
15from pyrocko import moment_tensor as mtm
17logger = logging.getLogger('pyrocko.plot.beachball')
19NA = num.newaxis
20d2r = num.pi / 180.
23def view_rotation(strike, dip):
24 return mtm.euler_to_matrix(
25 dip*d2r, strike*d2r, -90.*d2r)
28_view_south = view_rotation(90., 90.)
29_view_north = view_rotation(-90., 90.)
30_view_east = view_rotation(0., 90.)
31_view_west = view_rotation(180., 90.)
34class BeachballError(Exception):
35 pass
38class _FixedPointOffsetTransform(Transform):
39 def __init__(self, trans, dpi_scale_trans, fixed_point):
40 Transform.__init__(self)
41 self.input_dims = self.output_dims = 2
42 self.has_inverse = False
43 self.trans = trans
44 self.dpi_scale_trans = dpi_scale_trans
45 self.fixed_point = num.asarray(fixed_point, dtype=num.float64)
47 def transform_non_affine(self, values):
48 fp = self.trans.transform(self.fixed_point)
49 return fp + self.dpi_scale_trans.transform(values)
52def vnorm(points):
53 return num.sqrt(num.sum(points**2, axis=1))
56def clean_poly(points):
57 if not num.all(points[0, :] == points[-1, :]):
58 points = num.vstack((points, points[0:1, :]))
60 dupl = num.concatenate(
61 (num.all(points[1:, :] == points[:-1, :], axis=1), [False]))
62 points = points[num.logical_not(dupl)]
63 return points
66def close_poly(points):
67 if not num.all(points[0, :] == points[-1, :]):
68 points = num.vstack((points, points[0:1, :]))
70 return points
73def circulation(points, axis):
74 # assert num.all(points[:, axis] >= 0.0) or num.all(points[:, axis] <= 0.0)
76 points2 = points[:, ((axis+2) % 3, (axis+1) % 3)].copy()
77 points2 *= 1.0 / num.sqrt(1.0 + num.abs(points[:, axis]))[:, num.newaxis]
79 result = -num.sum(
80 (points2[1:, 0] - points2[:-1, 0]) *
81 (points2[1:, 1] + points2[:-1, 1]))
83 result -= (points2[0, 0] - points2[-1, 0]) \
84 * (points2[0, 1] + points2[-1, 1])
85 return result
88def spoly_cut(l_points, axis=0, nonsimple=True, arcres=181):
89 dphi = 2.*PI / (2*arcres)
91 # cut sub-polygons and gather crossing point information
92 crossings = []
93 snippets = {}
94 for ipath, points in enumerate(l_points):
95 if not num.all(points[0, :] == points[-1, :]):
96 points = num.vstack((points, points[0:1, :]))
98 # get upward crossing points
99 iup = num.where(num.logical_and(points[:-1, axis] <= 0.,
100 points[1:, axis] > 0.))[0]
101 aup = - points[iup, axis] / (points[iup+1, axis] - points[iup, axis])
102 pup = points[iup, :] + aup[:, num.newaxis] * (points[iup+1, :] -
103 points[iup, :])
104 phiup = num.arctan2(pup[:, (axis+2) % 3], pup[:, (axis+1) % 3])
106 for i in range(len(iup)):
107 crossings.append((phiup[i], ipath, iup[i], 1, pup[i], [1, -1]))
109 # get downward crossing points
110 idown = num.where(num.logical_and(points[:-1, axis] > 0.,
111 points[1:, axis] <= 0.))[0]
112 adown = - points[idown+1, axis] / (points[idown, axis] -
113 points[idown+1, axis])
114 pdown = points[idown+1, :] + adown[:, num.newaxis] * (
115 points[idown, :] - points[idown+1, :])
116 phidown = num.arctan2(pdown[:, (axis+2) % 3], pdown[:, (axis+1) % 3])
118 for i in range(idown.size):
119 crossings.append(
120 (phidown[i], ipath, idown[i], -1, pdown[i], [1, -1]))
122 icuts = num.sort(num.concatenate((iup, idown)))
124 for i in range(icuts.size-1):
125 snippets[ipath, icuts[i]] = (
126 ipath, icuts[i+1], points[icuts[i]+1:icuts[i+1]+1])
128 if icuts.size:
129 points_last = num.concatenate((
130 points[icuts[-1]+1:],
131 points[:icuts[0]+1]))
133 snippets[ipath, icuts[-1]] = (ipath, icuts[0], points_last)
134 else:
135 snippets[ipath, 0] = (ipath, 0, points)
137 crossings.sort()
139 # assemble new sub-polygons
140 current = snippets.pop(list(snippets.keys())[0])
141 outs = [[]]
142 while True:
143 outs[-1].append(current[2])
144 for i, c1 in enumerate(crossings):
145 if c1[1:3] == current[:2]:
146 direction = -1 * c1[3]
147 break
148 else:
149 if not snippets:
150 break
151 current = snippets.pop(list(snippets.keys())[0])
152 outs.append([])
153 continue
155 while True:
156 i = (i + direction) % len(crossings)
157 if crossings[i][3] == direction and direction in crossings[i][-1]:
158 break
160 c2 = crossings[i]
161 c2[-1].remove(direction)
163 phi1 = c1[0]
164 phi2 = c2[0]
165 if direction == 1:
166 if phi1 > phi2:
167 phi2 += PI * 2.
169 if direction == -1:
170 if phi1 < phi2:
171 phi2 -= PI * 2.
173 n = int(abs(phi2 - phi1) / dphi) + 2
175 phis = num.linspace(phi1, phi2, n)
176 cpoints = num.zeros((n, 3))
177 cpoints[:, (axis+1) % 3] = num.cos(phis)
178 cpoints[:, (axis+2) % 3] = num.sin(phis)
179 cpoints[:, axis] = 0.0
181 outs[-1].append(cpoints)
183 try:
184 current = snippets[c2[1:3]]
185 del snippets[c2[1:3]]
187 except KeyError:
188 if not snippets:
189 break
191 current = snippets.pop(list(snippets.keys())[0])
192 outs.append([])
194 # separate hemispheres, force polygons closed, remove duplicate points
195 # remove polygons with less than 3 points (4, when counting repeated
196 # endpoint)
198 outs_upper = []
199 outs_lower = []
200 for out in outs:
201 if out:
202 out = clean_poly(num.vstack(out))
203 if out.shape[0] >= 4:
204 if num.sum(out[:, axis]) > 0.0:
205 outs_upper.append(out)
206 else:
207 outs_lower.append(out)
209 if nonsimple and (
210 len(crossings) == 0 or
211 len(outs_upper) == 0 or
212 len(outs_lower) == 0):
214 # check if we are cutting between holes
215 need_divider = False
216 if outs_upper:
217 candis = sorted(
218 outs_upper, key=lambda out: num.min(out[:, axis]))
220 if circulation(candis[0], axis) > 0.0:
221 need_divider = True
223 if outs_lower:
224 candis = sorted(
225 outs_lower, key=lambda out: num.max(out[:, axis]))
227 if circulation(candis[0], axis) < 0.0:
228 need_divider = True
230 if need_divider:
231 phi1 = 0.
232 phi2 = PI*2.
233 n = int(abs(phi2 - phi1) / dphi) + 2
235 phis = num.linspace(phi1, phi2, n)
236 cpoints = num.zeros((n, 3))
237 cpoints[:, (axis+1) % 3] = num.cos(phis)
238 cpoints[:, (axis+2) % 3] = num.sin(phis)
239 cpoints[:, axis] = 0.0
241 outs_upper.append(cpoints)
242 outs_lower.append(cpoints[::-1, :])
244 return outs_lower, outs_upper
247def numpy_rtp2xyz(rtp):
248 r = rtp[:, 0]
249 theta = rtp[:, 1]
250 phi = rtp[:, 2]
251 vecs = num.empty(rtp.shape, dtype=num.float64)
252 vecs[:, 0] = r*num.sin(theta)*num.cos(phi)
253 vecs[:, 1] = r*num.sin(theta)*num.sin(phi)
254 vecs[:, 2] = r*num.cos(theta)
255 return vecs
258def numpy_xyz2rtp(xyz):
259 x, y, z = xyz[:, 0], xyz[:, 1], xyz[:, 2]
260 vecs = num.empty(xyz.shape, dtype=num.float64)
261 vecs[:, 0] = num.sqrt(x**2+y**2+z**2)
262 vecs[:, 1] = num.arctan2(num.sqrt(x**2+y**2), z)
263 vecs[:, 2] = num.arctan2(y, x)
264 return vecs
267def circle_points(aphi, sign=1.0):
268 vecs = num.empty((aphi.size, 3), dtype=num.float64)
269 vecs[:, 0] = num.cos(sign*aphi)
270 vecs[:, 1] = num.sin(sign*aphi)
271 vecs[:, 2] = 0.0
272 return vecs
275def eig2gx(eig, arcres=181):
276 aphi = num.linspace(0., 2.*PI, arcres)
277 ep, en, et, vp, vn, vt = eig
279 mt_sign = num.sign(ep + en + et)
281 groups = []
282 for (pt_name, pt_sign) in [('P', -1.), ('T', 1.)]:
283 patches = []
284 patches_lower = []
285 patches_upper = []
286 lines = []
287 lines_lower = []
288 lines_upper = []
289 for iperm, (va, vb, vc, ea, eb, ec) in enumerate([
290 (vp, vn, vt, ep, en, et),
291 (vt, vp, vn, et, ep, en)]): # (vn, vt, vp, en, et, ep)]):
293 perm_sign = [-1.0, 1.0][iperm]
294 to_e = num.vstack((vb, vc, va))
295 from_e = to_e.T
297 poly_es = []
298 polys = []
299 for sign in (-1., 1.):
300 xphi = perm_sign*pt_sign*sign*aphi
301 denom = eb*num.cos(xphi)**2 + ec*num.sin(xphi)**2
302 if num.any(denom == 0.):
303 continue
305 Y = -ea/denom
306 if num.any(Y < 0.):
307 continue
309 xtheta = num.arctan(num.sqrt(Y))
310 rtp = num.empty(xphi.shape+(3,), dtype=num.float64)
311 rtp[:, 0] = 1.
312 if sign > 0:
313 rtp[:, 1] = xtheta
314 else:
315 rtp[:, 1] = PI - xtheta
317 rtp[:, 2] = xphi
318 poly_e = numpy_rtp2xyz(rtp)
319 poly = num.dot(from_e, poly_e.T).T
320 poly[:, 2] -= 0.001
322 poly_es.append(poly_e)
323 polys.append(poly)
325 if polys:
326 polys_lower, polys_upper = spoly_cut(polys, 2, arcres=arcres)
327 lines.extend(polys)
328 lines_lower.extend(polys_lower)
329 lines_upper.extend(polys_upper)
331 if poly_es:
332 for aa in spoly_cut(poly_es, 0, arcres=arcres):
333 for bb in spoly_cut(aa, 1, arcres=arcres):
334 for cc in spoly_cut(bb, 2, arcres=arcres):
335 for poly_e in cc:
336 poly = num.dot(from_e, poly_e.T).T
337 poly[:, 2] -= 0.001
338 polys_lower, polys_upper = spoly_cut(
339 [poly], 2, nonsimple=False, arcres=arcres)
341 patches.append(poly)
342 patches_lower.extend(polys_lower)
343 patches_upper.extend(polys_upper)
345 if not patches:
346 if mt_sign * pt_sign == 1.:
347 patches_lower.append(circle_points(aphi, -1.0))
348 patches_upper.append(circle_points(aphi, 1.0))
349 lines_lower.append(circle_points(aphi, -1.0))
350 lines_upper.append(circle_points(aphi, 1.0))
352 groups.append((
353 pt_name,
354 patches, patches_lower, patches_upper,
355 lines, lines_lower, lines_upper))
357 return groups
360def extr(points):
361 pmean = num.mean(points, axis=0)
362 return points + pmean*0.05
365def draw_eigenvectors_mpl(eig, axes):
366 vp, vn, vt = eig[3:]
367 for lab, v in [('P', vp), ('N', vn), ('T', vt)]:
368 sign = num.sign(v[2]) + (v[2] == 0.0)
369 axes.plot(sign*v[1], sign*v[0], 'o', color='black')
370 axes.text(sign*v[1], sign*v[0], ' '+lab)
373def project(points, projection='lambert'):
374 '''
375 Project 3D points to 2D.
377 :param projection:
378 Projection to use. Choices: ``'lambert'``, ``'stereographic'``,
379 ``'orthographic'``.
380 :type projection:
381 str
382 '''
383 points_out = points[:, :2].copy()
384 if projection == 'lambert':
385 factor = 1.0 / num.sqrt(1.0 + points[:, 2])
386 elif projection == 'stereographic':
387 factor = 1.0 / (1.0 + points[:, 2])
388 elif projection == 'orthographic':
389 factor = None
390 else:
391 raise BeachballError(
392 'invalid argument for projection: %s' % projection)
394 if factor is not None:
395 points_out *= factor[:, num.newaxis]
397 return points_out
400def inverse_project(points, projection='lambert'):
401 points_out = num.zeros((points.shape[0], 3))
403 rsqr = points[:, 0]**2 + points[:, 1]**2
404 if projection == 'lambert':
405 points_out[:, 2] = 1.0 - rsqr
406 points_out[:, 1] = num.sqrt(2.0 - rsqr) * points[:, 1]
407 points_out[:, 0] = num.sqrt(2.0 - rsqr) * points[:, 0]
408 elif projection == 'stereographic':
409 points_out[:, 2] = - (rsqr - 1.0) / (rsqr + 1.0)
410 points_out[:, 1] = 2.0 * points[:, 1] / (rsqr + 1.0)
411 points_out[:, 0] = 2.0 * points[:, 0] / (rsqr + 1.0)
412 elif projection == 'orthographic':
413 points_out[:, 2] = num.sqrt(num.maximum(1.0 - rsqr, 0.0))
414 points_out[:, 1] = points[:, 1]
415 points_out[:, 0] = points[:, 0]
416 else:
417 raise BeachballError(
418 'invalid argument for projection: %s' % projection)
420 return points_out
423def deco_part(mt, mt_type='full', view='top'):
424 mt = mtm.as_mt(mt)
426 if isinstance(view, str):
427 if view == 'top':
428 pass
429 elif view == 'north':
430 mt = mt.rotated(_view_north)
431 elif view == 'south':
432 mt = mt.rotated(_view_south)
433 elif view == 'east':
434 mt = mt.rotated(_view_east)
435 elif view == 'west':
436 mt = mt.rotated(_view_west)
437 elif isinstance(view, tuple):
438 mt = mt.rotated(view_rotation(*view))
439 else:
440 raise BeachballError(
441 'Invaild argument for `view`. Allowed values are "top", "north", '
442 '"south", "east", "west" or a tuple of angles `(strike, dip)` '
443 'orienting the view plane.')
445 if mt_type == 'full':
446 return mt
448 res = mt.standard_decomposition()
449 m = dict(
450 dc=res[1][2],
451 deviatoric=res[3][2])[mt_type]
453 return mtm.MomentTensor(m=m)
456def choose_transform(axes, size_units, position, size):
458 if size_units == 'points':
459 transform = _FixedPointOffsetTransform(
460 axes.transData,
461 axes.figure.dpi_scale_trans,
462 position)
464 if size is None:
465 size = 12.
467 size = size * 0.5 / 72.
468 position = (0., 0.)
470 elif size_units == 'data':
471 transform = axes.transData
473 if size is None:
474 size = 1.0
476 size = size * 0.5
478 elif size_units == 'axes':
479 transform = axes.transAxes
480 if size is None:
481 size = 1.
483 size = size * .5
485 else:
486 raise BeachballError(
487 'invalid argument for size_units: %s' % size_units)
489 position = num.asarray(position, dtype=num.float64)
491 return transform, position, size
494def mt2beachball(
495 mt,
496 beachball_type='deviatoric',
497 position=(0., 0.),
498 size=None,
499 color_t='red',
500 color_p='white',
501 edgecolor='black',
502 linewidth=2,
503 projection='lambert',
504 view='top'):
506 position = num.asarray(position, dtype=num.float64)
507 size = size or 1
508 mt = deco_part(mt, beachball_type, view)
510 eig = mt.eigensystem()
511 if eig[0] == 0. and eig[1] == 0. and eig[2] == 0:
512 raise BeachballError('eigenvalues are zero')
514 data = []
515 for (group, patches, patches_lower, patches_upper,
516 lines, lines_lower, lines_upper) in eig2gx(eig):
518 if group == 'P':
519 color = color_p
520 else:
521 color = color_t
523 for poly in patches_upper:
524 verts = project(poly, projection)[:, ::-1] * size + \
525 position[NA, :]
526 data.append((verts, color, color, 1.0))
528 for poly in lines_upper:
529 verts = project(poly, projection)[:, ::-1] * size + \
530 position[NA, :]
531 data.append((verts, 'none', edgecolor, linewidth))
532 return data
535def plot_beachball_mpl(
536 mt, axes,
537 beachball_type='deviatoric',
538 position=(0., 0.),
539 size=None,
540 zorder=0,
541 color_t='red',
542 color_p='white',
543 edgecolor='black',
544 linewidth=2,
545 alpha=1.0,
546 arcres=181,
547 decimation=1,
548 projection='lambert',
549 size_units='points',
550 view='top'):
552 '''
553 Plot beachball diagram to a Matplotlib plot
555 :param mt: :py:class:`pyrocko.moment_tensor.MomentTensor` object or an
556 array or sequence which can be converted into an MT object
557 :param beachball_type: ``'deviatoric'`` (default), ``'full'``, or ``'dc'``
558 :param position: position of the beachball in data coordinates
559 :param size: diameter of the beachball either in points or in data
560 coordinates, depending on the ``size_units`` setting
561 :param zorder: (passed through to matplotlib drawing functions)
562 :param color_t: color for compressional quadrants (default: ``'red'``)
563 :param color_p: color for extensive quadrants (default: ``'white'``)
564 :param edgecolor: color for lines (default: ``'black'``)
565 :param linewidth: linewidth in points (default: ``2``)
566 :param alpha: (passed through to matplotlib drawing functions)
567 :param projection: ``'lambert'`` (default), ``'stereographic'``, or
568 ``'orthographic'``
569 :param size_units: ``'points'`` (default) or ``'data'``, where the
570 latter causes the beachball to be projected in the plots data
571 coordinates (axes must have an aspect ratio of 1.0 or the
572 beachball will be shown distorted when using this).
573 :param view: View the beachball from ``'top'``, ``'north'``, ``'south'``,
574 ``'east'`` or ``'west'``, or project onto plane given by
575 ``(strike, dip)``. Useful to show beachballs in cross-sections.
576 Default is ``'top'``.
577 '''
579 transform, position, size = choose_transform(
580 axes, size_units, position, size)
582 mt = deco_part(mt, beachball_type, view)
584 eig = mt.eigensystem()
585 if eig[0] == 0. and eig[1] == 0. and eig[2] == 0:
586 raise BeachballError('eigenvalues are zero')
588 data = []
589 for (group, patches, patches_lower, patches_upper,
590 lines, lines_lower, lines_upper) in eig2gx(eig, arcres):
592 if group == 'P':
593 color = color_p
594 else:
595 color = color_t
597 # plot "upper" features for lower hemisphere, because coordinate system
598 # is NED
600 for poly in patches_upper:
601 verts = project(poly, projection)[:, ::-1] * size + position[NA, :]
602 if alpha == 1.0:
603 data.append(
604 (verts[::decimation], color, color, linewidth))
605 else:
606 data.append(
607 (verts[::decimation], color, 'none', 0.0))
609 for poly in lines_upper:
610 verts = project(poly, projection)[:, ::-1] * size + position[NA, :]
611 data.append(
612 (verts[::decimation], 'none', edgecolor, linewidth))
614 patches = []
615 for (path, facecolor, edgecolor, linewidth) in data:
616 patches.append(Polygon(
617 xy=path, facecolor=facecolor,
618 edgecolor=edgecolor,
619 linewidth=linewidth,
620 alpha=alpha))
622 collection = PatchCollection(
623 patches, zorder=zorder, transform=transform, match_original=True)
625 axes.add_artist(collection)
626 return collection
629def amplitudes_ned(mt, vecs):
630 ep, en, et, vp, vn, vt = mt.eigensystem()
631 to_e = num.vstack((vn, vt, vp))
632 vecs_e = num.dot(to_e, vecs.T).T
633 rtp = numpy_xyz2rtp(vecs_e)
634 atheta, aphi = rtp[:, 1], rtp[:, 2]
635 return ep * num.cos(atheta)**2 + (
636 en * num.cos(aphi)**2 + et * num.sin(aphi)**2) * num.sin(atheta)**2
639def amplitudes(mt, azimuths, takeoff_angles):
640 '''
641 Get beachball amplitude values for selected azimuths and takeoff angles.
643 :param azimuths:
644 Azimuths, measured clockwise from north [deg].
645 :type azimuths:
646 :py:class:`~numpy.ndarray`
648 :param takeoff_angles:
649 Takeoff angles, measured from downward vertical [deg].
650 :type takeoff_angles:
651 :py:class:`~numpy.ndarray`
652 '''
653 azimuths = num.asarray(azimuths, dtype=float)
654 takeoff_angles = num.asarray(takeoff_angles, dtype=float)
655 assert azimuths.size == takeoff_angles.size
656 rtps = num.vstack(
657 (num.ones(azimuths.size), takeoff_angles*d2r, azimuths*d2r)).T
658 vecs = numpy_rtp2xyz(rtps)
659 return amplitudes_ned(mt, vecs)
662def mts2amps(
663 mts,
664 projection,
665 beachball_type,
666 grid_resolution=200,
667 mask=True,
668 view='top'):
670 n_balls = len(mts)
671 nx = grid_resolution
672 ny = grid_resolution
674 x = num.linspace(-1., 1., nx)
675 y = num.linspace(-1., 1., ny)
677 vecs2 = num.zeros((nx * ny, 2), dtype=num.float64)
678 vecs2[:, 0] = num.tile(x, ny)
679 vecs2[:, 1] = num.repeat(y, nx)
681 ii_ok = vecs2[:, 0]**2 + vecs2[:, 1]**2 <= 1.0
682 amps = num.full(nx * ny, num.nan, dtype=num.float64)
684 amps[ii_ok] = 0.
685 vecs3_ok = inverse_project(vecs2[ii_ok, :], projection)
687 for mt in mts:
688 amps_ok = amplitudes_ned(deco_part(mt, beachball_type, view), vecs3_ok)
689 if mask:
690 amps_ok[amps_ok > 0] = 1.
691 amps_ok[amps_ok < 0] = 0.
693 amps[ii_ok] += amps_ok
695 return num.reshape(amps, (ny, nx)) / n_balls, x, y
698def plot_fuzzy_beachball_mpl_pixmap(
699 mts, axes,
700 best_mt=None,
701 beachball_type='deviatoric',
702 position=(0., 0.),
703 size=None,
704 zorder=0,
705 color_t='red',
706 color_p='white',
707 edgecolor='black',
708 best_color='red',
709 linewidth=2,
710 alpha=1.0,
711 projection='lambert',
712 size_units='data',
713 grid_resolution=200,
714 method='imshow',
715 view='top'):
716 '''
717 Plot fuzzy beachball from a list of given MomentTensors
719 :param mts: list of
720 :py:class:`pyrocko.moment_tensor.MomentTensor` object or an
721 array or sequence which can be converted into an MT object
722 :param best_mt: :py:class:`pyrocko.moment_tensor.MomentTensor` object or
723 an array or sequence which can be converted into an MT object
724 of most likely or minimum misfit solution to extra highlight
725 :param best_color: mpl color for best MomentTensor edges,
726 polygons are not plotted
728 See plot_beachball_mpl for other arguments
729 '''
730 if size_units == 'points':
731 raise BeachballError(
732 'size_units="points" not supported in '
733 'plot_fuzzy_beachball_mpl_pixmap')
735 transform, position, size = choose_transform(
736 axes, size_units, position, size)
738 amps, x, y = mts2amps(
739 mts,
740 grid_resolution=grid_resolution,
741 projection=projection,
742 beachball_type=beachball_type,
743 mask=True,
744 view=view)
746 ncolors = 256
747 cmap = LinearSegmentedColormap.from_list(
748 'dummy', [color_p, color_t], N=ncolors)
750 levels = num.linspace(0, 1., ncolors)
751 if method == 'contourf':
752 axes.contourf(
753 position[0] + y * size, position[1] + x * size, amps.T,
754 levels=levels,
755 cmap=cmap,
756 transform=transform,
757 zorder=zorder,
758 alpha=alpha)
760 elif method == 'imshow':
761 axes.imshow(
762 amps.T,
763 extent=(
764 position[0] + y[0] * size,
765 position[0] + y[-1] * size,
766 position[1] - x[0] * size,
767 position[1] - x[-1] * size),
768 cmap=cmap,
769 transform=transform,
770 zorder=zorder-0.1,
771 alpha=alpha)
772 else:
773 assert False, 'invalid `method` argument'
775 # draw optimum edges
776 if best_mt is not None:
777 best_amps, bx, by = mts2amps(
778 [best_mt],
779 grid_resolution=grid_resolution,
780 projection=projection,
781 beachball_type=beachball_type,
782 mask=False)
784 axes.contour(
785 position[0] + by * size, position[1] + bx * size, best_amps.T,
786 levels=[0.],
787 colors=[best_color],
788 linewidths=linewidth,
789 transform=transform,
790 zorder=zorder,
791 alpha=alpha)
793 phi = num.linspace(0., 2 * PI, 361)
794 x = num.cos(phi)
795 y = num.sin(phi)
796 axes.plot(
797 position[0] + x * size, position[1] + y * size,
798 linewidth=linewidth,
799 color=edgecolor,
800 transform=transform,
801 zorder=zorder,
802 alpha=alpha)
805def plot_beachball_mpl_construction(
806 mt, axes,
807 show='patches',
808 beachball_type='deviatoric',
809 view='top'):
811 mt = deco_part(mt, beachball_type, view)
812 eig = mt.eigensystem()
814 for (group, patches, patches_lower, patches_upper,
815 lines, lines_lower, lines_upper) in eig2gx(eig):
817 if group == 'P':
818 color = 'blue'
819 lw = 1
820 else:
821 color = 'red'
822 lw = 1
824 if show == 'patches':
825 for poly in patches_upper:
826 px, py, pz = poly.T
827 axes.plot(*extr(poly).T, color=color, lw=lw, alpha=0.5)
829 if show == 'lines':
830 for poly in lines_upper:
831 px, py, pz = poly.T
832 axes.plot(*extr(poly).T, color=color, lw=lw, alpha=0.5)
835def plot_beachball_mpl_pixmap(
836 mt, axes,
837 beachball_type='deviatoric',
838 position=(0., 0.),
839 size=None,
840 zorder=0,
841 color_t='red',
842 color_p='white',
843 edgecolor='black',
844 linewidth=2,
845 alpha=1.0,
846 projection='lambert',
847 size_units='data',
848 view='top'):
850 if size_units == 'points':
851 raise BeachballError(
852 'size_units="points" not supported in plot_beachball_mpl_pixmap')
854 transform, position, size = choose_transform(
855 axes, size_units, position, size)
857 mt = deco_part(mt, beachball_type, view)
859 ep, en, et, vp, vn, vt = mt.eigensystem()
861 amps, x, y = mts2amps(
862 [mt], projection, beachball_type, grid_resolution=200, mask=False)
864 axes.contourf(
865 position[0] + y * size, position[1] + x * size, amps.T,
866 levels=[-num.inf, 0., num.inf],
867 colors=[color_p, color_t],
868 transform=transform,
869 zorder=zorder,
870 alpha=alpha)
872 axes.contour(
873 position[0] + y * size, position[1] + x * size, amps.T,
874 levels=[0.],
875 colors=[edgecolor],
876 linewidths=linewidth,
877 transform=transform,
878 zorder=zorder,
879 alpha=alpha)
881 phi = num.linspace(0., 2 * PI, 361)
882 x = num.cos(phi)
883 y = num.sin(phi)
884 axes.plot(
885 position[0] + x * size, position[1] + y * size,
886 linewidth=linewidth,
887 color=edgecolor,
888 transform=transform,
889 zorder=zorder,
890 alpha=alpha)
893if __name__ == '__main__':
894 import sys
895 import os
896 import matplotlib.pyplot as plt
897 from pyrocko import model
899 args = sys.argv[1:]
901 data = []
902 for iarg, arg in enumerate(args):
904 if os.path.exists(arg):
905 events = model.load_events(arg)
906 for ev in events:
907 if not ev.moment_tensor:
908 logger.warning('no moment tensor given for event')
909 continue
911 data.append((ev.name, ev.moment_tensor))
912 else:
913 vals = list(map(float, arg.split(',')))
914 mt = mtm.as_mt(vals)
915 data.append(('%i' % (iarg+1), mt))
917 n = len(data)
919 ncols = 1
920 while ncols**2 < n:
921 ncols += 1
923 nrows = ncols
925 fig = plt.figure()
926 axes = fig.add_subplot(1, 1, 1, aspect=1.)
927 axes.axison = False
928 axes.set_xlim(-0.05 - ncols, ncols + 0.05)
929 axes.set_ylim(-0.05 - nrows, nrows + 0.05)
931 for ibeach, (name, mt) in enumerate(data):
932 irow = ibeach // ncols
933 icol = ibeach % ncols
934 plot_beachball_mpl(
935 mt, axes,
936 position=(icol*2-ncols+1, -irow*2+nrows-1),
937 size_units='data')
939 axes.annotate(
940 name,
941 xy=(icol*2-ncols+1, -irow*2+nrows-2),
942 xycoords='data',
943 xytext=(0, 0),
944 textcoords='offset points',
945 verticalalignment='center',
946 horizontalalignment='center',
947 rotation=0.)
949 plt.show()