"""
Utility code for most of the plots used as part of the EQcorrscan package.
:copyright:
Calum Chamberlain, Chet Hopp.
:license:
GNU Lesser General Public License, Version 3
(https://www.gnu.org/copyleft/lesser.html)
"""
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
def _check_save_args(save, savefile):
if save and not savefile:
raise IOError('save is set to True, but no savefile is given')
else:
return
[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 = int(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=None):
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 save 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
:returns: matplotlib.figure
"""
_check_save_args(save, savefile)
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 fig
[docs]def peaks_plot(data, starttime, samp_rate, save=False, peaks=[(0, 0)],
savefile=None):
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
:returns: :class: matplotlib.figure
"""
_check_save_args(save, savefile)
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 fig
[docs]def cumulative_detections(dates, template_names, show=False,
save=False, savefile=None):
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 show: bool
:param show: Wether or not to show the plot, defaults to False which will \
return the axes for editing
:type save: bool
:param save: Save figure or show to screen, optional
:type savefile: str
:param savefile: String to save to, required is save=True
:returns: :class: matplotlib.figure
"""
_check_save_args(save, savefile)
# 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...
fig, ax1 = plt.subplots()
for k, template_dates in enumerate(dates):
template_dates.sort()
counts = np.arange(0, len(template_dates))
print(str(i)+' '+str(j)+' '+str(k))
ax1.plot(template_dates, counts, linestyles[j],
color=colors[i], label=template_names[k],
linewidth=3.0)
if i < len(colors) - 1:
i += 1
else:
i = 0
if j < len(linestyles) - 1:
j += 1
else:
j = 0
ax1.set_xlabel('Date')
ax1.set_ylabel('Cumulative detections')
plt.title('Cumulative detections for all templates')
ax1.legend(loc=2, prop={'size': 8}, ncol=2)
if save:
fig.savefig(savefile)
plt.close()
else:
if show:
plt.show()
return fig
[docs]def threeD_gridplot(nodes, save=False, savefile=None):
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.
:returns: :class: matplotlib.figure
"""
_check_save_args(save, savefile)
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 fig
[docs]def multi_event_singlechan(streams, catalog, station, channel,
clip=10.0, pre_pick=2.0,
freqmin=False, freqmax=False, realign=False,
cut=(-3.0, 5.0), PWS=False, title=False,
save=False, savefile=None):
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 - must be in the same order as events in catalog.
:type catalog: obspy.core.event.Catalog
:param catalog: Catalog of events, one for each stream.
:type station: str
:param station: Station to plot.
:type channel: str
:param channel: Channel to plot.
: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.
:type save: bool
:param save: False will plot to screen, true will save plot and not show \
to screen.
:type savefile: str
:param savefile: Filename to save to, required for save=True
:returns: Alligned and cut traces, new picks, matplotlib.figure
"""
_check_save_args(save, savefile)
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))
if len(catalog) > 1:
axes = axes.ravel()
traces = []
al_traces = []
# Keep input safe
clist = copy.deepcopy(catalog)
if isinstance(streams, Stream):
streams = [streams]
st_list = copy.deepcopy(streams)
for i, event in enumerate(clist):
# Extract the appropriate pick
_pick = [pick for pick in event.picks if
pick.waveform_id.station_code == station and
pick.waveform_id.channel_code == channel][0]
if st_list[i].select(station=station, channel=channel):
tr = st_list[i].select(station=station, channel=channel)[0]
else:
print('No data for ' + _pick.waveform_id)
continue
tr.detrend('linear')
if freqmin:
tr.filter('bandpass', freqmin=freqmin, freqmax=freqmax)
if realign:
tr_cut = tr.copy()
tr_cut.trim(_pick.time + cut[0],
_pick.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(_pick.time)])
warnings.warn(msg)
else:
al_traces.append(tr_cut)
else:
tr.trim(_pick.time - pre_pick,
_pick.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(_pick.time - pre_pick,
_pick.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=station, channel=channel)[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)
if not save:
plt.show()
else:
plt.savefig(savefile)
return traces, clist, fig
[docs]def detection_multiplot(stream, template, times, streamcolour='k',
templatecolour='r', save=False, savefile=None):
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.
:type save: bool
:param save: False will plot to screen, true will save plot and not show \
to screen.
:type savefile: str
:param savefile: Filename to save to, required for save=True
:returns: :class: matplotlib.figure
"""
_check_save_args(save, savefile)
import datetime as dt
from obspy import UTCDateTime
fig, axes = plt.subplots(len(template), 1, sharex=True)
if len(template) > 1:
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(int(image.stats.sampling_rate // 20))
if template_tr.stats.sampling_rate > 20:
template_tr.decimate(int(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)
if not save:
plt.show()
else:
plt.savefig(savefile)
return fig
[docs]def interev_mag_sfiles(sfiles, save=False, savefile=None):
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
:type save: bool
:param save: False will plot to screen, true will save plot and not show \
to screen.
:type savefile: str
:param savefile: Filename to save to, required for save=True
:returns: :class: matplotlib.figure
"""
_check_save_args(save, savefile)
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]
fig = interev_mag(times, mags, save, savefile)
return fig
[docs]def interev_mag(times, mags, save=False, savefile=None):
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
:type save: bool
:param save: False will plot to screen, true will save plot and not show \
to screen.
:type savefile: str
:param savefile: Filename to save to, required for save=True
:returns: :class: matplotlib.figure
"""
_check_save_args(save, savefile)
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)
if not save:
plt.show()
else:
plt.savefig(savefile)
return fig
[docs]def obspy_3d_plot(inventory, catalog, save=False, savefile=None):
r"""Wrapper on threeD_seismplot() to plot obspy.Inventory and
obspy.Catalog classes in three dimensions.
:type inventory: obspy.Inventory
:param inventory: Obspy inventory class containing station metadata
:type catalog: obspy.Catalog
:param catalog: Obspy catalog class containing event metadata
:type save: bool
:param save: False will plot to screen, true will save plot and not show \
to screen.
:type savefile: str
:param savefile: Filename to save to, required for save=True
:returns: :class: matplotlib.figure
"""
_check_save_args(save, savefile)
from eqcorrscan.utils.plotting import threeD_seismplot
nodes = [(ev.preferred_origin().latitude,
ev.preferred_origin().longitude,
ev.preferred_origin().depth / 1000) for ev in catalog]
# Will plot borehole instruments at elevation - depth if provided
all_stas = []
for net in inventory:
stations = [(sta.latitude, sta.longitude,
sta.elevation / 1000 - sta.channels[0].depth / 1000)
for sta in net]
all_stas += stations
fig = threeD_seismplot(all_stas, nodes, save, savefile)
return fig
[docs]def threeD_seismplot(stations, nodes, save=False, savefile=None):
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.
:type save: bool
:param save: False will plot to screen, true will save plot and not show \
to screen.
:type savefile: str
:param savefile: Filename to save to, required for save=True
:returns: :class: matplotlib.figure
"""
_check_save_args(save, savefile)
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)
if not save:
plt.show()
else:
plt.savefig(savefile)
return fig
[docs]def pretty_template_plot(template, size=(10.5, 7.5), save=False,
savefile=None, 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 savefile: str
:param savefile: String to save plot as, required if save=True.
: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 obspy.core.event.pick
:param picks: List of obspy type picks.
:returns: :class: matplotlib.figure
"""
_check_save_args(save, savefile)
fig, axes = plt.subplots(len(template), 1, sharex=True, figsize=size)
if len(template) > 1:
axes = axes.ravel()
if not background:
mintime = template.sort(['starttime'])[0].stats.starttime
else:
mintime = background.sort(['starttime'])[0].stats.starttime
template.sort(['network', 'station', 'starttime'])
lengths = []
lines = []
labels = []
for i, tr in enumerate(template):
# Cope with a singe channel template case.
if len(template) > 1:
axis = axes[i]
else:
axis = axes
delay = tr.stats.starttime - mintime
y = tr.data
x = np.linspace(0, (len(y)-1) * 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)-1) * btr.stats.delta, len(by))
bx += bdelay
axis.plot(bx, by, 'k', linewidth=1)
template_line, = axis.plot(x, y, 'r', linewidth=1.1,
label='Template')
if i == 0:
lines.append(template_line)
labels.append('Template')
lengths.append(max(bx[-1], x[-1]))
else:
template_line, = axis.plot(x, y, 'k', linewidth=1.1,
label='Template')
if i == 0:
lines.append(template_line)
labels.append('Template')
lengths.append(x[-1])
# print(' '.join([tr.stats.station, str(len(x)), str(len(y))]))
axis.set_ylabel('.'.join([tr.stats.station, tr.stats.channel]),
rotation=0, horizontalalignment='right')
axis.yaxis.set_ticks([])
# Plot the picks if they are given
if picks:
tr_picks = [pick for pick in picks if
pick.waveform_id.station_code == tr.stats.station and
pick.waveform_id.channel_code[0] +
pick.waveform_id.channel_code[-1] ==
tr.stats.channel[0] + tr.stats.channel[-1]]
for pick in tr_picks:
if 'P' in pick.phase_hint.upper():
pcolor = 'red'
label = 'P-pick'
elif 'S' in pick.phase_hint.upper():
pcolor = 'blue'
label = 'S-pick'
else:
pcolor = 'k'
label = 'Unknown pick'
pdelay = pick.time - mintime
# print(pdelay)
line = axis.axvline(x=pdelay, color=pcolor, linewidth=2,
linestyle='--', label=label)
if label not in labels:
lines.append(line)
labels.append(label)
# axes[i].plot([pdelay, pdelay], [])
axis.set_xlim([0, max(lengths)])
if len(template) > 1:
axis = axes[len(template) - 1]
else:
axis = axes
axis.set_xlabel('Time (s) from start of template')
plt.figlegend(lines, labels, 'upper right')
if title:
if len(template) > 1:
axes[0].set_title(title)
else:
axes.set_title(title)
else:
plt.subplots_adjust(top=0.98)
plt.tight_layout()
plt.subplots_adjust(hspace=0)
if not save:
plt.show()
plt.close()
else:
plt.savefig(savefile)
return fig
[docs]def NR_plot(stream, NR_stream, detections, false_detections=False,
size=(18.5, 10), save=False, savefile=None, 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
:returns: :class: matplotlib.figure
"""
_check_save_args(save, savefile)
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(savefile)
return fig
[docs]def SVD_plot(SVStreams, SValues, stachans, title=False, save=False,
savefile=None):
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
:type save: bool
:param save: False will plot to screen, true will save plot and not show \
to screen.
:type savefile: str
:param savefile: Filename to save to, required for save=True, will label \
additionally according to station and channel.
:returns: :class: matplotlib.figure
"""
_check_save_args(save, savefile)
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)
if len(plot_traces) > 1:
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)
if not save:
plt.show()
else:
plt.savefig(savefile.split('.') + '_stachan.' +
savefile.split('.')[-1])
return fig
[docs]def plot_synth_real(real_template, synthetic, channels=False, save=False,
savefile=None):
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.
:type save: bool
:param save: False will plot to screen, true will save plot and not show \
to screen.
:type savefile: str
:param savefile: Filename to save to, required for save=True
:returns: :class: matplotlib.figure
"""
_check_save_args(save, savefile)
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))
if len(stachans) > 1:
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)')
if not save:
plt.show()
else:
plt.savefig(savefile)
return fig
[docs]def freq_mag(magnitudes, completeness, max_mag, binsize=0.2, save=False,
savefile=None):
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
:type save: bool
:param save: False will plot to screen, true will save plot and not show \
to screen.
:type savefile: str
:param savefile: Filename to save to, required for save=True
:returns: :class: matplotlib.figure
"""
_check_save_args(save, savefile)
# 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)
if not save:
plt.show()
else:
plt.savefig(savefile)
return fig
[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.
:returns: :class: matplotlib.figure
"""
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()