Source code for eqcorrscan.core.lag_calc

"""
Functions to generate pick-corrections for events detected by correlation.


"""
import numpy as np
from eqcorrscan.core.match_filter import normxcorr2
import scipy
import warnings


def _xcorr_interp(ccc, dt):
    """
    Intrpolate around the maximum correlation value for sub-sample precision.

    :param ccc: Cross-correlation array
    :type ccc: numpy.ndarray
    :param dt: sample interval
    :type dt: float

    :return: Position of interpolated maximum in seconds from start of ccc
    :rtype: float
    """
    if ccc.shape[0] == 1:
        cc = ccc[0]
    else:
        cc = ccc
    # Code borrowed from obspy.signal.cross_correlation.xcorr_pick_correction
    cc_curvature = np.concatenate((np.zeros(1), np.diff(cc, 2), np.zeros(1)))
    cc_t = np.arange(0, len(cc) * dt, dt)
    peak_index = cc.argmax()
    first_sample = peak_index
    # XXX this could be improved..
    while first_sample > 0 and cc_curvature[first_sample - 1] <= 0:
        first_sample -= 1
    last_sample = peak_index
    while last_sample < len(cc) - 1 and cc_curvature[last_sample + 1] <= 0:
        last_sample += 1
    num_samples = last_sample - first_sample + 1
    if num_samples < 3:
        msg = "Less than 3 samples selected for fit to cross " + \
              "correlation: %s" % num_samples
        raise IndexError(msg)
    if num_samples < 5:
        msg = "Less than 5 samples selected for fit to cross " + \
              "correlation: %s" % num_samples
        warnings.warn(msg)
    coeffs, residual = scipy.polyfit(
        cc_t[first_sample:last_sample + 1],
        cc[first_sample:last_sample + 1], deg=2, full=True)[:2]
    # check results of fit
    if coeffs[0] >= 0:
        msg = "Fitted parabola opens upwards!"
        warnings.warn(msg)
    if residual > 0.1:
        msg = "Residual in quadratic fit to cross correlation maximum " + \
              "larger than 0.1: %s" % residual
        warnings.warn(msg)
    # X coordinate of vertex of parabola gives time shift to correct
    # differential pick time. Y coordinate gives maximum correlation
    # coefficient.
    shift = -coeffs[1] / 2.0 / coeffs[0]
    coeff = (4 * coeffs[0] * coeffs[2] - coeffs[1] ** 2) / (4 * coeffs[0])
    return shift

