Source code for seismic.plot.plot_dv

'''
:copyright:
    The SeisMIC development team (makus@gfz-potsdam.de).
:license:
    GNU Lesser General Public License, Version 3
    (https://www.gnu.org/copyleft/lesser.html)
:author:
   Peter Makus (makus@gfz-potsdam.de)

Created: Friday, 16th July 2021 02:30:02 pm
Last Modified: Monday, 4th March 2024 02:44:41 pm
'''

from datetime import datetime
from typing import Tuple, List
import os
import locale

import matplotlib as mpl
from matplotlib import pyplot as plt
from matplotlib import dates as mdates
import numpy as np
from obspy import UTCDateTime

from seismic.plot.plot_utils import set_mpl_params


[docs]def plot_fancy_dv( dv, xlim: Tuple[datetime, datetime] = None, ylim: Tuple[float, float] = None, return_ax: bool = False, title: str = None, dateformat: str = '%d %b %y', ax: plt.axes = None, *args, **kwargs): """ Prettier than the technical plot, but less informative""" set_mpl_params() if not ax: fig = plt.figure(figsize=(12, 8)) else: fig = ax.get_figure() val = -dv.value[~np.isnan(dv.corr)]*100 corr_starts = np.array(dv.stats.corr_start) t_real = [t.datetime for t in corr_starts[~np.isnan(dv.corr)]] ax = ax or plt.gca() # plot dv/v map = ax.scatter( t_real, val, c=dv.corr[~np.isnan(dv.corr)], cmap='inferno_r', s=22) # Correct format of X-Axis ax.set_ylim(ylim) plt.ylabel(r'$\frac{dv}{v}$ [%]') plt.grid(True, axis='y') try: locale.setlocale(locale.LC_ALL, "en_GB.utf8") except Exception: locale.setlocale(locale.LC_ALL, "en_GB.utf-8") ax.xaxis.set_major_formatter(mdates.DateFormatter(dateformat)) plt.xticks(rotation=25) fig.colorbar( map, orientation='horizontal', pad=0.1, shrink=.6, label='Correlation Coefficient', anchor=(0.5, .8)) plt.title(title) if xlim is not None: plt.xlim(xlim) else: plt.xlim((min(t_real), max(t_real))) if return_ax: return fig, ax
[docs]def plot_technical_dv( dv, save_dir='.', figure_file_name=None, mark_time=None, normalize_simmat=False, sim_mat_Clim=[], figsize=(9, 11), dpi=72, ylim: Tuple[float, float] = None, xlim: Tuple[datetime, datetime] = None, title: str = None, plot_scatter: bool = False, return_ax: bool = False, *args, **kwargs) -> Tuple[plt.figure, List[plt.axis]]: """ Plot a :class:`seismic.monitor.dv.DV` object The produced figure is saved in `save_dir` that, if necessary, it is created. It is also possible to pass a "special" time value `mark_time` that will be represented in the `dv/v` and `corr` plot as a vertical line; It can be a string (i.e. YYYY-MM-DD) or directly a :class:`~datetime.datetime` object. if the `dv` dictionary also contains a 'comb_mseedid' keyword, its `value` (i.e. MUST be a string) will be reported in the title. In case of the chosen filename exist in the `save_dir` directory, a prefix _<n> with n:0..+Inf, is added. The aspect of the plot may change depending on the matplotlib version. The recommended one is matplotlib 1.1.1 :type dv: dict :param dv: velocity change estimate dictionary as output by :class:`~miic.core.stretch_mod.multi_ref_vchange_and_align` and successively "extended" to contain also the timing in the form {'time': time_vect} where `time_vect` is a :class:`~numpy.ndarray` of :class:`~datetime.datetime` objects. :type save_dir: string :param save_dir: Directory where to save the produced figure. It is created if it doesn't exist. :type figure_file_name: string :param figure_file_name: filename to use for saving the figure. If None the figure is displayed in interactive mode. :type mark_time: string or :class:`~datetime.datetime` object :param mark_time: It is a "special" time location that will be represented in the `dv/v` and `corr` plot as a vertical line. :type normalize_simmat: Bool :param normalize_simmat: if True the simmat will be normalized to a maximum of 1. Defaults to False :type sim_mat_Clim: 2 element array_like :param sim_mat_Clim: if non-empty it set the color scale limits of the similarity matrix image :param ylim: Limits for the stretch axis. Defaults to None :type ylim: Tuple[float, float], optional :param return_ax: Return plt.figure and list of axes. Defaults to False. This overwrites any choice to save the figure. :type return_ax: bool, optional :returns: If `return_ax` is set to True it returns fig and axes. """ set_mpl_params() if sim_mat_Clim and len(sim_mat_Clim) != 2: raise ValueError('Sim_mat_Clim has to be a list or Tuple of length 2.') if figure_file_name: os.makedirs(save_dir, exist_ok=True) # Create a unique filename if TraitsUI-default is given if figure_file_name == 'plot_default': fname = figure_file_name + '_change.png' exist = os.path.isfile(os.path.join(save_dir, fname)) i = 0 while exist: fname = "%s_%i" % (figure_file_name, i) exist = os.path.isfile(os.path.join(save_dir, fname + '_change.png')) i += 1 figure_file_name = fname # Extract the data from the dictionary value_type = dv.value_type method = dv.method corr = dv.corr dt = dv.value sim_mat = dv.sim_mat stretch_vect = dv.second_axis stats = dv.stats plot_sim_mat = sim_mat.size > 0 rtime = np.array( [utcdt.datetime for utcdt in stats['corr_start']], dtype=np.datetime64) # normalize simmat if requested and if dv contains simmat if normalize_simmat and plot_sim_mat: sim_mat = sim_mat/np.tile( np.max(sim_mat, axis=1), (sim_mat.shape[1], 1)).T stretching_amount = np.max(stretch_vect) # Adapt plot details in agreement with the type of dictionary that # has been passed if (value_type == 'stretch') and (method == 'single_ref'): tit = "Single reference dv/v" # Find order of magnitude of max strech oom = -int(np.floor(np.log10(stretch_vect.max()/5))) dv_tick_delta = round(stretch_vect.max()/5, oom) dv_y_label = "dv/v" # plotting velocity requires to flip the stretching axis elif (value_type == 'stretch') and (method == 'multi_ref'): # Find order of magnitude of max strech oom = -int(np.floor(np.log10(stretch_vect.max()/5))) tit = "Multi reference dv/v" dv_tick_delta = round(stretch_vect.max()/5, oom) dv_y_label = "dv/v" # plotting velocity requires to flip the stretching axis elif (value_type == 'shift') and (method == 'time_shift'): tit = "Time shift" dv_tick_delta = 5 dv_y_label = "Time Shift (sample)" elif (value_type == 'shift') and (method == 'absolute_shift'): tit = "Time shift" dv_tick_delta = ( np.max(dv.second_axis) - np.min(dv.second_axis))/5 dv_y_label = "Time Shift [s]" else: raise ValueError(f"Unknown dv type, {value_type}!") f = plt.figure(figsize=figsize, dpi=dpi) if dv.n_stat is not None and plot_scatter: gs = mpl.gridspec.GridSpec(4, 1, height_ratios=[12, 4, 4, 1]) else: gs = mpl.gridspec.GridSpec(3, 1, height_ratios=[3, 1, 1]) ax1 = f.add_subplot(gs[0]) if plot_sim_mat: imh = plt.imshow( np.flipud(sim_mat.T).astype(float), interpolation='none', aspect='auto') # plotting value is way easier now plt.plot(-dv.value, 'b.') # Set extent so we can treat the axes properly (mainly y) if plot_sim_mat: imh.set_extent(( 0, sim_mat.shape[0], stretch_vect[-1], stretch_vect[0])) ### if xlim: xlim = (np.datetime64(xlim[0]), np.datetime64(xlim[1])) xl0 = np.argmin(abs(rtime-xlim[0])) xl1 = np.argmin(abs(rtime-xlim[1])) ax1.set_xlim(xl0, xl1+1) plt.xlim(xl0, xl1+1) else: ax1.set_xlim(0, len(dv.value)) plt.xlim(0, len(dv.value)) if value_type == 'stretch': ax1.invert_yaxis() if ylim: plt.ylim(ylim) # ### if sim_mat_Clim and plot_sim_mat: imh.set_clim(sim_mat_Clim[0], sim_mat_Clim[1]) plt.gca().get_xaxis().set_visible(False) comb_mseedid = '%s.%s.%s' % ( stats['network'], stats['station'], # stats['location'], stats['channel']) if title: tit = title else: tit = "%s estimate (%s)" % (tit, comb_mseedid) ax1.set_title(tit) ax1.yaxis.set_ticks_position('right') ax1.yaxis.set_label_position('left') ax1.set_xticklabels([]) ax1.set_ylabel(dv_y_label) ax2 = f.add_subplot(gs[1]) if plot_scatter: # reshape so we can plot a histogram histt = np.array([t.timestamp for t in stats['corr_start']]) histt = np.tile(histt, dv.stretches.shape[0]) histcorrs = np.reshape(dv.corrs, -1) histstretches = np.reshape(-dv.stretches, -1) n_bins = np.flip(np.array(dv.sim_mat.shape))//10 # remove nans nanmask = ~np.isnan(histcorrs) histt = histt[nanmask] histcorrs = histcorrs[nanmask] histstretches = histstretches[nanmask] # create histograms H_c, xedges_c, yedges_c = np.histogram2d(histt, histcorrs, bins=n_bins) H_v, xedges_v, yedges_v = np.histogram2d( histt, histstretches, bins=n_bins) xedges_c = [UTCDateTime(xe).datetime for xe in xedges_c] xedges_v = [UTCDateTime(xe).datetime for xe in xedges_v] plt.pcolor(xedges_v, yedges_v, H_v.T, cmap='binary') plt.plot(rtime, -dt, '.', markersize=3) if xlim: plt.xlim(xlim[0], xlim[1]) else: plt.xlim([rtime[0], rtime[-1]]) if ylim: plt.ylim(ylim) else: plt.ylim((-stretching_amount, stretching_amount)) if mark_time and not ( np.all(rtime < mark_time) and np.all(rtime > mark_time)): plt.axvline(mark_time, lw=1, color='r') ax2.yaxis.set_ticks_position('left') ax2.yaxis.set_label_position('right') ax2.yaxis.label.set_rotation(270) ax2.yaxis.set_label_coords(1.03, 0.5) ax2.set_ylabel(dv_y_label) ax2.yaxis.set_major_locator(plt.MultipleLocator(dv_tick_delta)) ax2.yaxis.grid(True, 'major', linewidth=1) ax2.xaxis.grid(True, 'major', linewidth=1) ax2.set_xticklabels([]) ax3 = f.add_subplot(gs[2]) if plot_scatter: plt.pcolor(xedges_c, yedges_c, H_c.T, cmap='binary') plt.plot(rtime, corr, '.', markersize=3) if xlim: plt.xlim(xlim[0], xlim[1]) else: plt.xlim([rtime[0], rtime[-1]]) ax3.yaxis.set_ticks_position('right') ax3.set_ylabel("Correlation") plt.ylim((0, 1)) if mark_time and not ( np.all(rtime < mark_time) and np.all(rtime > mark_time)): plt.axvline(mark_time, lw=1, color='r') ax3.yaxis.grid(True, 'major', linewidth=1) ax3.xaxis.grid(True, 'major', linewidth=1) ax3.yaxis.set_major_locator(plt.MultipleLocator(0.2)) # Plot number of stations if dv.n_stat is not None and plot_scatter: ax4 = f.add_subplot(gs[3]) plt.fill_between( rtime, 0, dv.n_stat, interpolate=False) plt.setp(ax4.get_xticklabels(), rotation=45, ha='right') ax3.set_xticklabels([]) ax4.yaxis.set_label_position('right') ax4.set_ylabel("N") plt.ylim(0, int(dv.n_stat.max()*1.1)) if xlim: plt.xlim(xlim[0], xlim[1]) else: plt.xlim([rtime[0], rtime[-1]]) else: ax4 = None plt.setp(ax3.get_xticklabels(), rotation=45, ha='right') plt.subplots_adjust(hspace=0, wspace=0) if return_ax: return f, [ax1, ax2, ax3, ax4] if figure_file_name is None: plt.show() else: print('saving to %s' % figure_file_name) if figure_file_name.split('.')[-1].lower() not in [ 'png', 'svg', 'pdf', 'jpg']: figure_file_name += '.png' f.savefig(os.path.join(save_dir, figure_file_name), dpi=dpi)
[docs]def plot_dv(style: str, *args, **kwargs): if style == 'technical': return plot_technical_dv(*args, **kwargs) elif style == 'publication': return plot_fancy_dv(*args, **kwargs) else: raise ValueError(f'Unknown style: {style}')