Source code for EQcorrscan_plotting

"""
Utility code for most of the plots used as part of the EQcorrscan package.

Copyright 2015 Calum Chamberlain

This file is part of EQcorrscan.

    EQcorrscan is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    EQcorrscan is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with EQcorrscan.  If not, see <http://www.gnu.org/licenses/>.

"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import numpy as np
import matplotlib.pylab as plt


[docs]def chunk_data(tr, samp_rate, state='mean'): r"""Function to downsample data for plotting by computing the maximum of \ data within chunks, useful for plotting waveforms or cccsums, large \ datasets that would otherwise exceed the complexity allowed, and overflow. :type tr: obspy.Trace :param tr: Trace to be chunked :type samp_rate: float :param samp_rate: Desired sampling rate in Hz :type state: str :param state: Either 'Min', 'Max', 'Mean' or 'Maxabs' to return one of \ these for the chunks. Maxabs will return the largest (positive or \ negative) for that chunk. :returns: :class: obspy.Trace """ trout = tr.copy() # Don't do it inplace on data x = np.arange(len(tr.data)) y = tr.data chunksize = int(round(tr.stats.sampling_rate / samp_rate)) # Wrap the array into a 2D array of chunks, truncating the last chunk if # chunksize isn't an even divisor of the total size. # (This part won't use _any_ additional memory) numchunks = y.size // chunksize ychunks = y[:chunksize*numchunks].reshape((-1, chunksize)) xchunks = x[:chunksize*numchunks].reshape((-1, chunksize)) # Calculate the max, min, and means of chunksize-element chunks... if state == 'Max': trout.data = ychunks.max(axis=1) elif state == 'Min': trout.data = ychunks.min(axis=1) elif state == 'Mean': trout.data = ychunks.mean(axis=1) elif state == 'Maxabs': max_env = ychunks.max(axis=1) min_env = ychunks.min(axis=1) indeces = np.argmax(np.vstack([np.abs(max_env), np.abs(min_env)]), axis=0) stack = np.vstack([max_env, min_env]).T trout.data = np.array([stack[i][indeces[i]] for i in xrange(len(stack))]) xcenters = xchunks.mean(axis=1) trout.stats.starttime = tr.stats.starttime + xcenters[0] /\ tr.stats.sampling_rate trout.stats.sampling_rate = samp_rate return trout
[docs]def triple_plot(cccsum, cccsum_hist, trace, threshold, save=False, savefile=''): r"""Main function to make a triple plot with a day-long seismogram, \ day-long correlation sum trace and histogram of the correlation sum to \ show normality. :type cccsum: numpy.ndarray :param cccsum: Array of the cross-channel cross-correlation sum :type cccsum_hist: numpy.ndarray :param cccsum_hist: cccsum for histogram plotting, can be the same as \ cccsum but included if cccsum is just an envelope. :type trace: obspy.Trace :param trace: A sample trace from the same time as cccsum :type threshold: float :param threshold: Detection threshold within cccsum :type save: bool, optional :param save: If True will svae and not plot to screen, vice-versa if False :type savefile: str, optional :param savefile: Path to save figure to, only required if save=True """ if len(cccsum) != len(trace.data): print('cccsum is: ' + str(len(cccsum))+' trace is: '+str(len(trace.data))) msg = ' '.join(['cccsum and trace must have the', 'same number of data points']) raise ValueError(msg) df = trace.stats.sampling_rate npts = trace.stats.npts t = np.arange(npts, dtype=np.float32) / (df * 3600) # Generate the subplot for the seismic data ax1 = plt.subplot2grid((2, 5), (0, 0), colspan=4) ax1.plot(t, trace.data, 'k') ax1.axis('tight') ax1.set_ylim([-15 * np.mean(np.abs(trace.data)), 15 * np.mean(np.abs(trace.data))]) # Generate the subplot for the correlation sum data ax2 = plt.subplot2grid((2, 5), (1, 0), colspan=4, sharex=ax1) # Plot the threshold values ax2.plot([min(t), max(t)], [threshold, threshold], color='r', lw=1, label="Threshold") ax2.plot([min(t), max(t)], [-threshold, -threshold], color='r', lw=1) ax2.plot(t, cccsum, 'k') ax2.axis('tight') ax2.set_ylim([-1.7 * threshold, 1.7 * threshold]) ax2.set_xlabel("Time after %s [hr]" % trace.stats.starttime.isoformat()) # ax2.legend() # Generate a small subplot for the histogram of the cccsum data ax3 = plt.subplot2grid((2, 5), (1, 4), sharey=ax2) ax3.hist(cccsum_hist, 200, normed=1, histtype='stepfilled', orientation='horizontal', color='black') ax3.set_ylim([-5, 5]) fig = plt.gcf() fig.suptitle(trace.id) fig.canvas.draw() if not save: plt.show() plt.close() else: plt.savefig(savefile) return
[docs]def peaks_plot(data, starttime, samp_rate, save=False, peaks=[(0, 0)], savefile=''): r"""Simple utility code to plot the correlation peaks to check that the \ peak finding routine is running correctly, used in debugging for the \ EQcorrscan module. :type data: numpy.array :param data: Numpy array of the data within which peaks have been found :type starttime: obspy.UTCDateTime :param starttime: Start time for the data :type samp_rate: float :param samp_rate: Sampling rate of data in Hz :type save: Boolean, optional :param save: Save figure or plot to screen (False) :type peaks: list of Tuple, optional :param peaks: List of peak locations and amplitudes (loc, amp) :type savefile: String, optional :param savefile: Path to save to, only used if save=True """ npts = len(data) t = np.arange(npts, dtype=np.float32) / (samp_rate * 3600) fig = plt.figure() ax1 = fig.add_subplot(111) ax1.plot(t, data, 'k') ax1.scatter(peaks[0][1] / (samp_rate * 3600), abs(peaks[0][0]), color='r', label='Peaks') for peak in peaks: ax1.scatter(peak[1] / (samp_rate * 3600), abs(peak[0]), color='r') ax1.legend() ax1.set_xlabel("Time after %s [hr]" % starttime.isoformat()) ax1.axis('tight') fig.suptitle('Peaks') if not save: plt.show() plt.close() else: plt.savefig(savefile) return
[docs]def cumulative_detections(dates, template_names, save=False, savefile=''): r"""Simple plotting function to take a list of datetime objects and plot \ a cumulative detections list. Can take dates as a list of lists and will \ plot each list seperately, e.g. if you have dates from more than one \ template it will overlay them in different colours. :type dates: list of lists of datetime.datetime :param dates: Must be a list of lists of datetime.datetime objects :type template_names: list of strings :param template_names: List of the template names in order of the dates :type save: bool :param save: Save figure or show to screen, optional :type savefile: str :param savefile: String to save to, optional """ # Set up a default series of parameters for lines colors = ['blue', 'green', 'red', 'cyan', 'magenta', 'yellow', 'black', 'firebrick', 'purple', 'darkgoldenrod', 'gray'] linestyles = ['-', '-.', '--', ':'] # Check that dates is a list of lists if type(dates[0]) != list: dates = [dates] i = 0 j = 0 # This is an ugly way of looping through colours and linestyles, it would # be better with itertools functions... plothandles = [] for k, template_dates in enumerate(dates): template_dates.sort() counts = np.arange(0, len(template_dates)) print(str(i)+' '+str(j)+' '+str(k)) filename = plt.plot(template_dates, counts, linestyles[j], color=colors[i], label=template_names[k], linewidth=3.0) plothandles.append(filename) if i < len(colors) - 1: i += 1 else: i = 0 if j < len(linestyles) - 1: j += 1 else: j = 0 plt.xlabel('Date') plt.ylabel('Cumulative detections') plt.title('Cumulative detections for all templates') plt.legend(loc=2, prop={'size': 8}, ncol=2) # handles=plothandles) if save: plt.savefig(savefile) plt.close() else: plt.show() return
[docs]def threeD_gridplot(nodes, save=False, savefile=''): r"""Function to plot in 3D a series of grid points. :type nodes: list of tuples :param nodes: List of tuples of the form (lat, long, depth) :type save: bool :param save: if True will save without plotting to screen, if False \ (default) will plot to screen but not save :type savefile: str :param savefile: required if save=True, path to save figure to. """ lats = [] longs = [] depths = [] for node in nodes: lats.append(float(node[0])) longs.append(float(node[1])) depths.append(float(node[2])) fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax.scatter(lats, longs, depths) ax.set_ylabel("Latitude (deg)") ax.set_xlabel("Longitude (deg)") ax.set_zlabel("Depth(km)") ax.get_xaxis().get_major_formatter().set_scientific(False) ax.get_yaxis().get_major_formatter().set_scientific(False) if not save: plt.show() plt.close() else: plt.savefig(savefile) return
[docs]def multi_event_singlechan(streams, catalog, clip=10.0, pre_pick=2.0, freqmin=False, freqmax=False, realign=False, cut=(-3.0, 5.0), PWS=False, title=False): r"""Function to plot data from a single channel at a single station for \ multiple events - data will be alligned by their pick-time given in the \ picks. :type streams: list of :class:obspy.stream :param streams: List of the streams to use, can contain more traces than \ you plan on plotting :type catalog: obspy.core.event.Catalog :param catalog: Catalog of events, one for each trace, with a single pick :type clip: float :param clip: Length in seconds to plot, defaults to 10.0 :type pre_pick: float :param pre_pick: Length in seconds to extract and plot before the pick, \ defaults to 2.0 :type freqmin: float :param freqmin: Low cut for bandpass in Hz :type freqmax: float :param freqmax: High cut for bandpass in Hz :type realign: bool :param realign: To compute best alignement based on correlation or not. :type cut: tuple :param cut: tuple of start and end times for cut in seconds from the pick :type PWS: bool :param PWS: compute Phase Weighted Stack, if False, will compute linear \ stack. :type title: str :param title: Plot title. :returns: Alligned and cut traces, and new picks """ from eqcorrscan.utils import stacking import copy from eqcorrscan.core.match_filter import normxcorr2 from obspy import Stream import warnings fig, axes = plt.subplots(len(catalog)+1, 1, sharex=True, figsize=(7, 12)) axes = axes.ravel() traces = [] al_traces = [] # Keep input safe clist = copy.deepcopy(catalog) st_list = copy.deepcopy(streams) for i, event in enumerate(clist): if st_list[i].select(station=event.picks[0].waveform_id.station_code, channel='*' + event.picks[0].waveform_id.channel_code[-1]): tr = st_list[i].select(station=event.picks[0].waveforms_id. station_code, channel='*' + event.picks[0].waveform_id. channel_code[-1])[0] else: print('No data for '+event.pick[0].waveform_id) continue tr.detrend('linear') if freqmin: tr.filter('bandpass', freqmin=freqmin, freqmax=freqmax) if realign: tr_cut = tr.copy() tr_cut.trim(event.picks[0].time + cut[0], event.picks[0].time + cut[1], nearest_sample=False) if len(tr_cut.data) <= (0.5 * (cut[1] - cut[0]) * tr_cut.stats.sampling_rate): msg = ''.join(['Not enough in the trace for ', tr.stats.station, '.', tr.stats.channel, '\n', 'Suggest removing pick from sfile at time ', str(event.picks[0].time)]) warnings.warn(msg) else: al_traces.append(tr_cut) else: tr.trim(event.picks[0].time - pre_pick, event.picks[0].time + clip - pre_pick, nearest_sample=False) if len(tr.data) == 0: msg = ''.join(['No data in the trace for ', tr.stats.station, '.', tr.stats.channel, '\n', 'Suggest removing pick from sfile at time ', str(event.picks[0].time)]) warnings.warn(msg) continue traces.append(tr) if realign: shift_len = int(0.25 * (cut[1] - cut[0]) * al_traces[0].stats.sampling_rate) shifts = stacking.align_traces(al_traces, shift_len) for i in xrange(len(shifts)): print('Shifting by '+str(shifts[i])+' seconds') event.picks[0].time -= shifts[i] traces[i].trim(event.picks[0].time - pre_pick, event.picks[0].time + clip-pre_pick, nearest_sample=False) # We now have a list of traces traces = [(trace, trace.stats.starttime.datetime) for trace in traces] traces.sort(key=lambda tup: tup[1]) traces = [trace[0] for trace in traces] # Plot the traces for i, tr in enumerate(traces): y = tr.data x = np.arange(len(y)) x = x / tr.stats.sampling_rate # convert to seconds axes[i+1].plot(x, y, 'k', linewidth=1.1) axes[i+1].yaxis.set_ticks([]) traces = [Stream(trace) for trace in traces] if PWS: linstack = stacking.PWS_stack(traces) else: linstack = stacking.linstack(traces) tr = linstack.select(station=event[0].picks[0].waveform_id.station_code, channel='*' + event[0].picks[0].waveform_id.channel_code[-1])[0] y = tr.data x = np.arange(len(y)) x = x / tr.stats.sampling_rate axes[0].plot(x, y, 'r', linewidth=2.0) axes[0].set_ylabel('Stack', rotation=0) axes[0].yaxis.set_ticks([]) for i, slave in enumerate(traces): cc = normxcorr2(tr.data, slave[0].data) axes[i+1].set_ylabel('cc='+str(round(np.max(cc), 2)), rotation=0) axes[i+1].text(0.9, 0.15, str(round(np.max(slave[0].data))), bbox=dict(facecolor='white', alpha=0.95), transform=axes[i+1].transAxes) axes[i+1].text(0.7, 0.85, slave[0].stats.starttime.datetime. strftime('%Y/%m/%d %H:%M:%S'), bbox=dict(facecolor='white', alpha=0.95), transform=axes[i+1].transAxes) axes[-1].set_xlabel('Time (s)') if title: axes[0].set_title(title) plt.subplots_adjust(hspace=0) plt.show() return traces, clist
[docs]def detection_multiplot(stream, template, times, streamcolour='k', templatecolour='r'): r"""Function to plot the stream of data that has been detected in, with\ the template on top of it timed according to a list of given times, just\ a pretty way to show a detection! :type stream: obspy.Stream :param stream: Stream of data to be plotted as the base (black) :type template: obspy.Stream :param template: Template to be plotted on top of the base stream (red) :type times: list of datetime.datetime :param times: list of times of detections in the order of the channels in template. :type streamcolour: str :param streamcolour: String of matplotlib colour types for the stream :type templatecolour: str :param templatecolour: Colour to plot the template in. """ import datetime as dt from obspy import UTCDateTime fig, axes = plt.subplots(len(template), 1, sharex=True) axes = axes.ravel() mintime = min([tr.stats.starttime for tr in template]) for i, template_tr in enumerate(template): image = stream.select(station=template_tr.stats.station, channel='*'+template_tr.stats.channel[-1]) if not image: msg = ' '.join(['No data for', template_tr.stats.station, template_tr.stats.channel]) print(msg) continue image = image.merge()[0] # Downsample if needed if image.stats.sampling_rate > 20: image.decimate(image.stats.sampling_rate // 20) if template_tr.stats.sampling_rate > 20: template_tr.decimate(template_tr.stats.sampling_rate // 20) # Get a list of datetime objects image_times = [image.stats.starttime.datetime + dt.timedelta((j * image.stats.delta) / 86400) for j in range(len(image.data))] axes[i].plot(image_times, image.data / max(image.data), streamcolour, linewidth=1.2) for k, time in enumerate(times): lagged_time = UTCDateTime(time) + (template_tr.stats.starttime - mintime) lagged_time = lagged_time.datetime template_times = [lagged_time + dt.timedelta((j * template_tr.stats.delta) / 86400) for j in range(len(template_tr.data))] axes[i].plot(template_times, template_tr.data / max(template_tr.data), templatecolour, linewidth=1.2) ylab = '.'.join([template_tr.stats.station, template_tr.stats.channel]) axes[i].set_ylabel(ylab, rotation=0, horizontalalignment='right') axes[i].yaxis.set_ticks([]) axes[len(axes) - 1].set_xlabel('Time') plt.subplots_adjust(hspace=0, left=0.175, right=0.95, bottom=0.07) plt.show() return
[docs]def interev_mag_sfiles(sfiles): r"""Function to plot interevent-time versus magnitude for series of events. **thin** Wrapper for interev_mag. :type sfiles: list :param sfiles: List of sfiles to read from """ from eqcorrscan.utils import Sfile_util times = [Sfile_util.readheader(sfile)[0].origins[0].time for sfile in sfiles] mags = [Sfile_util.readheader(sfile)[0].magnitudes[0].mag for sfile in sfiles] interev_mag(times, mags)
[docs]def interev_mag(times, mags): r"""Function to plot interevent times against magnitude for given times and magnitudes. :type times: list of datetime :param times: list of the detection times, must be sorted the same as mags :type mags: list of float :param mags: list of magnitudes """ l = [(times[i], mags[i]) for i in xrange(len(times))] l.sort(key=lambda tup: tup[0]) times = [x[0] for x in l] mags = [x[1] for x in l] # Make two subplots next to each other of time before and time after fig, axes = plt.subplots(1, 2, sharey=True) axes = axes.ravel() pre_times = [] post_times = [] for i in range(len(times)): if i > 0: pre_times.append((times[i] - times[i - 1]) / 60) if i < len(times) - 1: post_times.append((times[i + 1] - times[i]) / 60) axes[0].scatter(pre_times, mags[1:]) axes[0].set_title('Pre-event times') axes[0].set_ylabel('Magnitude') axes[0].set_xlabel('Time (Minutes)') plt.setp(axes[0].xaxis.get_majorticklabels(), rotation=30) axes[1].scatter(pre_times, mags[:-1]) axes[1].set_title('Post-event times') axes[1].set_xlabel('Time (Minutes)') plt.setp(axes[1].xaxis.get_majorticklabels(), rotation=30) plt.show()
[docs]def threeD_seismplot(stations, nodes): r"""Function to plot seismicity and stations in a 3D, movable, zoomable \ space using matplotlibs Axes3D package. :type stations: list of tuple :param stations: list of one tuple per station of (lat, long, elevation), \ with up positive. :type nodes: list of tuple :param nodes: list of one tuple per event of (lat, long, depth) with down \ positive. """ stalats, stalongs, staelevs = zip(*stations) evlats, evlongs, evdepths = zip(*nodes) evdepths = [-1 * depth for depth in evdepths] fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax.scatter(evlats, evlongs, evdepths, marker="x", c="k") ax.scatter(stalats, stalongs, staelevs, marker="v", c="r") ax.set_ylabel("Latitude (deg)") ax.set_xlabel("Longitude (deg)") ax.set_zlabel("Depth(km)") ax.get_xaxis().get_major_formatter().set_scientific(False) ax.get_yaxis().get_major_formatter().set_scientific(False) plt.show() return
[docs]def pretty_template_plot(template, size=(18.5, 10.5), save=False, title=False, background=False, picks=False): r"""Function to make a pretty plot of a single template, designed to work \ better than the default obspy plotting routine for short data lengths. :type template: :class: obspy.Stream :param template: Template stream to plot :type size: tuple :param size: tuple of plot size :type save: bool :param save: if False will plot to screen, if True will save :type title: bool :param title: String if set will be the plot title :type background: :class: obspy.stream :param background: Stream to plot the template within. :type picks: list of :class: eqcorrscan.utils.Sfile_util.PICK :param picks: List of eqcorrscan type picks. """ fig, axes = plt.subplots(len(template), 1, sharex=True, figsize=size) if len(template) > 1: axes = axes.ravel() else: return if not background: mintime = template.sort(['starttime'])[0].stats.starttime else: mintime = background.sort(['starttime'])[0].stats.starttime template.sort(['network', 'station', 'starttime']) lengths = [] for i, tr in enumerate(template): delay = tr.stats.starttime - mintime y = tr.data x = np.linspace(0, len(y) * tr.stats.delta, len(y)) x += delay if background: btr = background.select(station=tr.stats.station, channel=tr.stats.channel)[0] bdelay = btr.stats.starttime - mintime by = btr.data bx = np.linspace(0, len(by) * btr.stats.delta, len(by)) bx += bdelay axes[i].plot(bx, by, 'k', linewidth=1) axes[i].plot(x, y, 'r', linewidth=1.1) lengths.append(max(bx[-1], x[-1])) else: axes[i].plot(x, y, 'k', linewidth=1.1) lengths.append(x[-1]) # print(' '.join([tr.stats.station, str(len(x)), str(len(y))])) axes[i].set_ylabel('.'.join([tr.stats.station, tr.stats.channel]), rotation=0, horizontalalignment='right') axes[i].yaxis.set_ticks([]) # Plot the picks if they are given if picks: tr_picks = [pick for pick in picks if pick.station == tr.stats.station and pick.channel[0] + pick.channel[-1] == tr.stats.channel[0] + tr.stats.channel[-1]] for pick in tr_picks: if pick.phase == 'P': pcolor = 'red' elif pick.phase == 'S': pcolor = 'blue' else: pcolor = 'k' pdelay = pick.time - mintime # print(pdelay) axes[i].axvline(x=pdelay, color=pcolor, linewidth=2) # axes[i].plot([pdelay, pdelay], []) axes[i].set_xlim([0, max(lengths)]) axes[len(template)-1].set_xlabel('Time (s) from start of template') plt.subplots_adjust(hspace=0, left=0.175, right=0.95, bottom=0.07) if title: axes[0].set_title(title) else: plt.subplots_adjust(top=0.98) if not save: plt.show() plt.close() else: plt.savefig(save)
[docs]def NR_plot(stream, NR_stream, detections, false_detections=False, size=(18.5, 10), save=False, title=False): r"""Function to plot the Network response alongside the streams used -\ highlights detection times in the network response. :type stream: :class: obspy.Stream :param stream: Stream to plot :type NR_stream: :class: obspy.Stream :param NR_stream: Stream for the network response :type detections: list of datetime objects :param detections: List of the detections :type false_detections: list of datetime :param false_detections: Either False (default) or list of false detection\ times :type size: tuple :param size: Size of figure, default is (18.5,10) :type save: bool :param save: Save figure or plot to screen, if not False, must be string\ of save path. :type title: str :param title: String for the title of the plot, set to False """ import datetime as dt import matplotlib.dates as mdates fig, axes = plt.subplots(len(stream)+1, 1, sharex=True, figsize=size) if len(stream) > 1: axes = axes.ravel() else: return mintime = stream.sort(['starttime'])[0].stats.starttime stream.sort(['network', 'station', 'starttime']) for i, tr in enumerate(stream): delay = tr.stats.starttime - mintime delay *= tr.stats.sampling_rate y = tr.data x = [tr.stats.starttime + dt.timedelta(seconds=s / tr.stats.sampling_rate) for s in xrange(len(y))] x = mdates.date2num(x) axes[i].plot(x, y, 'k', linewidth=1.1) axes[i].set_ylabel(tr.stats.station+'.'+tr.stats.channel, rotation=0) axes[i].yaxis.set_ticks([]) axes[i].set_xlim(x[0], x[-1]) # Plot the network response tr = NR_stream[0] delay = tr.stats.starttime - mintime delay *= tr.stats.sampling_rate y = tr.data x = [tr.stats.starttime + dt.timedelta(seconds=s / tr.stats.sampling_rate) for s in range(len(y))] x = mdates.date2num(x) axes[i].plot(x, y, 'k', linewidth=1.1) axes[i].set_ylabel(tr.stats.station+'.'+tr.stats.channel, rotation=0) axes[i].yaxis.set_ticks([]) axes[-1].set_xlabel('Time') axes[-1].set_xlim(x[0], x[-1]) # Plot the detections! ymin, ymax = axes[-1].get_ylim() if false_detections: for detection in false_detections: xd = mdates.date2num(detection) axes[-1].plot((xd, xd), (ymin, ymax), 'k--', linewidth=0.5, alpha=0.5) for detection in detections: xd = mdates.date2num(detection) axes[-1].plot((xd, xd), (ymin, ymax), 'r--', linewidth=0.75) # Set formatters for x-labels mins = mdates.MinuteLocator() timedif = tr.stats.endtime.datetime - tr.stats.starttime.datetime if timedif.total_seconds() >= 10800 and timedif.total_seconds() <= 25200: hours = mdates.MinuteLocator(byminute=[0, 15, 30, 45]) elif timedif.total_seconds() <= 1200: hours = mdates.MinuteLocator(byminute=range(0, 60, 2)) elif timedif.total_seconds > 25200 and timedif.total_seconds() <= 172800: hours = mdates.HourLocator(byhour=range(0, 24, 3)) elif timedif.total_seconds() > 172800: hours = mdates.DayLocator() else: hours = mdates.MinuteLocator(byminute=range(0, 60, 5)) hrFMT = mdates.DateFormatter('%Y/%m/%d %H:%M:%S') axes[-1].xaxis.set_major_locator(hours) axes[-1].xaxis.set_major_formatter(hrFMT) axes[-1].xaxis.set_minor_locator(mins) plt.gcf().autofmt_xdate() axes[-1].fmt_xdata = mdates.DateFormatter('%Y/%m/%d %H:%M:%S') plt.subplots_adjust(hspace=0) if title: axes[0].set_title(title) if not save: plt.show() plt.close() else: plt.savefig(save) return
[docs]def SVD_plot(SVStreams, SValues, stachans, title=False): r"""Function to plot the singular vectors from the clustering routines, one\ plot for each stachan :type SVStreams: list of :class:Obspy.Stream :param SVStreams: See clustering.SVD_2_Stream - will assume these are\ ordered by power, e.g. first singular vector in the first stream :type SValues: list of float :param SValues: List of the singular values corresponding to the SVStreams :type stachans: list :param stachans: List of station.channel """ for stachan in stachans: print(stachan) plot_traces = [SVStream.select(station=stachan.split('.')[0], channel=stachan.split('.')[1])[0] for SVStream in SVStreams] fig, axes = plt.subplots(len(plot_traces), 1, sharex=True) axes = axes.ravel() for i, tr in enumerate(plot_traces): y = tr.data x = np.linspace(0, len(y) * tr.stats.delta, len(y)) axes[i].plot(x, y, 'k', linewidth=1.1) ylab = 'SV '+str(i+1)+'='+str(round(SValues[i] / len(SValues), 2)) axes[i].set_ylabel(ylab, rotation=0) axes[i].yaxis.set_ticks([]) print(i) axes[-1].set_xlabel('Time (s)') plt.subplots_adjust(hspace=0) if title: axes[0].set_title(title) else: axes[0].set_title(stachan) plt.show() return
[docs]def plot_synth_real(real_template, synthetic, channels=False): r"""Plot multiple channels of data for real data and synthetic. :type real_template: obspy.Stream :param real_template: Stream of the real template :type synthetic: obspy.Stream :param synthetic: Stream of synthetic template :type channels: list of str :param channels: List of tuples of (station, channel) to plot, default is\ False, which plots all. """ from obspy.signal.cross_correlation import xcorr from obspy import Stream colours = ['k', 'r'] labels = ['Real', 'Synthetic'] if channels: real = [] synth = [] for stachan in channels: real.append(real_template.select(station=stachan[0], channel=stachan[1])) synth.append(synthetic.select(station=stachan[0], channel=stachan[1])) real_template = Stream(real) synthetic = Stream(synth) # Extract the station and channels stachans = list(set([(tr.stats.station, tr.stats.channel) for tr in real_template])) fig, axes = plt.subplots(len(stachans), 1, sharex=True, figsize=(5, 10)) axes = axes.ravel() for i, stachan in enumerate(stachans): real_tr = real_template.select(station=stachan[0], channel=stachan[1])[0] synth_tr = synthetic.select(station=stachan[0], channel=stachan[1])[0] shift, corr = xcorr(real_tr, synth_tr, 2) print('Shifting by: '+str(shift)+' samples') if corr < 0: synth_tr.data = synth_tr.data * -1 corr = corr * -1 if shift < 0: synth_tr.data = synth_tr.data[abs(shift):] real_tr.data = real_tr.data[0:len(synth_tr.data)] elif shift > 0: real_tr.data = real_tr.data[abs(shift):] synth_tr.data = synth_tr.data[0:len(real_tr.data)] for j, tr in enumerate([real_tr, synth_tr]): y = tr.data y = y / float(max(abs(y))) x = np.linspace(0, len(y) * tr.stats.delta, len(y)) axes[i].plot(x, y, colours[j], linewidth=2.0, label=labels[j]) axes[i].get_yaxis().set_ticks([]) ylab = stachan[0]+'.'+stachan[1]+' cc='+str(round(corr, 2)) axes[i].set_ylabel(ylab, rotation=0) plt.subplots_adjust(hspace=0) # axes[0].legend() axes[-1].set_xlabel('Time (s)') plt.show()
[docs]def freq_mag(magnitudes, completeness, max_mag, binsize=0.2): r"""Function to make a frequency-magnitude histogram and cumulative \ density plot. This can compute a b-value, but not a completeness at \ the moment. B-value is computed by linear fitting to section of curve \ between completeness and max_mag. :type magnitudes: list :param magnitudes: list of float of magnitudes :type completeness: float :param completeness: Level to compute the b-value above :type max_mag: float :param max_mag: Maximum magnitude to try and fit a b-value to :type binsize: float :param binsize: Width of histogram bins, defaults to 0.2 """ # Ensure magnitudes are sorted magnitudes.sort() fig, ax1 = plt.subplots() # Set up the bins, the bin-size could be a variables bins = np.arange(min(magnitudes), max(magnitudes), binsize) n, bins, patches = ax1.hist(magnitudes, bins, facecolor='Black', alpha=0.5, label='Magnitudes') ax1.set_ylabel('Frequency') ax1.set_ylim([0, max(n) + 0.5 * max(n)]) plt.xlabel('Magnitude') # Now make the cumulative density function cdf = np.arange(len(magnitudes)) / float(len(magnitudes)) cdf = ((cdf * -1.0) + 1.0) * len(magnitudes) ax2 = ax1.twinx() ax2.scatter(magnitudes, np.log10(cdf), c='k', marker='+', s=20, lw=2, label='Magnitude cumulative density') # Now we want to calculate the b-value and plot the fit x = [] y = [] for i, magnitude in enumerate(magnitudes): if magnitude >= completeness <= max_mag: x.append(magnitude) y.append(cdf[i]) fit = np.polyfit(x, np.log10(y), 1) fit_fn = np.poly1d(fit) ax2.plot(magnitudes, fit_fn(magnitudes), '--k', label='GR trend, b-value = ' + str(abs(fit[0]))[0:4] + '\n $M_C$ = ' + str(completeness)) ax2.set_ylabel('$Log_{10}$ of cumulative density') plt.xlim([min(magnitudes) - 0.5, max(np.log10(cdf)) + 0.2]) plt.ylim([min(magnitudes) - 0.5, max(np.log10(cdf)) + 1.0]) plt.legend(loc=2) plt.show()
[docs]def spec_trace(traces, cmap=None, wlen=0.4, log=False, trc='k', tralpha=0.9, size=(10, 13), Fig=None, title=None, show=True): r"""Wrapper for _spec_trace, take a stream or list of traces and plots \ the trace with the spectra beneath it - this just does the overseeing to \ work out if it needs to add subplots or not. :type traces: either stream or list of traces :param traces: Traces to be plotted, can be a single obspy.Stream, or a \ list of obspy.Trace :type cmap: str :param cmap: [Matplotlib colormap](http://matplotlib.org/examples/color/ \ colormaps_reference.html) :type wlen: float :param wlen: Window length for fft in seconds :type log: bool :param log: Use a log frequency scale :type trc: str :param trc: Color for the trace. :type tralpha: float :param tralpha: Opacity level for the seismogram, from transparent (0.0) \ to opaque (1.0). :type size: tuple :param size: Plot size, tuple of floats, inches :type Fig: matplotlib Fig :param axes: Figure to plot onto, defaults to self generating. :type show: bool :param show: To show plot or not, if false, will return Fig. """ from obspy import Stream if isinstance(traces, Stream): traces.sort(['station', 'channel']) if not Fig: Fig = plt.figure(figsize=size) for i, tr in enumerate(traces): if i == 0: ax = Fig.add_subplot(len(traces), 1, i+1) else: ax = Fig.add_subplot(len(traces), 1, i+1, sharex=ax) ax1, ax2 = _spec_trace(tr, wlen=wlen, log=log, trc=trc, tralpha=tralpha, axes=ax) ax2.set_yticks([]) if i < len(traces) - 1: plt.setp(ax1.get_xticklabels(), visible=False) if type(traces) == list: ax2.text(0.005, 0.85, tr.stats.starttime.datetime. strftime('%Y/%m/%d %H:%M:%S'), bbox=dict(facecolor='white', alpha=0.8), transform=ax2.transAxes) else: ax2.text(0.005, 0.85, '.'.join([tr.stats.station, tr.stats.channel]), bbox=dict(facecolor='white', alpha=0.8), transform=ax2.transAxes) ax2.text(0.005, 0.02, str(np.max(tr.data).round(1)), bbox=dict(facecolor='white', alpha=0.95), transform=ax2.transAxes) ax1.set_xlabel('Time (s)') Fig.subplots_adjust(hspace=0) Fig.text(0.04, 0.5, 'Frequency (Hz)', va='center', rotation='vertical') if title: plt.suptitle(title) if show: plt.show() else: return Fig
[docs]def _spec_trace(trace, cmap=None, wlen=0.4, log=False, trc='k', tralpha=0.9, size=(10, 2.5), axes=None, title=None): r"""Function to plot a trace over that traces spectrogram. Uses obspys spectrogram routine. :type trace: obspy.Trace :param trace: trace to plot :type cmap: str :param cmap: [Matplotlib colormap](http://matplotlib.org/examples/color/ colormaps_reference.html) :type wlen: float :param wlen: Window length for fft in seconds :type log: bool :param log: Use a log frequency scale :type trc: str :param trc: Color for the trace. :type tralpha: float :param tralpha: Opacity level for the seismogram, from transparent (0.0) \ to opaque (1.0). :type size: tuple :param size: Plot size, tuple of floats, inches :type axes: matplotlib axes :param axes: Axes to plot onto, defaults to self generating. :type title: str :param title: Title for the plot. """ if not axes: Fig = plt.figure(figsize=size) ax1 = Fig.add_subplot(111) else: ax1 = axes trace.spectrogram(wlen=wlen, log=log, show=False, cmap=cmap, axes=ax1) Fig = plt.gcf() ax2 = ax1.twinx() y = trace.data x = np.linspace(0, len(y) / trace.stats.sampling_rate, len(y)) ax2.plot(x, y, color=trc, linewidth=2.0, alpha=tralpha) ax2.set_xlim(min(x), max(x)) ax2.set_ylim(min(y) * 2, max(y) * 2) if title: ax1.set_title(' '.join([trace.stats.station, trace.stats.channel, trace.stats.starttime.datetime. strftime('%Y/%m/%d %H:%M:%S')])) if not axes: Fig.set_size_inches(size) Fig.show() else: return ax1, ax2
if __name__ == "__main__": import doctest doctest.testmod()