[docs]def _channel_loop(detection, template, min_cc, interpolate=False, i=0, debug=0): """ Inner loop for correlating and assigning picks. Utility function to take a stream of data for the detected event and write maximum correlation to absolute time as picks in an obspy.core.event.Event object. Only outputs picks for picks above min_cc. :type detection: obspy.core.stream.Stream :param detection: Stream of data for the slave event detected using \ template. :type template: obspy.core.stream.Stream :param template: Stream of data as the template for the detection. :type interpolate: bool :param interpolate: Interpolate the correlation function to achieve \ sub-sample precision. :type i: int :param i: Used to track which process has occurred when running in \ parallel. :returns: Event object containing net, sta, chan information :rtype: obspy.core.event.Event """ from obspy.core.event import Event, Pick, WaveformStreamID from obspy.core.event import ResourceIdentifier event = Event() s_stachans = {} used_s_sta = [] for tr in template: temp_net = tr.stats.network temp_sta = tr.stats.station temp_chan = tr.stats.channel image = detection.select(station=temp_sta, channel=temp_chan) if image: ccc = normxcorr2(tr.data, image[0].data) # Convert the maximum cross-correlation time to an actual time if debug > 3: print('********DEBUG: Maximum cross-corr=%s' % np.amax(ccc)) if np.amax(ccc) > min_cc: if interpolate: try: interp_max = _xcorr_interp(ccc=ccc, dt=image[0].stats.delta) except IndexError: print('Could not interpolate ccc, not smooth') interp_max = np.argmax(ccc) * image[0].stats.delta picktime = image[0].stats.starttime + interp_max else: picktime = image[0].stats.starttime + (np.argmax(ccc) * image[0].stats.delta) else: continue # Perhaps weight each pick by the cc val or cc val^2? # weight = np.amax(ccc) ** 2 if temp_chan[-1:] == 'Z': phase = 'P' # Only take the S-pick with the best correlation elif temp_chan[-1:] in ['E', 'N']: phase = 'S' if temp_sta not in s_stachans and np.amax(ccc) > min_cc: s_stachans[temp_sta] = ((temp_chan, np.amax(ccc), picktime)) elif temp_sta in s_stachans and np.amax(ccc) > min_cc: if np.amax(ccc) > s_stachans[temp_sta][1]: picktime = picktime else: picktime = s_stachans[temp_sta][2] temp_chan = s_stachans[temp_sta][0] elif np.amax(ccc) < min_cc and temp_sta not in used_s_sta: used_s_sta.append(temp_sta) else: continue else: phase = None _waveform_id = WaveformStreamID(network_code=temp_net, station_code=temp_sta, channel_code=temp_chan) event.picks.append(Pick(waveform_id=_waveform_id, time=picktime, method_id=ResourceIdentifier('EQcorrscan'), phase_hint=phase)) return (i, event)
[docs]def _day_loop(detection_streams, template, min_cc, interpolate=False, cores=False, debug=0): """ Function to loop through multiple detections for one template. Designed to run for the same day of data for I/O simplicity, but as you are passing stream objects it could run for all the detections ever, as long as you have the RAM! :type detection_streams: list :param detection_streams: List of all the detections for this template that you want to compute the optimum pick for. Individual things in list should be of obspy.core.stream.Stream type. :type template: obspy.core.stream.Stream :param template: The original template used to detect the detections passed :type min_cc: float :param min_cc: Minimum cross-correlation value to be allowed for a pick. :type interpolate: bool :param interpolate: Interpolate the correlation function to achieve \ sub-sample precision. :returns: Catalog object containing Event objects for each detection created by this template. :rtype: obspy.core.event.Catalog """ from multiprocessing import Pool, cpu_count # Used to run detections in parallel from obspy.core.event import Catalog if not cores: num_cores = cpu_count() else: num_cores = cores if num_cores > len(detection_streams): num_cores = len(detection_streams) pool = Pool(processes=num_cores) # Parallelize generation of events for each detection: # results is a list of (i, event class) results = [pool.apply_async(_channel_loop, args=(detection_streams[i], template, min_cc, interpolate, i, debug)) for i in range(len(detection_streams))] pool.close() events_list = [p.get() for p in results] pool.join() events_list.sort(key=lambda tup: tup[0]) # Sort based on i. temp_catalog = Catalog() temp_catalog.events = [event_tup[1] for event_tup in events_list] return temp_catalog
def _prepare_data(detect_data, detections, zipped_templates, delays, shift_len): """Prepare data for lag_calc - reduce memory here. :type detect_data: obspy.core.Stream :type detections: list :type zipped_templates: zip :type delays: list :type shift_len: float :returns: List of detect_streams to be worked on :rtype: list """ from obspy import Stream detect_streams = [] for detection in detections: # Stream to be saved for new detection detect_stream = [] for tr in detect_data: tr_copy = tr.copy() # Right now, copying each trace hundreds of times... template = [t for t in zipped_templates if str(t[0]) == str(detection.template_name)] if len(template) > 0: template = template[0] else: warnings.warn('No template with name: %s' % detection.template_name) for t in zipped_templates: print(t) continue template = template[1].select(station=tr.stats.station, channel=tr.stats.channel) if template: # Save template trace length in seconds template_len = len(template[0]) / \ template[0].stats.sampling_rate else: continue # If there is no template-data match then skip the rest # of the trace loop. # Grab the delays for the desired template: [(sta, chan, delay)] delay = [delay for delay in delays if delay[0] == detection. template_name][0][1] # Now grab the delay for the desired trace for this template delay = [d for d in delay if d[0] == tr.stats.station and d[1] == tr.stats.channel][0][2] detect_stream.append(tr_copy.trim(starttime=detection.detect_time - shift_len + delay, endtime=detection.detect_time + delay + shift_len + template_len)) del tr_copy for tr in detect_stream: if len(tr.data) == 0: detect_stream.remove(tr) if not len(detect_stream) == 0: # Create tuple of (template name, data stream) detect_streams.append((detection.template_name, Stream(detect_stream))) return detect_streams
[docs]def lag_calc(detections, detect_data, template_names, templates, shift_len=0.2, min_cc=0.4, cores=1, interpolate=False, plot=False): """ Main lag-calculation function for detections of specific events. Overseer function to take a list of detection objects, cut the data for them to lengths of the same length of the template + shift_len on either side. This will then write out SEISAN s-file or QuakeML for the detections with pick times based on the lag-times found at the maximum correlation, providing that correlation is above the min_cc. :type detections: list :param detections: List of DETECTION objects :type detect_data: obspy.core.stream.Stream :param detect_data: All the data needed to cut from - can be a gappy Stream :type template_names: list :param template_names: List of the template names, used to help identify \ families of events. Must be in the same order as templates. :type templates: list :param templates: List of the templates, templates are of type: \ obspy.core.stream.Stream. :type shift_len: float :param shift_len: Shift length allowed for the pick in seconds, will be plus/minus this amount - default=0.2 :type min_cc: float :param min_cc: Minimum cross-correlation value to be considered a pick, default=0.4 :type cores: int :param cores: Number of cores to use in parallel processing, defaults to \ one. :type interpolate: bool :param interpolate: Interpolate the correlation function to achieve \ sub-sample precision. :type plot: bool :param plot: To generate a plot for every detection or not, defaults to \ False. :returns: Catalog of events with picks. No origin information is \ included, these events can then be written out via \ obspy.core.event functions, or to seisan Sfiles using Sfile_util \ and located. :rtype: obspy.core.event.Catalog .. rubric: Example >>> from eqcorrscan.core import lag_calc .. note:: Picks output in catalog are generated relative to the template \ start-time. For example, if you generated your template with a \ pre_pick time of 0.2 seconds, you should expect picks generated by \ lag_calc to occur 0.2 seconds before the true phase-pick. This \ is because we do not currently store template meta-data alongside the \ templates. .. warning:: Because of the above note, origin times will be consistently \ shifted by the static pre_pick applied to the templates. """ from obspy.core.event import Catalog from eqcorrscan.utils.plotting import plot_repicked # First work out the delays for each template delays = [] # List of tuples of (tempname, (sta, chan, delay)) zipped_templates = list(zip(template_names, templates)) for template in zipped_templates: temp_delays = [] for tr in template[1]: temp_delays.append((tr.stats.station, tr.stats.channel, tr.stats.starttime - template[1]. sort(['starttime'])[0].stats.starttime)) delays.append((template[0], temp_delays)) # Segregate detections by template, then feed to day_loop initial_cat = Catalog() for template in zipped_templates: template_detections = [detection for detection in detections if detection.template_name == template[0]] detect_streams = _prepare_data(detect_data=detect_data, detections=template_detections, zipped_templates=zipped_templates, delays=delays, shift_len=shift_len) detect_streams = [detect_stream[1] for detect_stream in detect_streams] if len(template_detections) > 0: template_cat = _day_loop(detection_streams=detect_streams, template=template[1], min_cc=min_cc, interpolate=interpolate, cores=cores) initial_cat += template_cat if plot: for i, event in enumerate(template_cat): if len(event.picks) == 0: print('Made no picks for event') print(event) continue plot_repicked(template=template[1], picks=event.picks, det_stream=detect_streams[i]) return initial_cat
if __name__ == '__main__': import doctest doctest.testmod()