"""
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()