Source code for fracspy.detection.stacking

import numpy as np
#from fracspy.location.utils import *
from fracspy.location import Location

[docs] def maxdiffstack(data: np.ndarray, x: np.ndarray, y: np.ndarray, z: np.ndarray, tt: np.ndarray, dt: float, stack_type: str = None, swsize: int = 0, polcor_type: str = None, recs: np.ndarray=None): r"""Maximum diffraction stack function for microseismic event detection. This routine uses diffraction stacking to obtain the time-dependent maximum stack function used for microseismic event detection. Parameters ---------- data : :obj:`numpy.ndarray` Data of shape :math:`n_r \times n_t` x : :obj:`numpy.ndarray` Imaging area grid vector in X-axis y : :obj:`numpy.ndarray` Imaging area grid vector in Y-axis z : :obj:`numpy.ndarray` Imaging area grid vector in Z-axis tt : :obj:`numpy.ndarray` Traveltime table of size :math:`n_r \times n_x \times n_y \times n_z` stack_type : :obj:`str`, optional, default: None Diffraction stacking type (imaging condition), default None is the same as ``squared``. Types: ``absolute`` (absolute value), ``squared`` (squared value), ``semblance`` (semblance-based). swsize : :obj:`int`, optional, default: 0 Sliding time window size for semblance-based type, amount of time steps polcor_type : :obj:`str`, optional, default: None Polarity correction type to be used for amplitudes. None is default for no polarity correction, ``mti`` is for polarity correction based on moment tensor inversion. recs : :obj:`numpy.ndarray`, optional, default: None Array of shape (3, nrec) containing receiver coordinates. Must be provided if polcor_type is not None Returns ------- msf : :obj:`numpy.ndarray` Time-dependent maximum stack function ds_full : :obj:`numpy.ndarray` Diffraction stack array reshaped to 4D (nt,nx,ny,nz) Notes ----- For every time step :math:`t` the maximum of the diffraction stacking imaging function :math:`F(\mathbf{r},t)` over all potential locations :math:`\mathbf{r}` is evaluated [1]_: .. math:: F_t(t) = \max_{\mathbf{r}} F(\mathbf{r},t), where :math:`F_t(t)` is referred to as the maximum stack function or MSF [2]_. References ---------- .. [1] Anikiev, D. (2015). Joint detection, location and source mechanism determination of microseismic events (Doctoral dissertation). St. Petersburg State University. St. Petersburg. https://disser.spbu.ru/files/phdspsu2015/Anikiev_PhD_web_final.pdf .. [2] Anikiev, D., Valenta, J., Staněk, F. & Eisner, L. (2014). Joint location and source mechanism inversion of microseismic events: Benchmarking on seismicity induced by hydraulic fracturing. Geophysical Journal International, 198(1), 249–258. https://doi.org/10.1093/gji/ggu126 """ # Get sizes nx, ny, nz = len(x), len(y), len(z) ngrid = nx * ny * nz nr, nt = data.shape # Set up the location class L = Location(x, y, z) # Apply diffraction stacking to get full 4D function ds_full, _ = L.apply(data=data, kind="diffstack", x=x, y=y, z=z, tt=tt, dt=dt, stack_type=stack_type, swsize=swsize, polcor_type=polcor_type, recs=recs, output_type = "full") # Get the time-dependent MSF by taking maximum over the spatial dimension msf = np.max(ds_full, axis=1) # Return MSF and diffraction stack 4D array, i.e. reshaped to (nt,nx,ny,nz) return msf, ds_full.reshape(nt, nx, ny, nz)
[docs] def stalta(tdf:np.ndarray, dt:float, stw:float, ltw:float, gtw:float=0): r"""STA/LTA computation. Computes Short Term Average / Long Term Average (STA/LTA) for a time-dependent function provided the short time window (STW), long time window (LTW) and a gap time window (GTW) between them. Parameters ---------- tdf : :obj:`numpy.ndarray` Time-dependent function of shape (nt,) dt : :obj:`float` Time step of the tdf stw : :obj:`float` Short time window in seconds (must be more than two time steps) ltw : :obj:`float` Long time window in seconds (must be more than two time steps) gtw : :obj:`float`, optional, default: 0 Gap time window in seconds (must be non-negative) Returns ------- slf : :obj:`numpy.ndarray` Time-dependent STA/LTA function sta : :obj:`numpy.ndarray` Time-dependent STA function lta : :obj:`numpy.ndarray` Time-dependent LTA function Notes ----- The LTA and STA are defined as root mean squares of the signal in the LTW and STW with subtracted constant level. This constant level is the minimum between the average of the signal in the STW and LTW. The LTW goes first, then there is a GTW (if non-zero) and then goes the STW. The value of LTA/STA is calculated for the time moment on the border of the GTW and STW. The time function is padded appropriately on the both sides to have a full-length preceding LTW + GTW and a full-length following STW. """ # Get size of the function nt = len(tdf) # Check if nt is at least 4 time steps if nt < 3 * dt: raise ValueError("Input time-dependent function must be more than three time steps.") # Check if stw and ltw are more than two time steps if stw < 2 * dt or ltw < 2 * dt: raise ValueError("STW and LTW must be more than two time steps.") # Check if gtw is non-negative if gtw < 0: raise ValueError("GTW must be non-negative.") # Get sizes of stw, ltw and gtw, rounding and converting to integers nstw = int(np.around(stw / dt)) nltw = int(np.around(ltw / dt)) ngtw = int(np.around(gtw / dt)) # Initialise STA/LTA function (SLF) slf = np.zeros_like(tdf) # Initialise arrays for STA/LTA, STA, LTA slf = np.zeros(nt) sta = np.zeros(nt) lta = np.zeros(nt) # Find low level of amplitudes to substitute for zeros in tdf lowlevel = max(np.finfo(float).eps, 1e-3 * np.median(np.abs(tdf))) # Substitute zeros with low level in tdf to avoid nans tdf[tdf == 0] = lowlevel # Avoid boundary problems by padding the input signal appropriately padded_tdf = np.pad(tdf, (nltw + ngtw, nstw), mode='reflect') # Compute LTA and STA as the root mean square (RMS) in the respective windows for it in range(nt): # Indices for the LTA window lta_start = it lta_end = it + nltw # Indices for the STA window sta_start = it + nltw + ngtw sta_end = sta_start + nstw # Calculate RMS for LTA and STA level = min(np.mean(padded_tdf[lta_start:lta_end]),np.mean(padded_tdf[sta_start:sta_end])) lta[it] = np.sqrt(np.mean((padded_tdf[lta_start:lta_end]-level) ** 2)) sta[it] = np.sqrt(np.mean((padded_tdf[sta_start:sta_end]-level) ** 2)) # Calculate STA/LTA ratio, avoid division by zero slf[it] = sta[it] / lta[it] if lta[it] != 0 else 0 return slf, sta, lta
[docs] def detect_peaks(msf:np.ndarray, slf:np.ndarray, slt:float): r"""Detect peaks in the maximum stack function based on STA/LTA function. Detect local maxima in the time-dependent maximum stack function (MSF) based on the analysis of STA/LTA function (SLF) and an STA/LTA thereshold (SLT). Parameters ---------- msf : :obj:`numpy.ndarray` Time-dependent maximum stack function of shape (nt,) slf : :obj:`numpy.ndarray` Time-dependent STA/LTA function of shape (nt,) slt : :obj:`float` STA/LTA threshold, must be non-negative Returns ------- idp : :obj:`numpy.ndarray` Integer array of time indices of determined peaks Raises ------ ValueError : if slt is negative Notes ----- The SLF is compared to the SLT. If all values of the SLF are below the SLT, the array of time indices of determined peaks (`idp`) is set to be empty. Otherwise, there will be one or more regions where SLf exceeds SLT. For each of these regions, sequentially, the algorithm finds an index of the maximum value of the MSF and saves it the `idp`. """ # Check SLT if slt<0: raise ValueError(f"STA/LTA threshold must be non-negative, but it is {slt}") # Identify regions where SLF exceeds the threshold SLT above_threshold = slf > slt if not np.any(above_threshold): # Return empty array if no values in SLF exceed the threshold return np.array([], dtype=int) # Find the boundaries of regions where SLF exceeds the threshold regions = np.where(np.diff(above_threshold.astype(int)))[0] + 1 # Include start and end boundaries if necessary if above_threshold[0]: regions = np.insert(regions, 0, 0) if above_threshold[-1]: regions = np.append(regions, len(above_threshold)) # Initialize the array for storing peak indices idp = [] # Iterate over the regions to detect peaks for i in range(0, len(regions), 2): start = regions[i] end = regions[i+1] # Find the index of the maximum value of MSF in this region max_index = np.argmax(msf[start:end]) + start idp.append(max_index) return np.array(idp, dtype=int)
[docs] def estimate_origin_times(idp: np.ndarray, ds_full: np.ndarray, tt: np.ndarray, dt: float, x: np.ndarray, y: np.ndarray, z: np.ndarray): r"""Estimate origin times of the events detected with diffraction stacking. Estimate origin times of the events using traveltimes and the time indices determined from the maximum stack function (MSF) using STA/LTA analysis. Parameters ---------- idp : :obj:`numpy.ndarray` Integer array of time indices of determined peaks ds_full : :obj:`numpy.ndarray` Diffraction stack full 4D array of shape (nt, nx, ny, nz) tt : :obj:`numpy.ndarray` Traveltime table of size :math:`n_r \times n_x \times n_y \times n_z` dt : :obj:`float` Time step x : :obj:`numpy.ndarray` Imaging area grid vector in X-axis y : :obj:`numpy.ndarray` Imaging area grid vector in Y-axis z : :obj:`numpy.ndarray` Imaging area grid vector in Z-axis Returns ------- eot : :obj:`numpy.ndarray` Array of estimated origin times for each of the determined peaks Notes ----- For each determined MSF peak, the origin time :math:`t_{est}` of the corresponding event can be determined from peak time :math:`t_{peak}` as .. math:: \mathbf{t}_{est} = t_{peak} - \min_{R} T_R(\mathbf{r}_{peak}). where :math:`T_R(\mathbf{r}_{peak})` is the traveltime to receiver :math:`R` from :math:`\mathbf{r}_{peak}` - the location determined from the maximum of the 4D imaging function :math:`F(\mathbf{r},t)` at the time moment :math:`t_{peak}`: .. math:: \mathbf{r}_{peak} = \arg\!\max_{\mathbf{r}} F(\mathbf{r},t_{peak}). """ # Get sizes nt, nx, ny, nz = ds_full.shape # Number of detected peaks nde = len(idp) # Create data time vector t = np.arange(0, nt * dt, dt) # Initialize the eot array eot = np.zeros(nde) # Loop over detections for ide in range(nde): # Get time index it = idp[ide] # Find spatial indices corresponding to the maximum of ds_full at time index it ix, iy, iz = np.unravel_index(np.argmax(ds_full[it, :, :, :]), ds_full[it, :, :, :].shape) # Get the estimated origin time eot[ide] = t[it] - np.min(np.squeeze(tt[:,ix,iy,iz])) return eot
[docs] def diffstack_detect(data: np.ndarray, x: np.ndarray, y: np.ndarray, z: np.ndarray, tt: np.ndarray, dt: float, stw: float, ltw: float, gtw: float=0, slt: float=3, stack_type: str = None, swsize: int = 0, polcor_type: str = None, recs: np.ndarray=None): r"""Microseismic event detection based on diffraction stacking. The time-dependent maximum stack function (MSF) obtained by diffraction stacking is analysed using STA/LTA. Parameters ---------- data : :obj:`numpy.ndarray` Data of shape :math:`n_r \times n_t` x : :obj:`numpy.ndarray` Imaging area grid vector in X-axis y : :obj:`numpy.ndarray` Imaging area grid vector in Y-axis z : :obj:`numpy.ndarray` Imaging area grid vector in Z-axis tt : :obj:`numpy.ndarray` Traveltime table of size :math:`n_r \times n_x \times n_y \times n_z` dt : :obj:`float` Time step stw : :obj:`float` Short time window in seconds (must be more than two time steps) ltw : :obj:`float` Long time window in seconds (must be more than two time steps) gtw : :obj:`float`, optional, default: 0 Gap time window in seconds (must be non-negative) gtw : :obj:`float`, optional, default: 0 Gap time window in seconds (must be non-negative) stack_type : :obj:`str`, optional, default: None Diffraction stacking type (imaging condition), default None is the same as ``squared``. Types: ``absolute`` (absolute value), ``squared`` (squared-value), ``semblance`` (semblance-based). swsize : :obj:`int`, optional, default: 0 Sliding time window size for semblance-based type, amount of time steps polcor_type : :obj:`str`, optional, default: None Polarity correction type to be used for amplitudes. None is default for no polarity correction, ``mti`` is for polarity correction based on moment tensor inversion. recs : :obj:`numpy.ndarray`, optional, default: None Array of shape (3, nrec) containing receiver coordinates. Must be provided if polcor_type is not None Returns ------- msf : :obj:`numpy.ndarray` Time-dependent maximum stack function slf : :obj:`numpy.ndarray` Time-dependent STA/LTA function idp : :obj:`numpy.ndarray` Integer array of time indices of determined peaks eot : :obj:`numpy.ndarray` Array of estimated origin times for each of the determined peaks ds_full : :obj:`numpy.ndarray` Diffraction stack full 4D array of shape (nt, nx, ny, nz) Notes ----- The local maxima of the maximum stack function [1]_ (MSF) occur at the times linked to the origin times of microseismic events. These local maxima can be found by the STA/LTA (Short Term Average / Long Term Average) method [3]_. Local maxima are detected by measuring the ratio of average stack energy in short and long sliding time windows and comparing this ratio with the pre-defined STA/LTA threshold (SLT). The number of detected events depends on the signal-to-noise ratio (SNR) of the maximum stack function measured by STA/LTA [2]_. If the SLT is too high, too few events are detected, but there is a high certainty that the detected events are real. If a too low threshold is used, false events will be detected as real. References ---------- .. [1] Anikiev, D. (2015). Joint detection, location and source mechanism determination of microseismic events (Doctoral dissertation). St. Petersburg State University. St. Petersburg. https://disser.spbu.ru/files/phdspsu2015/Anikiev_PhD_web_final.pdf .. [2] Anikiev, D., Valenta, J., Staněk, F. & Eisner, L. (2014). Joint location and source mechanism inversion of microseismic events: Benchmarking on seismicity induced by hydraulic fracturing. Geophysical Journal International, 198(1), 249–258. https://doi.org/10.1093/gji/ggu126 .. [3] Trnkoczy, A. (2012). Understanding and parameter setting of STA/LTA trigger algorithm. In: Bormann, P. (Ed.), New Manual of Seismological Observatory Practice 2 (NMSOP-2), Potsdam: Deutsches GeoForschungsZentrum GFZ, 1-20. https://doi.org/10.2312/GFZ.NMSOP-2_IS_8.1 """ # Get maximum stack function msf,ds_full = maxdiffstack(data=data, x=x, y=y, z=z, tt=tt, dt=dt, stack_type=stack_type) # Compute STA/LTA slf,_,_ = stalta(tdf=msf, dt=dt, stw=stw, ltw=ltw, gtw=gtw) # Detect msf peaks in triggered zones idp = detect_peaks(msf=msf, slf=slf, slt=slt) # Estimate event origin times eot = estimate_origin_times(idp=idp, ds_full=ds_full, tt=tt, dt=dt, x=x, y=y, z=z) return msf,slf,idp,eot,ds_full