# http://pyrocko.org - GPLv3 # # The Pyrocko Developers, 21st Century # ---|P------/S----------~Lg---------- Simple queries and statistical analysis'''
'vp': '$V_P$', 'p': '$V_P$', 'vs': '$V_S$', 's': '$V_S$', }
import matplotlib.pyplot as plt
if axes is None: fig = plt.figure() return fig, fig.gca() return axes.figure, axes
from matplotlib.ticker import ScalarFormatter, AutoLocator
class FormatVelocities(ScalarFormatter): @staticmethod def __call__(value, pos): return u'%.1f' % ((value-offset) * scale)
class OffsetLocator(AutoLocator): def tick_values(self, vmin, vmax): return [v + offset for v in AutoLocator.tick_values(self, vmin, vmax)]
ax.get_xaxis().set_major_formatter(FormatVelocities()) ax.get_xaxis().set_major_locator(OffsetLocator())
optional=True, help='Unique ID of measurement')
help='Latitude [deg]') help='Longitude [deg]') default=num.nan, help='Elevation [m]') shape=(None, 1), help='P Wave velocities [m/s]') shape=(None, 1), help='S Wave velocities [m/s]') shape=(None, 1), help='Interface depth, top [m]') shape=(None, 1), help='Interface thickness [m]')
optional=True, help='Heatflow [W/m^2]') optional=True, help='Geographic Location') optional=True, help='Geological Province') optional=True, help='Geological Age') optional=True, help='Measurement method') optional=True, help='Publication Reference') help='Publication Date')
provinceKey(self.geographical_location), self.geographical_location)
def publication_year__(self): return pubYear(self.publication_reference)
'''Get a continuous velocity function at arbitrary depth
:param depth: Depths to interpolate :type depth: :class:`numpy.ndarray` :param phase: P or S wave velocity, **p** or **s** :type phase: str, optional :param stepped: Use a stepped velocity function or gradient :type stepped: bool :returns: velocities at requested depths :rtype: :class:`numpy.ndarray` '''
if phase not in ['s', 'p']: raise AttributeError('Phase has to be either \'p\' or \'s\'.')
if phase == 'p': vel = self._step_vp if stepped else self.vp elif phase == 's': vel = self._step_vs if stepped else self.vs d = self._step_d if stepped else self.d
if vel.size == 0: raise ProfileEmpty('Phase %s does not contain velocities' % phase)
try: res = num.interp(depths, d, vel, left=num.nan, right=num.nan) except ValueError: raise ValueError('Could not interpolate velocity profile.')
return res
''' Plot the velocity profile, see :class:`pyrocko.cake`.
:param axes: Axes to plot into. :type axes: :class:`matplotlib.Axes`'''
import matplotlib.pyplot as plt
fig, ax = _getCanvas(axes) my_model_plot(self.getLayeredModel(), axes=axes) ax.set_title('Global Crustal Database\n' 'Velocity Structure at {p.lat:.4f}N, ' ' {p.lat:.4f}E (uid {p.uid})'.format(p=self)) if axes is None: plt.show()
''' Get a layered model, see :class:`pyrocko.cake.LayeredModel`. ''' def iterLines(): for il, m in enumerate(self.iterLayers()): yield self.d[il], m, ''
return LayeredModel.from_scanlines(iterLines())
''' Iterator returns a :class:`pyrocko.cake.Material` for each layer''' for il in range(self.nlayers): yield Material(vp=self.vp[il], vs=self.vs[il])
def geog_loc_long(self): return provinceKey(self.geog_loc)
def geol_age_long(self): return ageKey(self.geol_age)
def has_s(self): return num.any(self.vp)
def has_p(self): return num.any(self.vs)
''' Get weeded representation of layers used in the profile. See :func:`pyrocko.cake.get_weeded` for details. ''' weeded = num.zeros((self.nlayers, 4)) weeded[:, 0] = self.d weeded[:, 1] = self.vp weeded[:, 2] = self.vs
output = '' for d in range(len(self.h)): output += ('{p.uid}, {p.lat}, {p.lon},' ' {vp}, {vs}, {h}, {d}, {self.reference}').format( p=self, vp=self.vs[d], vs=self.vp[d], h=self.h[d], d=self.d[d]) return output
''' CrustDB is a container for :class:`VelocityProfile` and provides functions for spatial selection, querying, processing and visualising data from the Global Crustal Database. '''
self._read(database_file) else:
return len(self.profiles)
if not isinstance(value, VelocityProfile): raise TypeError('Element is not a VelocityProfile') self.profiles[key] = value
self.profiles.remove(key)
return self.profiles[key]
def __str__(self): rstr = "Container contains %d velocity profiles:\n\n" % self.nprofiles return rstr
def nprofiles(self):
raise TypeError('Element is not a VelocityProfile')
return copy.deepcopy(self)
return num.array( [p.lat for p in self.profiles])
return num.array( [p.lon for p in self.profiles])
if self.data_matrix is not None: return self.data_matrix
self.data_matrix = num.core.records.fromarrays( num.vstack([ num.concatenate([p.vp for p in self.profiles]), num.concatenate([p.vs for p in self.profiles]), num.concatenate([p.h for p in self.profiles]), num.concatenate([p.d for p in self.profiles]) ]), names='vp, vs, h, d') return self.data_matrix
'''Create a regular sampled velocity matrix
:param depth_range: Depth range, ``(dmin, dmax)``, defaults to ``(0, 6000.)`` :type depth_range: tuple :param ddepth: Stepping in [m], defaults to ``100.`` :type ddepth: float :param phase: Phase to calculate ``p`` or ``s``, defaults to ``p`` :type phase: str :returns: Sample depths, veloctiy matrix :rtype: tuple, (sample_depth, :class:`numpy.ndarray`) ''' dmin, dmax = depth_range uid = '.'.join(map(repr, (dmin, dmax, ddepth, phase))) sdepth = num.linspace(dmin, dmax, (dmax - dmin) / ddepth) ndepth = sdepth.size
if uid not in self._velocity_matrix_cache: vel_mat = num.empty((self.nprofiles, ndepth)) for ip, profile in enumerate(self.profiles): vel_mat[ip, :] = profile.interpolateProfile(sdepth, phase=phase) self._velocity_matrix_cache[uid] = num.ma.masked_invalid(vel_mat)
return sdepth, self._velocity_matrix_cache[uid]
phase='p'): '''Correlates ``ref_profile`` to each profile in the database
:param ref_profile: Reference profile :type ref_profile: :class:`VelocityProfile` :param depth_range: Depth range in [m], ``(dmin, dmax)``, defaults to ``(0, 35000.)`` :type depth_range: tuple, optional :param ddepth: Stepping in [m], defaults to ``100.`` :type ddepth: float :param phase: Phase to calculate ``p`` or ``s``, defaults to ``p`` :type phase: str :returns: RMS factor length of N_profiles :rtype: :class:`numpy.ndarray` ''' if not isinstance(ref_profile, VelocityProfile): raise ValueError('ref_profile is not a VelocityProfile')
sdepth, vel_matrix = self.velocityMatrix(depth_range, ddepth, phase=phase) ref_vel = ref_profile.interpolateProfile(sdepth, phase=phase)
rms = num.empty(self.nprofiles) for p in range(self.nprofiles): profile = vel_matrix[p, :] rms[p] = num.sqrt(profile**2 - ref_vel**2).sum() / ref_vel.size return rms
ddepth=100., dvbin=100., ddbin=2000., phase='p'): '''Create a 2D Histogram of all the velocity profiles
Check :func:`numpy.histogram2d` for more information.
:param depth_range: Depth range in [m], ``(dmin, dmax)``, defaults to ``(0., 60000.)`` :type depth_range: tuple :param vel_range: Depth range, ``(vmin, vmax)``, defaults to ``(5500., 8500.)`` :type vel_range: tuple :param ddepth: Stepping in [km], defaults to ``100.`` :type ddepth: float :param dvbin: Bin size in velocity dimension [m/s], defaults to 100. :type dvbin: float :param dvbin: Bin size in depth dimension [m], defaults to 2000. :type dvbin: float :param phase: Phase to calculate ``p`` or ``s``, defaults to ``p`` :type phase: str
:returns: :func:`numpy.histogram2d` :rtype: tuple ''' sdepth, v_mat = self.velocityMatrix(depth_range, ddepth, phase=phase) d_vec = num.tile(sdepth, self.nprofiles)
# Velocity and depth bins if vel_range is None: vel_range = ((v_mat.min() // 1e2) * 1e2, (v_mat.max() // 1e2) * 1e2) nvbins = int((vel_range[1] - vel_range[0]) / dvbin) ndbins = int((depth_range[1] - depth_range[0]) / ddbin)
return num.histogram2d(v_mat.flatten(), d_vec, range=(vel_range, depth_range), bins=[nvbins, ndbins], normed=False)
'''Mean velocity profile plus std variation
:param depth_range: Depth range in [m], ``(dmin, dmax)``, defaults to ``(0., 60000.)`` :type depth_range: tuple :param ddepth: Stepping in [m], defaults to ``100.`` :type ddepth: float :param phase: Phase to calculate ``p`` or ``s``, defaults to ``p`` :type phase: str :returns: depth vector, mean velocities, standard deviations :rtype: tuple of :class:`numpy.ndarray` ''' sdepth, v_mat = self.velocityMatrix(depth_range, ddepth, phase=phase) v_mean = num.ma.mean(v_mat, axis=0) v_std = num.ma.std(v_mat, axis=0)
return sdepth, v_mean.flatten(), v_std.flatten()
'''Mode velocity profile plus std variation
:param depth_range: Depth range in [m], ``(dmin, dmax)``, defaults to ``(0., 60000.)`` :type depth_range: tuple :param ddepth: Stepping in [m], defaults to ``100.`` :type ddepth: float :param phase: Phase to calculate ``p`` or ``s``, defaults to ``p`` :type phase: str :returns: depth vector, mode velocity, number of counts at each depth :rtype: tuple of :class:`numpy.ndarray` ''' import scipy.stats
sdepth, v_mat = self.velocityMatrix(depth_range, ddepth) v_mode, v_counts = scipy.stats.mstats.mode(v_mat, axis=0) return sdepth, v_mode.flatten(), v_counts.flatten()
'''Median velocity profile plus std variation
:param depth_range: Depth range in [m], ``(dmin, dmax)``, defaults to ``(0., 60000.)`` :type depth_range: tuple :param ddepth: Stepping in [m], defaults to ``100.`` :type ddepth: float :param phase: Phase to calculate ``p`` or ``s``, defaults to ``p`` :type phase: str :returns: depth vector, median velocities, standard deviations :rtype: tuple of :class:`numpy.ndarray` ''' sdepth, v_mat = self.velocityMatrix(depth_range, ddepth, phase=phase) v_mean = num.ma.median(v_mat, axis=0) v_std = num.ma.std(v_mat, axis=0)
return sdepth, v_mean.flatten(), v_std.flatten()
axes=None): '''Plot 1D histogram of seismic velocities in the container
:param vel_range: Velocity range, defaults to (5.5, 8.5) :type vel_range: tuple, optional :param bins: bins, defaults to 30 (see :func:`numpy.histogram`) :type bins: int, optional :param phase: Property to plot out of ``['vp', 'vs']``, defaults to 'vp' :type phase: str, optional :param figure: Figure to plot in, defaults to None :type figure: :class:`matplotlib.Figure`, optional '''
import matplotlib.pyplot as plt
fig, ax = _getCanvas(axes)
if phase not in ['vp', 'vs']: raise AttributeError('phase has to be either vp or vs')
data = self._dataMatrix()[phase]
ax.hist(data, weights=self.data_matrix['h'], range=vel_range, bins=bins, color='g', alpha=.5) ax.text(.95, .95, '%d Profiles' % self.nprofiles, transform=ax.transAxes, fontsize=10, va='top', ha='right', alpha=.7)
ax.set_title('Distribution of %s' % vel_labels[phase]) ax.set_xlabel('%s [km/s]' % vel_labels[phase]) ax.set_ylabel('Cumulative occurrence [N]') xscaled(1./km, ax) ax.yaxis.grid(alpha=.4)
if self.name is not None: ax.set_title('%s for %s' % (ax.get_title(), self.name))
if axes is None: plt.show()
vel_range=None, dvbin=100., percent=False, plot_mode=True, plot_median=True, plot_mean=False, show_cbar=True, aspect=.02, phase='p', axes=None): ''' Plot a two 2D Histogram of seismic velocities
:param depth_range: Depth range, ``(dmin, dmax)``, defaults to ``(0, 60)`` :type depth_range: tuple :param vel_range: Velocity range, ``(vmin, vmax)`` :type vel_range: tuple :param ddepth: Stepping in [m], defaults to ``.1`` :type ddepth: float :param dvbin: Bin size in velocity dimension [m/s], defaults to .1 :type dvbin: float :param dvbin: Bin size in depth dimension [m], defaults to 2000. :type dvbin: float :param phase: Phase to calculate ``p`` or ``s``, defaults to ``p`` :type phase: str :param plot_mode: Plot the Mode :type plot_mode: bool :param plot_mean: Plot the Mean :type plot_mean: bool :param plot_median: Plot the Median :type plot_median: bool :param axes: Axes to plot into, defaults to None :type axes: :class:`matplotlib.Axes` '''
import matplotlib.pyplot as plt
fig, ax = _getCanvas(axes)
ax = fig.gca()
if vel_range is not None: vmin, vmax = vel_range dmin, dmax = depth_range
vfield, vedg, dedg = self.histogram2d(vel_range=vel_range, depth_range=depth_range, ddepth=ddepth, dvbin=dvbin, ddbin=ddbin, phase=phase) vfield /= (ddbin / ddepth)
if percent: vfield /= vfield.sum(axis=1)[num.newaxis, :]
grid_ext = [vedg[0], vedg[-1], dedg[-1], dedg[0]] histogram = ax.imshow(vfield.swapaxes(0, 1), interpolation='nearest', extent=grid_ext, aspect=aspect)
if show_cbar: cticks = num.unique( num.arange(0, vfield.max(), vfield.max() // 10).round()) cbar = fig.colorbar(histogram, ticks=cticks, format='%1i', orientation='horizontal') if percent: cbar.set_label('Percent') else: cbar.set_label('Number of Profiles')
if plot_mode: sdepth, vel_mode, _ = self.modeVelocity(depth_range=depth_range, ddepth=ddepth) ax.plot(vel_mode[sdepth < dmax] + ddepth/2, sdepth[sdepth < dmax], alpha=.8, color='w', label='Mode')
if plot_mean: sdepth, vel_mean, _ = self.meanVelocity(depth_range=depth_range, ddepth=ddepth) ax.plot(vel_mean[sdepth < dmax] + ddepth/2, sdepth[sdepth < dmax], alpha=.8, color='w', linestyle='--', label='Mean')
if plot_median: sdepth, vel_median, _ = self.medianVelocity( depth_range=depth_range, ddepth=ddepth) ax.plot(vel_median[sdepth < dmax] + ddepth/2, sdepth[sdepth < dmax], alpha=.8, color='w', linestyle=':', label='Median')
ax.grid(True, which="both", color="w", linewidth=.8, alpha=.4)
ax.text(.025, .025, '%d Profiles' % self.nprofiles, color='w', alpha=.7, transform=ax.transAxes, fontsize=9, va='bottom', ha='left')
ax.set_title('Crustal Velocity Distribution') ax.set_xlabel('%s [km/s]' % vel_labels[phase]) ax.set_ylabel('Depth [km]') yscaled(1./km, ax) xoffset_scale(dvbin/2, 1./km, ax) ax.set_xlim(vel_range)
if self.name is not None: ax.set_title('%s for %s' % (ax.get_title(), self.name))
if plot_mode or plot_mean or plot_median: leg = ax.legend(loc=1, fancybox=True, prop={'size': 10.}) leg.get_frame().set_alpha(.6)
if axes is None: plt.show()
'''Plot a triangulated a depth surface exceeding velocity'''
import matplotlib.pyplot as plt
fig, ax = _getCanvas(axes) d = self.exceedVelocity(v_max, d_min, d_max) lons = self.lons()[d > 0] lats = self.lats()[d > 0] d = d[d > 0]
ax.tricontourf(lats, lons, d)
if axes is None: plt.show()
from pyrocko.plot import gmtpy lats = self.lats() lons = self.lons() s, n, w, e = (lats.min(), lats.max(), lons.min(), lons.max())
def darken(c, f=0.7): return (c[0]*f, c[1]*f, c[2]*f)
gmt = gmtpy.GMT() gmt.psbasemap(B='40/20', J='M0/12', R='%f/%f/%f/%f' % (w, e, s, n)) gmt.pscoast(R=True, J=True, D='i', S='216/242/254', A=10000, W='.2p') gmt.psxy(R=True, J=True, in_columns=[lons, lats], S='c2p', G='black') gmt.save(outfile)
''' Returns the last depth ``v_max`` has not been exceeded.
:param v_max: maximal velocity :type vmax: float :param dz: depth is sampled in dz steps :type dz: float :param d_max: maximum depth :type d_max: int :param d_min: minimum depth :type d_min: int :returns: Lat, Lon, Depth and uid where ``v_max`` is exceeded :rtype: list(num.array) ''' self.profile_exceed_velocity = num.empty(len(self.profiles)) self.profile_exceed_velocity[:] = num.nan
for ip, profile in enumerate(self.profiles): for il in range(len(profile.d)): if profile.d[il] <= d_min\ or profile.d[il] >= d_max: continue if profile.vp[il] < v_max: continue else: self.profile_exceed_velocity[ip] = profile.d[il] break return self.profile_exceed_velocity
'''Select profiles within a region by geographic corner coordinates
:param west: west corner :type west: float :param east: east corner :type east: float :param south: south corner :type south: float :param north: north corner :type north: float :returns: Selected profiles :rtype: :class:`CrustDB` '''
and profile.lat <= north and profile.lat >= south:
'''Select profiles within a polygon.
The algorithm is called the **Ray Casting Method**
:param poly: Latitude Longitude pairs of the polygon :type param: list of :class:`numpy.ndarray` :returns: Selected profiles :rtype: :class:`CrustDB` '''
(p2y - p1y) + p1x
'''Select profiles at a geographic location within a ``radius``.
:param lat: Latitude in [deg] :type lat: float :param lon: Longitude in [deg] :type lon: float :param radius: Radius in [deg] :type radius: float :returns: Selected profiles :rtype: :class:`CrustDB` ''' (lon - profile.lon)**2) <= radius:
'''Select profiles with more than ``nlayers``
:param nlayers: Minimum number of layers :type nlayers: int :returns: Selected profiles :rtype: :class:`CrustDB` ''' r_container = self._emptyCopy() logger.info('Selecting minimum %d layers...' % nlayers)
for profile in self.profiles: if profile.nlayers >= nlayers: r_container.append(profile)
return r_container
'''Select profiles with more than ``nlayers``.
:param nlayers: Maximum number of layers :type nlayers: int :returns: Selected profiles :rtype: :class:`CrustDB` ''' r_container = self._emptyCopy() logger.info('Selecting maximum %d layers...' % nlayers)
for profile in self.profiles: if profile.nlayers <= nlayers: r_container.append(profile)
return r_container
'''Select profiles describing layers deeper than ``depth``
:param depth: Minumum depth in [m] :type depth: float :returns: Selected profiles :rtype: :class:`CrustDB` '''
'''Select profiles describing layers shallower than ``depth``
:param depth: Maximum depth in [m] :type depth: float :returns: Selected profiles :rtype: :class:`CrustDB` '''
'''Select profiles describing P Wave velocity
:returns Selected profiles :rtype: :class:`CrustDB` '''
'''Select profiles describing P Wave velocity
:returns: Selected profiles :rtype: :class:`CrustDB` '''
'''Export a CSV file as specified in the header below
:param filename: Export filename :type filename: str ''' with open(filename, 'w') as file: file.write('# uid, Lat, Lon, vp, vs, H, Depth, Reference\n') for profile in self.profiles: file.write(profile._csv())
'''Exports a readable file YAML :filename:
:param filename: Export filename :type filename: str ''' with open(filename, 'w') as file: for profile in self.profiles: file.write(profile.__str__())
def readDatabase(cls, database_file): db = cls() CrustDB._read(db, database_file) return db
def _getRepositoryDatabase():
'''Reads in the the GSN databasefile and puts it in CrustDB
File format:
uid lat/lon vp vs hc depth 2 29.76N 2.30 .00 2.00 .00 s 25.70 .10 .00 NAC-CO 5 U 96.31W 3.94 .00 5.30 2.00 s 33.00 MCz 39.00 61C.3 EXC 5.38 .00 12.50 7.30 c 6.92 .00 13.20 19.80 c 8.18 .00 .00 33.00 m
3 34.35N 3.00 .00 3.00 .00 s 35.00 1.60 .00 NAC-BR 4 R 117.83W 6.30 .00 16.50 3.00 38.00 MCz 55.00 63R.1 ORO 7.00 .00 18.50 19.50 7.80 .00 .00 38.00 m
:param database_file: path to database file, type string
'''
'uid': num.nan, 'geographical_location': None, 'geological_province': None, 'geological_age': None, 'elevation': num.nan, 'heatflow': num.nan, 'measurement_method': None, 'publication_reference': None } # vp, vs, h, d, lat, lon, meta num.zeros(128, dtype=num.float32), num.zeros(128, dtype=num.float32), num.zeros(128, dtype=num.float32), 0., 0., meta)
vp=vp[:nlayer] * km, vs=vs[:nlayer] * km, h=h[:nlayer] * km, d=d[:nlayer] * km, lat=lat, lon=lon, **meta))
raise DatabaseError( 'Inconsistent database, check line %d!\n\tDebug: ' % line, lat, lon, vp, vs, h, d, meta)
else: lat = -lat # Additional meta data dbline[66:72].strip() lon = -lon # Additional meta data dbline[66:72].strip() except ValueError: logger.warning( 'Could not interpret line %d, skipping\n%s' % (line, dbline)) while not database.readlines(): pass vp, vs, h, d, lat, lon, meta = get_empty_record() # Append last profile (self.nprofiles, database_file)) |