def ppoints(self, pp_depth, pp_phase=None, model='iasp91'): """ Return coordinates of piercing point calculated by 1D ray tracing. Piercing point coordinates are stored in the stats attributes plat and plon. Needs stats attributes station_latitude, station_longitude, slowness and back_azimuth. :param pp_depth: depth of interface in km :param pp_phase: 'P' for piercing points of P wave, 'S' for piercing points of S wave or multiples, if None will be set to 'P' or 'S' depending on method :param model: path to model file (see `.SimpleModel`, default: iasp91) :return: NumPy array with coordinates of piercing points .. note:: ``phase='S'`` is usually wanted for P receiver functions and ``'P'`` for S receiver functions. """ if pp_phase is None: pp_phase = {'P': 'S', 'S': 'P'}[self.method] model = load_model(model) for tr in self: model.ppoint(tr.stats, pp_depth, phase=pp_phase) return np.array([(tr.stats.pp_latitude, tr.stats.pp_longitude) for tr in self])
def ppoint(self, pp_depth, pp_phase='S', model='iasp91'): """ Calculate coordinates of piercing point by 1D ray tracing. The iasp91 model is used. Piercing point coordinates are stored in the stats attributes plat and plon. Needs stats attributes station_latitude, station_longitude, slowness and back_azimuth. :param pp_depth: depth of interface in km :param pp_phase: 'P' for piercing points of P wave, 'S' for piercing points of S wave. Multiples are possible, too. :param model: Path to model file (see :class:`~rf.simple_model.SimpleModel`, default: iasp91) :return: NumPy array with coordinates of piercing points .. note:: `phase='S'` is usually wanted for P receiver functions and 'P' for S receiver functions. """ model = load_model(model) for tr in self: model.ppoint(tr.stats, pp_depth, phase=pp_phase) return np.array([(tr.stats.pp_latitude, tr.stats.pp_longitude) for tr in self])
def moveout(self, phase='Ps', ref=6.4, model='iasp91'): """ In-place moveout correction to a reference slowness. Needs stats attributes slowness and onset. :param phase: 'Ps', 'Sp', 'Ppss' or other multiples :param ref: reference ray parameter in s/deg :param model: path to model file or 'iasp91' """ model = load_model(model) model.moveout(self, phase=phase, ref=ref)
def moveout(self, phase='Ps', ref=6.4, model='iasp91'): """ In-place moveout correction to a reference slowness. Needs stats attributes slowness and onset. :param phase: 'Ps', 'Sp', 'Ppss' or other multiples :param ref: reference ray parameter in s/deg :param model: Path to model file (see :class:`~rf.simple_model.SimpleModel`, default: iasp91) """ model = load_model(model) model.moveout(self, phase=phase, ref=ref)
def moveout(self, phase=None, ref=6.4, model='iasp91'): """ In-place moveout correction to a reference slowness. Needs stats attributes slowness and onset. :param phase: 'Ps', 'Sp', 'Ppss' or other multiples, if None is set to 'Ps' for P receiver functions or 'Sp' for S receiver functions :param ref: reference ray parameter in s/deg :param model: Path to model file (see `.SimpleModel`, default: iasp91) """ if phase is None: phase = self.method + {'P': 's', 'S': 'p'}[self.method] model = load_model(model) model.moveout(self, phase=phase, ref=ref) for tr in self: tr.stats.moveout = phase tr.stats.slowness_before_moveout = tr.stats.slowness tr.stats.slowness = ref return self
def ppoints(self, pp_depth, pp_phase=None, model='iasp91'): """ Return coordinates of piercing point calculated by 1D ray tracing. Piercing point coordinates are stored in the stats attributes plat and plon. Needs stats attributes station_latitude, station_longitude, slowness and back_azimuth. :param pp_depth: depth of interface in km :param pp_phase: 'P' for piercing points of P wave, 'S' for piercing points of S wave or multiples, if None will be set to 'S' for P receiver functions or 'S' for S receiver functions :param model: path to model file (see `.SimpleModel`, default: iasp91) :return: NumPy array with coordinates of piercing points """ if pp_phase is None: pp_phase = {'P': 'S', 'S': 'P'}[self.method] model = load_model(model) for tr in self: model.ppoint(tr.stats, pp_depth, phase=pp_phase) return np.array([(tr.stats.pp_latitude, tr.stats.pp_longitude) for tr in self])
def rfstats(obj=None, event=None, station=None, phase='P', dist_range='default', tt_model='iasp91', pp_depth=None, pp_phase=None, model='iasp91'): """ Calculate ray specific values like slowness for given event and station. :param obj: `~obspy.core.trace.Stats` object with event and/or station attributes. Can be None if both event and station are given. It is possible to specify a stream object, too. Then, rfstats will be called for each Trace.stats object and traces outside dist_range will be discarded. :param event: ObsPy `~obspy.core.event.event.Event` object :param station: dictionary like object with items latitude, longitude and elevation :param phase: string with phase. Usually this will be 'P' or 'S' for P and S receiver functions, respectively. :type dist_range: tuple of length 2 :param dist_range: if epicentral of event is not in this intervall, None is returned by this function,\n if phase == 'P' defaults to (30, 90),\n if phase == 'S' defaults to (50, 85) :param tt_model: model for travel time calculation. (see the `obspy.taup` module, default: iasp91) :param pp_depth: Depth for piercing point calculation (in km, default: None -> No calculation) :param pp_phase: Phase for pp calculation (default: 'S' for P-receiver function and 'P' for S-receiver function) :param model: Path to model file for pp calculation (see `.SimpleModel`, default: iasp91) :return: `~obspy.core.trace.Stats` object with event and station attributes, distance, back_azimuth, inclination, onset and slowness or None if epicentral distance is not in the given interval. Stream instance if stream was specified instead of stats. """ if isinstance(obj, (Stream, RFStream)): stream = obj kwargs = { 'event': event, 'station': station, 'phase': phase, 'dist_range': dist_range, 'tt_model': tt_model, 'pp_depth': pp_depth, 'pp_phase': pp_phase, 'model': model } traces = [] for tr in stream: if rfstats(tr.stats, **kwargs) is not None: traces.append(tr) stream.traces = traces return stream if dist_range == 'default' and phase.upper() in 'PS': dist_range = (30, 90) if phase.upper() == 'P' else (50, 85) stats = AttribDict({}) if obj is None else obj if event is not None and station is not None: stats.update(obj2stats(event=event, station=station)) dist, baz, _ = gps2dist_azimuth(stats.station_latitude, stats.station_longitude, stats.event_latitude, stats.event_longitude) dist = dist / 1000 / DEG2KM if dist_range and not dist_range[0] <= dist <= dist_range[1]: return tt_model = TauPyModel(model=tt_model) arrivals = tt_model.get_travel_times(stats.event_depth, dist, (phase, )) if len(arrivals) == 0: raise Exception('TauPy does not return phase %s at distance %s' % (phase, dist)) if len(arrivals) > 1: msg = ('TauPy returns more than one arrival for phase %s at ' 'distance -> take first arrival') warnings.warn(msg % (phase, dist)) arrival = arrivals[0] onset = stats.event_time + arrival.time inc = arrival.incident_angle slowness = arrival.ray_param_sec_degree stats.update({ 'distance': dist, 'back_azimuth': baz, 'inclination': inc, 'onset': onset, 'slowness': slowness, 'phase': phase }) if pp_depth is not None: model = load_model(model) if pp_phase is None: pp_phase = 'S' if phase.upper().endswith('P') else 'P' model.ppoint(stats, pp_depth, phase=pp_phase) return stats
def plot_profile(profile, fname=None, scale=1, fillcolors=('r', 'b'), trim=None, top=None, moveout_model='iasp91'): """ Plot receiver function profile. :param profile: stream holding the profile :param fname: filename to save plot to. Can be None. In this case the figure is left open. :param scale: scale for individual traces :param fillcolors: fill colors for positive and negative wiggles :param trim: trim stream relative to onset before plotting using `~.rfstream.RFStream.slice2()` :param top: show second axes on top of profile with additional information. Valid values: 'hist' - Plot histogram showing the number of receiver functions stacked in the corresponding bin :param moveout_model: string with model filename. Will be loaded into a `~.simple_model.SimpleModel` object to calculate depths for tick labels. """ if len(profile) == 0: return if trim: profile = profile.slice2(*trim, reftime='onset') fig = plt.figure() ax = fig.add_axes([0.1, 0.1, 0.8, 0.7]) widths = [tr.stats.box_length for tr in profile] pad = max(1, scale) * min(widths) xlim = (min(tr.stats.box_pos for tr in profile) - pad, max(tr.stats.box_pos for tr in profile) + pad) max_ = max(np.max(np.abs(tr.data)) for tr in profile) for tr in profile: x = tr.stats.box_pos + scale * tr.data / max_ * min(widths) y = tr.times() - (tr.stats.onset - tr.stats.starttime) ax.plot(x, y, 'k') c1, c2 = fillcolors if c1: ax.fill_betweenx(y, x, tr.stats.box_pos, where=x >= tr.stats.box_pos, facecolor=c1) if c2: ax.fill_betweenx(y, x, tr.stats.box_pos, where=x < tr.stats.box_pos, facecolor=c2) ax.set_xlabel('distance (km)') ax.set_ylim(max(y), min(y)) ax.set_ylabel('time (s)') if moveout_model: from rf.simple_model import load_model model = load_model(moveout_model) phase = profile[0].stats.moveout slowness = profile[0].stats.slowness pd = model.calculate_delay_times(phase=phase, slowness=slowness) ax2 = ax.twinx() ax.get_shared_y_axes().join(ax, ax2) dkm = 50 if profile[0].stats.endtime - profile[0].stats.onset > 50: dkm = 200 d1 = np.arange(20) * dkm d2 = np.arange(100) * dkm / 5 t1 = np.interp(d1, model.z, pd) t2 = np.interp(d2, model.z, pd) myLocator = FixedLocator(t1) myMinorLocator = FixedLocator(t2) myFormatter = FixedFormatter([str(i) for i in d1]) ax2.yaxis.set_major_locator(myLocator) ax2.yaxis.set_minor_locator(myMinorLocator) ax2.yaxis.set_major_formatter(myFormatter) ax2.set_ylabel('depth (km)') ax2.set_ylim(ax.get_ylim()) if top is not None: ax3 = fig.add_axes([0.1, 0.85, 0.8, 0.1], sharex=ax) if top == 'hist': left = [tr.stats.box_pos - tr.stats.box_length / 2 for tr in profile] height = [tr.stats.num for tr in profile] ax3.bar(left, height, widths, color='cadetblue') plt.setp(ax3.get_xticklabels(), visible=False) ax3.spines['top'].set_color('none') ax3.spines['right'].set_color('none') ax3.spines['left'].set_color('none') ax3.xaxis.set_ticks_position('bottom') ax3.yaxis.set_ticks_position('left') ax3.set_yticks(ax3.get_ylim()) elif top is not None: raise NotImplementedError("'%s' not supported for top parameter" % top) ax.set_xlim(*xlim) if fname: fig.savefig(fname) plt.close(fig)
def setUp(self): self.model = load_model()
def setUp(self): self.stream = RFStream(read()) self.stream._write_test_header() self.model = load_model()
def rfstats(obj=None, event=None, station=None, phase='P', dist_range='default', tt_model='iasp91', pp_depth=None, pp_phase=None, model='iasp91'): """ Calculate ray specific values like slowness for given event and station. :param obj: `~obspy.core.trace.Stats` object with event and/or station attributes. Can be None if both event and station are given. It is possible to specify a stream object, too. Then, rfstats will be called for each Trace.stats object and traces outside dist_range will be discarded. :param event: ObsPy `~obspy.core.event.event.Event` object :param station: dictionary like object with items latitude, longitude and elevation :param phase: string with phase. Usually this will be 'P' or 'S' for P and S receiver functions, respectively. :type dist_range: tuple of length 2 :param dist_range: if epicentral of event is not in this intervall, None is returned by this function,\n if phase == 'P' defaults to (30, 90),\n if phase == 'S' defaults to (50, 85) :param tt_model: model for travel time calculation. (see the `obspy.taup` module, default: iasp91) :param pp_depth: Depth for piercing point calculation (in km, default: None -> No calculation) :param pp_phase: Phase for pp calculation (default: 'S' for P-receiver function and 'P' for S-receiver function) :param model: Path to model file for pp calculation (see `.SimpleModel`, default: iasp91) :return: `~obspy.core.trace.Stats` object with event and station attributes, distance, back_azimuth, inclination, onset and slowness or None if epicentral distance is not in the given interval. Stream instance if stream was specified instead of stats. """ if isinstance(obj, (Stream, RFStream)): stream = obj kwargs = {'event': event, 'station': station, 'phase': phase, 'dist_range': dist_range, 'tt_model': tt_model, 'pp_depth': pp_depth, 'pp_phase': pp_phase, 'model': model} traces = [] for tr in stream: if rfstats(tr.stats, **kwargs) is not None: traces.append(tr) stream.traces = traces return stream if dist_range == 'default' and phase.upper() in 'PS': dist_range = (30, 90) if phase.upper() == 'P' else (50, 85) stats = AttribDict({}) if obj is None else obj if event is not None and station is not None: stats.update(obj2stats(event=event, station=station)) dist, baz, _ = gps2dist_azimuth(stats.station_latitude, stats.station_longitude, stats.event_latitude, stats.event_longitude) dist = dist / 1000 / DEG2KM if dist_range and not dist_range[0] <= dist <= dist_range[1]: return tt_model = TauPyModel(model=tt_model) arrivals = tt_model.get_travel_times(stats.event_depth, dist, (phase,)) if len(arrivals) == 0: raise Exception('TauPy does not return phase %s at distance %s' % (phase, dist)) if len(arrivals) > 1: msg = ('TauPy returns more than one arrival for phase %s at ' 'distance -> take first arrival') warnings.warn(msg % (phase, dist)) arrival = arrivals[0] onset = stats.event_time + arrival.time inc = arrival.incident_angle slowness = arrival.ray_param_sec_degree stats.update({'distance': dist, 'back_azimuth': baz, 'inclination': inc, 'onset': onset, 'slowness': slowness, 'phase': phase}) if pp_depth is not None: model = load_model(model) if pp_phase is None: pp_phase = 'S' if phase.upper().endswith('P') else 'P' model.ppoint(stats, pp_depth, phase=pp_phase) return stats
def rfstats(stats=None, event=None, station=None, stream=None, phase='P', dist_range=None, tt_model='iasp91', pp_depth=None, pp_phase=None, model='iasp91'): """ Calculate ray specific values like slowness for given event and station. :param stats: stats object with event and/or station attributes. Can be None if both event and station are given. :param event: ObsPy :class:`~obspy.core.event.Event` object :param station: station object with attributes latitude, longitude and elevation :param stream: If a stream is given, stats has to be None. In this case rfstats will be called for every stats object in the stream. :param phase: string with phase. Usually this will be 'P' or 'S' for P and S receiver functions, respectively. :type dist_range: tuple of length 2 :param dist_range: if epicentral of event is not in this intervall, None is returned by this function,\n if phase == 'P' defaults to (30, 90),\n if phase == 'S' defaults to (50, 85) :param tt_model: model for travel time calculation. (see the :mod:`obspy.taup` module, default: iasp91) :param pp_depth: Depth for piercing point calculation (in km, default: None -> No calculation) :param pp_phase: Phase for pp calculation (default: 'S' for P-receiver function and 'P' for S-receiver function) :param model': Path to model file for pp calculation (see :class:`~rf.simple_model.SimpleModel`, default: iasp91) :return: ``stats`` object with event and station attributes, distance, back_azimuth, inclination, onset and slowness or None if epicentral distance is not in the given intervall """ if stream is not None: assert stats is None kwargs = {'event': event, 'station': station, 'stream':None, 'phase': phase, 'dist_range': dist_range, 'tt_model':tt_model, 'pp_depth': pp_depth, 'pp_phase': pp_phase, 'model': model} for tr in stream: rfstats(stats=tr.stats, **kwargs) return phase = phase.upper() if dist_range is None and phase in 'PS': dist_range = (30, 90) if phase == 'P' else (50, 85) if stats is None: stats = AttribDict({}) if event is not None and station is not None: stats.update(obj2stats(event=event, station=station)) dist, baz, _ = gps2DistAzimuth(stats.station_latitude, stats.station_longitude, stats.event_latitude, stats.event_longitude) dist = kilometer2degrees(dist / 1000) if dist_range and not dist_range[0] <= dist <= dist_range[1]: return tt_model = TauPyModel(model=tt_model) arrivals = tt_model.get_travel_times(stats.event_depth, dist, (phase,)) if len(arrivals) == 0: raise Exception('TauPy does not return phase %s at distance %s' % (phase, dist)) if len(arrivals) > 1: from warnings import warn msg = ('TauPy returns more than one arrival for phase %s at ' 'distance -> take first arrival' ) warn(msg % (phase, dist)) arrival = arrivals[0] onset = stats.event_time + arrival.time inc = arrival.incident_angle slowness = arrival.ray_param_sec_degree stats.update({'distance': dist, 'back_azimuth': baz, 'inclination': inc, 'onset': onset, 'slowness': slowness}) if pp_depth is not None: model = load_model(model) if pp_phase is None: pp_phase = 'S' if phase.upper().endswith('P') else 'P' model.ppoint(stats, pp_depth, phase=pp_phase) return stats