def coalescence_video(self, file_str): """ Generate a video over the marginal window showing the coalescence map and expected arrival times overlain on the station traces. Parameters ---------- file_str : str String {run_name}_{event_name} """ # Find index of start and end of marginal window idx0 = np.where(self.times == self.event_mw_data["DT"].iloc[0])[0][0] idx1 = np.where(self.times == self.event_mw_data["DT"].iloc[-1])[0][0] Writer = animation.writers["ffmpeg"] writer = Writer(fps=4, metadata=dict(artist="Ulvetanna"), bitrate=1800) fig = self._coalescence_frame(idx0) ani = animation.FuncAnimation(fig, self._video_update, frames=np.linspace(idx0 + 1, idx1, 200), blit=False, repeat=False) subdir = "videos" util.make_directories(self.run_path, subdir=subdir) out_str = self.run_path / subdir / file_str ani.save("{}_CoalescenceVideo.mp4".format(out_str), writer=writer)
def __init__(self, path, name=None, log=False): """ Class initialisation method. Parameters ---------- path : str Path to input / output directory name: str, optional Name of run """ path = pathlib.Path(path) if name is None: name = datetime.now().strftime("RUN_%Y%m%d_%H%M%S") self.path = path self.name = name self.run = path / name self.log_ = log # Make run output directory util.make_directories(self.run)
def write_event(self, event, event_name): """ Create a new .event file Parameters ---------- event : pandas DataFrame Final event location information. Columns = ["DT", "COA", "X", "Y", "Z", "LocalGaussian_X", "LocalGaussian_Y", "LocalGaussian_Z", "LocalGaussian_ErrX", "LocalGaussian_ErrY", "LocalGaussian_ErrZ", "GlobalCovariance_X", "GlobalCovariance_Y", "GlobalCovariance_Z", "GlobalCovariance_ErrX", "GlobalCovariance_ErrY", "GlobalCovariance_ErrZ"] All X / Y as lon / lat; Z and X / Y / Z uncertainties in metres event_name : str event_uid for file naming """ subdir = "events" util.make_directories(self.run, subdir=subdir) fname = self.run / subdir / "{}".format(event_name) fname = str(fname.with_suffix(".event")) event.to_csv(fname, index=False)
def write_coal4D(self, map_4d, event_name, start_time, end_time): """ Writes 4-D coalescence grid to a binary numpy file. Parameters ---------- map_4d : array-like 4-D coalescence grid output by _compute() event_name : str event_id for file naming start_time : UTCDateTime start time of 4-D coalescence map end_time : UTCDateTime end time of 4-D coalescence map """ start_time = UTCDateTime(start_time) end_time = UTCDateTime(end_time) subdir = "4d_coal_grids" util.make_directories(self.run, subdir=subdir) filestr = "{}_{}_{}_{}".format(self.name, event_name, start_time, end_time) fname = self.run / subdir / filestr fname = fname.with_suffix(".coal4D") np.save(str(fname), map_4d)
def write_picks(self, stations, event_name): """ Write phase picks to a new .picks file Parameters ---------- stations : pandas DataFrame object event_name : str event_id for file naming """ subdir = "picks" util.make_directories(self.run, subdir=subdir) fname = self.run / subdir / "{}".format(event_name) fname = str(fname.with_suffix(".picks")) stations.to_csv(fname, index=False)
def log(self, message, log): """ Write a log file to track the progress of a scanning run through time. Parameters ---------- message : str Information to be saved to the log file """ print(message) if log: subdir = "logs" util.make_directories(self.run, subdir=subdir) fname = (self.run / subdir / self.name).with_suffix(".log") with fname.open(mode="a") as f: f.write(message + "\n")
def event_summary(self, file_str=None): """ Create summary plot for an event. Shows the coalescence map sliced through the maximum coalescence showing calculated locations and uncdrtainties, the coalescence value over the course of the marginal time window and a gather of the station traces. Parameters ---------- file_str : str, optional String {run_name}_{event_name} (figure displayed by default) """ # Event is only in first line of earthquake, reduces chars later on if self.event is not None: eq = self.event.iloc[0] else: msg = "\t\tError: no event specified!" print(msg) return dt_max = (self.event_mw_data["DT"].iloc[np.argmax( np.array(self.event_mw_data["COA"]))]).to_pydatetime() # Determining the marginal window value from the coalescence function coa_map = np.ma.masked_invalid(self.coa_map) loc = np.where(coa_map == np.nanmax(coa_map)) point = np.array([[loc[0][0], loc[1][0], loc[2][0]]]) crd = self.lut.coord2loc(point, inverse=True) # Defining the plots to be represented fig = plt.figure(figsize=(25, 15)) fig.patch.set_facecolor("white") xy_slice = plt.subplot2grid((3, 5), (0, 0), colspan=2, rowspan=2) xz_slice = plt.subplot2grid((3, 5), (2, 0), colspan=2) yz_slice = plt.subplot2grid((3, 5), (0, 2), rowspan=2) trace = plt.subplot2grid((3, 5), (0, 3), colspan=2, rowspan=2) logo = plt.subplot2grid((3, 5), (2, 2)) coal_val = plt.subplot2grid((3, 5), (2, 3), colspan=2) # --- Ordering by distance to event --- if self.range_order: ttp = self.lut.get_value_at("TIME_P", point[0])[0] sidx = abs( np.argsort(np.argsort(ttp)) - np.max(np.argsort(np.argsort(ttp)))) else: sidx = np.argsort(self.data.stations)[::-1] for i in range(self.data.signal.shape[1]): if not self.filtered_signal: self._plot_signal_trace(trace, self.times, self.data.signal[0, i, :], sidx[i], color="r") self._plot_signal_trace(trace, self.times, self.data.signal[1, i, :], sidx[i], color="b") self._plot_signal_trace(trace, self.times, self.data.signal[2, i, :], sidx[i], color="g") else: self._plot_signal_trace(trace, self.times, self.data.filtered_signal[0, i, :], sidx[i], color="r") self._plot_signal_trace(trace, self.times, self.data.filtered_signal[1, i, :], sidx[i], color="b") self._plot_signal_trace(trace, self.times, self.data.filtered_signal[2, i, :], sidx[i], color="g") # --- Plotting the Station Travel Times --- ttime_range = self.lut.get_value_at("TIME_P", point[0])[0].shape[0] tps = [] tss = [] dt_max = UTCDateTime(dt_max) tmp_p = self.lut.get_value_at("TIME_P", point[0]) tmp_s = self.lut.get_value_at("TIME_S", point[0]) for i in range(ttime_range): tps.append((dt_max + tmp_p[0][i]).datetime) tss.append((dt_max + tmp_s[0][i]).datetime) del tmp_p, tmp_s self.tp_arrival = trace.scatter(tps, (sidx + 1), 50, "pink", marker="v", zorder=4, linewidth=0.1, edgecolors="black") self.ts_arrival = trace.scatter(tss, (sidx + 1), 50, "purple", marker="v", zorder=5, linewidth=0.1, edgecolors="black") # Set signal trace limits trace.set_xlim([(dt_max - 0.1).datetime, (self.data.end_time - 0.8).datetime]) trace.yaxis.tick_right() trace.yaxis.set_ticks(sidx + 1) trace.yaxis.set_ticklabels(self.data.stations) self.station_trace_vline = trace.axvline(dt_max.datetime, 0, 1000, linestyle="--", linewidth=2, color="r") # --- Plotting the Coalescence Function --- self._plot_coalescence_value(coal_val, dt_max.datetime) # --- Determining Error ellipse for Covariance --- cov_x = eq["GlobalCovariance_ErrX"] / self.lut.cell_size[0] cov_y = eq["GlobalCovariance_ErrY"] / self.lut.cell_size[1] cov_z = eq["GlobalCovariance_ErrZ"] / self.lut.cell_size[2] cov_crd = np.array([[ eq["GlobalCovariance_X"], eq["GlobalCovariance_Y"], eq["GlobalCovariance_Z"] ]]) cov_loc = self.lut.coord2loc(cov_crd) dCo = abs(cov_crd - self.lut.coord2loc(np.array([[ cov_loc[0][0] + cov_x, cov_loc[0][1] + cov_y, cov_loc[0][2] + cov_z ]]), inverse=True)) ellipse_XY = Ellipse( (eq["GlobalCovariance_X"], eq["GlobalCovariance_Y"]), 2 * dCo[0][0], 2 * dCo[0][1], angle=0, linewidth=2, edgecolor="k", fill=False, label="Global Covariance Error Ellipse") ellipse_YZ = Ellipse( (eq["GlobalCovariance_Z"], eq["GlobalCovariance_Y"]), 2 * dCo[0][2], 2 * dCo[0][1], angle=0, linewidth=2, edgecolor="k", fill=False) ellipse_XZ = Ellipse( (eq["GlobalCovariance_X"], eq["GlobalCovariance_Z"]), 2 * dCo[0][0], 2 * dCo[0][2], angle=0, linewidth=2, edgecolor="k", fill=False) # --- Determining Error ellipse for Gaussian --- gau_x = eq["LocalGaussian_ErrX"] / self.lut.cell_size[0] gau_y = eq["LocalGaussian_ErrY"] / self.lut.cell_size[1] gau_z = eq["LocalGaussian_ErrZ"] / self.lut.cell_size[2] gau_crd = np.array([[ eq["LocalGaussian_X"], eq["LocalGaussian_Y"], eq["LocalGaussian_Z"] ]]) gau_loc = self.lut.coord2loc(gau_crd) dGa = abs(gau_crd - self.lut.coord2loc(np.array([[ gau_loc[0][0] + gau_x, gau_loc[0][1] + gau_y, gau_loc[0][2] + gau_z ]]), inverse=True)) gellipse_XY = Ellipse((eq["LocalGaussian_X"], eq["LocalGaussian_Y"]), 2 * dGa[0][0], 2 * dGa[0][1], angle=0, linewidth=2, edgecolor="b", fill=False, label="Local Gaussian Error Ellipse") gellipse_YZ = Ellipse((eq["LocalGaussian_Z"], eq["LocalGaussian_Y"]), 2 * dGa[0][2], 2 * dGa[0][1], angle=0, linewidth=2, edgecolor="b", fill=False) gellipse_XZ = Ellipse((eq["LocalGaussian_X"], eq["LocalGaussian_Z"]), 2 * dGa[0][0], 2 * dGa[0][2], angle=0, linewidth=2, edgecolor="b", fill=False) # --- Plot slices through coalescence map --- self._plot_map_slice(xy_slice, eq, coa_map[:, :, int(loc[2][0])], crd, "X", "Y", ellipse_XY, gellipse_XY) xy_slice.legend() self._plot_map_slice(xz_slice, eq, coa_map[:, int(loc[1][0]), :], crd, "X", "Z", ellipse_XZ, gellipse_XZ) xz_slice.invert_yaxis() self._plot_map_slice(yz_slice, eq, np.transpose(coa_map[int(loc[0][0]), :, :]), crd, "Y", "Z", ellipse_YZ, gellipse_YZ) # --- Plotting the station locations --- xy_slice.scatter(self.lut.station_data["Longitude"], self.lut.station_data["Latitude"], 15, marker="^", color=self.line_station_color) xz_slice.scatter(self.lut.station_data["Longitude"], self.lut.station_data["Elevation"], 15, marker="^", color=self.line_station_color) yz_slice.scatter(self.lut.station_data["Elevation"], self.lut.station_data["Latitude"], 15, marker="<", color=self.line_station_color) for i, txt in enumerate(self.lut.station_data["Name"]): xy_slice.annotate(txt, [ self.lut.station_data["Longitude"][i], self.lut.station_data["Latitude"][i] ], color=self.line_station_color) # --- Plotting the xy_files --- self._plot_xy_files(xy_slice) # --- Plotting the logo --- self._plot_logo(logo, r"Earthquake Location Error", 10) if file_str is None: plt.show() else: fig.suptitle("Event Origin Time = {}".format(dt_max.datetime)) subdir = "summaries" util.make_directories(self.run_path, subdir=subdir) out_str = self.run_path / subdir / file_str plt.savefig("{}_EventSummary.pdf".format(out_str), dpi=400) plt.close("all")
def station_traces(self, file_str=None, event_name=None): """ Plot figures showing the filtered traces for each data component and the characteristic functions calculated from them (P and S) for each station. The search window to make a phase pick is displayed, along with the dynamic pick threshold (defined as a percentile of the background noise level), the phase pick time and its uncertainty (if made) and the gaussian fit to the characteristic function. Parameters ---------- file_str : str, optional String {run_name}_{evt_id} (figure displayed by default) event_name : str, optional Earthquake UID string; for subdirectory naming within directory {run_path}/traces/ """ # This function currently doesn't work due to a float/int issue # point = np.round(self.lut.coord2loc(np.array([[event["X"], # event["Y"], # event["Z"]]]))).astype(int) loc = np.where(self.coa_map == np.nanmax(self.coa_map)) point = np.array([[loc[0][0], loc[1][0], loc[2][0]]]) # Get P- and S-traveltimes at this location ptt = self.lut.get_value_at("TIME_P", point)[0] stt = self.lut.get_value_at("TIME_S", point)[0] # Make output dir for this event outside of loop if file_str: subdir = "traces" util.make_directories(self.run_path, subdir=subdir) out_dir = self.run_path / subdir / event_name util.make_directories(out_dir) # Looping through all stations for i in range(self.data.signal.shape[1]): station = self.lut.station_data["Name"][i] gau_p = self.phase_picks["GAU_P"][i] gau_s = self.phase_picks["GAU_S"][i] fig = plt.figure(figsize=(30, 15)) # Defining the plot fig.patch.set_facecolor("white") x_trace = plt.subplot(322) y_trace = plt.subplot(324) z_trace = plt.subplot(321) p_onset = plt.subplot(323) s_onset = plt.subplot(326) # Plotting the traces self._plot_signal_trace(x_trace, self.times, self.data.filtered_signal[0, i, :], -1, "r") self._plot_signal_trace(y_trace, self.times, self.data.filtered_signal[1, i, :], -1, "b") self._plot_signal_trace(z_trace, self.times, self.data.filtered_signal[2, i, :], -1, "g") p_onset.plot(self.times, self.data.p_onset[i, :], "r", linewidth=0.5) s_onset.plot(self.times, self.data.s_onset[i, :], "b", linewidth=0.5) # Defining Pick and Error picks = self.phase_picks["Pick"] phase_picks = picks[picks["Name"] == station].replace(-1, np.nan) phase_picks = phase_picks.reset_index(drop=True) for j, pick in phase_picks.iterrows(): if np.isnan(pick["PickError"]): continue pick_time = pick["PickTime"] pick_err = pick["PickError"] if pick["Phase"] == "P": self._pick_vlines(z_trace, pick_time, pick_err) yy = util.gaussian_1d(gau_p["xdata"], gau_p["popt"][0], gau_p["popt"][1], gau_p["popt"][2]) gau_dts = [x.datetime for x in gau_p["xdata_dt"]] p_onset.plot(gau_dts, yy) self._pick_vlines(p_onset, pick_time, pick_err) else: self._pick_vlines(y_trace, pick_time, pick_err) self._pick_vlines(x_trace, pick_time, pick_err) yy = util.gaussian_1d(gau_s["xdata"], gau_s["popt"][0], gau_s["popt"][1], gau_s["popt"][2]) gau_dts = [x.datetime for x in gau_s["xdata_dt"]] s_onset.plot(gau_dts, yy) self._pick_vlines(s_onset, pick_time, pick_err) dt_max = self.event_mw_data["DT"].iloc[np.argmax( np.array(self.event_mw_data["COA"]))] dt_max = UTCDateTime(dt_max) self._ttime_vlines(z_trace, dt_max, ptt[i]) self._ttime_vlines(p_onset, dt_max, ptt[i]) self._ttime_vlines(y_trace, dt_max, stt[i]) self._ttime_vlines(x_trace, dt_max, stt[i]) self._ttime_vlines(s_onset, dt_max, stt[i]) p_onset.axhline(gau_p["PickThreshold"]) s_onset.axhline(gau_s["PickThreshold"]) # Refining the window as around the pick time min_t = (dt_max + 0.5 * ptt[i]).datetime max_t = (dt_max + 1.5 * stt[i]).datetime x_trace.set_xlim([min_t, max_t]) y_trace.set_xlim([min_t, max_t]) z_trace.set_xlim([min_t, max_t]) p_onset.set_xlim([min_t, max_t]) s_onset.set_xlim([min_t, max_t]) suptitle = "Trace for Station {} - PPick = {}, SPick = {}" suptitle = suptitle.format(station, gau_p["PickValue"], gau_s["PickValue"]) fig.suptitle(suptitle) if file_str is None: plt.show() else: out_str = out_dir / file_str fname = "{}_{}.pdf" fname = fname.format(out_str, station) plt.savefig(fname) plt.close("all")
def write_cut_waveforms(self, data, event, event_name, data_format="MSEED", pre_cut=None, post_cut=None): """ Output raw cut waveform data as a waveform file -- defaults to mSEED. Parameters ---------- data : Archive object Contains read_waveform_data() method and stores read data in raw and processed state event : pandas DataFrame Final event location information. Columns = ["DT", "COA", "X", "Y", "Z", "LocalGaussian_X", "LocalGaussian_Y", "LocalGaussian_Z", "LocalGaussian_ErrX", "LocalGaussian_ErrY", "LocalGaussian_ErrZ", "GlobalCovariance_X", "GlobalCovariance_Y", "GlobalCovariance_Z", "GlobalCovariance_ErrX", "GlobalCovariance_ErrY", "GlobalCovariance_ErrZ"] All X / Y as lon / lat; Z and X / Y / Z uncertainties in metres event_name : str event_uid for file naming format : str, optional File format to write waveform data to. Options are all file formats supported by obspy, including: "MSEED" (default), "SAC", "SEGY", "GSE2" pre_cut : float, optional Specify how long before the event origin time to cut the waveform data from post_cut : float, optional Specify how long after the event origin time to cut the waveform data to """ st = data.raw_waveforms otime = UTCDateTime(event["DT"]) if pre_cut: for tr in st.traces: tr.trim(starttime=otime - pre_cut) if post_cut: for tr in st.traces: tr.trim(endtime=otime + post_cut) subdir = "cut_waveforms" util.make_directories(self.run, subdir=subdir) fname = self.run / subdir / "{}".format(event_name) if data_format == "MSEED": suffix = ".m" elif data_format == "SAC": suffix = ".sac" elif data_format == "SEGY": suffix = ".segy" elif data_format == "GSE2": suffix = ".gse2" else: suffix = ".waveforms" fname = str(fname.with_suffix(suffix)) st.write(str(fname), format=data_format) # , encoding="STEIM1")