Source code for fracspy.location.migration

import numpy as np

from fracspy.location.utils import *


[docs] def kmigration(data, n_xyz, Op, nforhc=10): r"""Kirchhoff migration for microseismic source location. This routine performs imaging of microseismic data by migration using the adjoint of the Kirchhoff modelling operator. Parameters ---------- data : :obj:`numpy.ndarray` Data of shape :math`n_r \times n_t` n_xyz : :obj:`tuple` Number of grid points in X-, Y-, and Z-axes for the imaging area Op : :obj:`pyfrac.modelling.kirchhoff.Kirchhoff` Kirchhoff operator nforhc : :obj:`int`, optional Number of points for hypocenter Returns ------- migrated : :obj:`numpy.ndarray` Migrated volume hc : :obj:`numpy.ndarray` Estimated hypocentral location """ nx, ny, nz = n_xyz migrated = (Op.H @ data).reshape(nx, ny, nz) hc, _ = get_max_locs(migrated, n_max=nforhc, rem_edge=False) return migrated, hc
[docs] def diffstack(data: np.ndarray, n_xyz: tuple, x: np.ndarray, y: np.ndarray, z: np.ndarray, tt: np.ndarray, dt: float, nforhc: int = 10, output_type: str = None, stack_type: str = None, swsize: int = 0, polcor_type: str = None, recs: np.ndarray=None): r"""Diffraction stacking for microseismic source location. This routine performs imaging of microseismic data by diffraction stacking with various stacking approaches and optional polarity correction. Parameters ---------- data : :obj:`numpy.ndarray` Data of shape :math:`n_r \times n_t` n_xyz : :obj:`tuple` Number of grid points in X-, Y-, and Z-axes for the imaging area 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 nforhc : :obj:`int`, optional, default: 10 Number of points for hypocenter output_type : :obj:`str`, optional, default: None Defines the output type. In the default case of None it is set to ``max``. Types: ``max`` (output 3D volume as a maximum of the image function through time), ``mean`` (output 3D volume as an average of the image function through time), ``sumsq`` (output 3D volume as a stack of the squared image function values through time), ``full`` (output full 4D image function: x,y,z,t). In this case hc is set to None. 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: 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 ------- ds_im_vol : :obj:`numpy.ndarray` Diffraction stack volume, returned if output_type is not "full" hc : :obj:`numpy.ndarray` Estimated hypocentral location, if output_type is not "full" is set to None ds_full : :obj:`numpy.ndarray` Diffraction stack full array of shape (nt, n_xyz), returned if output_type is "full" Raises ------ ValueError : if data and grid sizes do not fit the traveltimes array ValueError : if output_type value is unknown ValueError : if stack_type value is unknown ValueError : if polcor_type value is unknown ValueError : if recs is None and polcor_type is not None Notes ----- The subsurface volume is discretised and each grid node is considered to be a potential source position or a so-called image point. In other words, each image point represents a possible diffraction point from which seismic energy radiates. The term "diffraction stacking" dates back to the works of Claerbout (1971, 1985) [3]_, [4]_ and Timoshin (1972) [7]_. The concept was initially related to seismic migration and imaging of reflectors as a set of diffraction points (in the context of the exploding reflector principle). Here, the diffraction stacking is related to imaging of real excitation sources. Diffraction stacking is applied to data with corrected event moveout (EMO), and can be done in several ways: 1. Absolute-value based .. math:: F(\mathbf{r},t) = \left| \sum_{R=1}^{N_R} A_R^{EMO}(t,\mathbf{r}) \right|, where :math:`\mathbf{r}` is a vector that defines a spatial position :math:`(x, y, z)` of the image point, :math:`A_R^{EMO}(t,\mathbf{r})` represents the EMO-corrected data at the receiver :math:`R`, and :math:`N_R` is a number of receivers [1]_. 2. Squared-value based Similarly, but the stack is taken to the power of 2: .. math:: E(\mathbf{r},t) = \left[ \sum_{R=1}^{N_R} A_R^{EMO}(t,\mathbf{r}) \right]^2, 3. Semblance-based Simple stacking can be further improved using a semblance-based approach. The semblance is a coherency or similarity measure and can be understood as the ratio of the total energy (the square of sum of amplitudes) to the energy of individual traces (the sum of squares) [5]_: .. math:: S(\mathbf{r},t) = \frac{\left[ \sum_{R=1}^{N_R} A_R^{EMO}(t,\mathbf{r}) \right]^2} {N_R \sum_{R=1}^{N_R} \left[ A_R^{EMO}(t,\mathbf{r}) \right]^2} In order to suppress the effect of noise even better, it is possible to extend the semblance-based approach by introducing a sliding time window :math:`W` over which the energy measures are summed: .. math:: S_W(\mathbf{r},t,W) = \frac{\sum_{k=it-W}^{it+W}\left[ \sum_{R=1}^{N_R} A_R^{EMO}(t,\mathbf{r}) \right]^2} {N_R \sum_{k=it-W}^{it+W}\sum_{R=1}^{N_R} \left[ A_R^{EMO}(t,\mathbf{r}) \right]^2} where :math:`k` an index of the time-discretised signal within a sliding time interval consisting of the :math:`2W + 1` samples, and :math:`it` is the index of time :math:`t` [8]_. Depending on the desired output, the function can output either a 3D volume and a location, or a full 4D array (time dimension + 3 spatial dimensions). By default, a 3D image volume obtained as a maximum of the 4D image function, e.g.: .. math:: F_s(\mathbf{r}) = \max_t F(\mathbf{r},t). Optionally, one can output a 3D image volume obtained as a mean of the 4D image function over time, e.g.: .. math:: F_s(\mathbf{r}) = \underset{t}{\text{mean}}~F(\mathbf{r}, t). Another option is to output a 3D image volume obtained as a sum of squared values of the 4D image function over time, e.g.: .. math:: F_s(\mathbf{r}) = \sum_t F^2(\mathbf{r},t). The full 4D output is useful for subsequent detection of multiple events. Polarity correction, if requested, is done using moment tensor inversion [2]_: .. math:: F_{pc}(\mathbf{r},t) = \left|\sum_{R=1}^{N_R} \phi_R(\mathbf{r}) A_R^{EMO+PC} \right|, where .. math:: A_R^{EMO+PC}(t,\mathbf{r}) = \phi_R(\mathbf{r}) A_R \left(t + T_R(\mathbf{r})\right), and :math:`\phi_R(\mathbf{r})` are the defined through the :math:`\mathrm{sign}` function: .. math:: \phi_R(\mathbf{r})=\mathrm{sign}(\mathbf{M}(\mathbf{r})\mathbf{G}_R^{\phantom{T}}(\mathbf{r})),\quad R=1\dots N_R, where :math:`\mathbf{M}(\mathbf{r})` is the vectorized moment tensor derived [6]_ as .. math:: \mathbf{M}(\mathbf{r}) = \left(\sum_{R=1}^{N_R} \mathbf{G}_R^T(\mathbf{r})\mathbf{G}_R^{\phantom{T}}(\mathbf{r})\right)^{-1} \mathbf{d}(\mathbf{r}), where :math:`\mathbf{G}_R^{\phantom{T}}(\mathbf{r})` is the vectorized derivative of Green's function for receiver :math:`R` and :math:`\mathbf{d}(\mathbf{r})` is a vector obtained from amplitudes of the corresponding waveforms: .. math:: \mathbf{d}(\mathbf{r}) = \sum_{R=1}^{N_R} A_R(t+T_R(\mathbf{r})) \mathbf{G}_R^{\phantom{T}}(\mathbf{r}). More details on the described inversion including stability issues can be found in Zhebel & Eisner (2014) [9]_. 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] Claerbout, J. F. (1971). Toward a unified theory of reflector mapping. Geophysics, 36(3), 467-481. https://doi.org/10.1190/1.1440185 .. [4] Claerbout, J. (1985). Imaging the earth's interior. Oxford, England: Blackwell Scientific Publications. https://sepwww.stanford.edu/sep/prof/iei2/ .. [5] Neidell, N. S., & Taner, M. T. (1971). SEMBLANCE AND OTHER COHERENCY MEASURES FOR MULTICHANNEL DATA. Geophysics, 36(3), 482–497. https://doi.org/10.1190/1.1440186 .. [6] Sipkin, S. A. (1982). Estimation of earthquake source parameters by the inversion of waveform data: synthetic waveforms. Physics of the Earth and Planetary Interiors, 30(2–3), 242–259. https://doi.org/10.1016/0031-9201(82)90111-x .. [7] Timoshin Yu. V. (1972). Fundamentals of diffraction conversion of seismic recordings. Moscow: Nedra. [In Russian]. .. [8] Trojanowski, J., & Eisner, L. (2016). Comparison of migration‐based location and detection methods for microseismic events. Geophysical Prospecting, 65(1), 47–63. https://doi.org/10.1111/1365-2478.12366 .. [9] Zhebel, O., & Eisner, L. (2014). Simultaneous microseismic event localization and source mechanism determination. Geophysics, 80(1), KS1–KS9. https://doi.org/10.1190/geo2014-0055.1 """ # Get sizes nx, ny, nz = n_xyz ngrid = nx * ny * nz nr, nt = data.shape # Check sizes if nr != tt.shape[0]: raise ValueError(f"Number of traces in data is not consistent with traveltimes: {nr} != {tt.shape[0]}") if nx != tt.shape[1]: raise ValueError(f"Input nx is not consistent with traveltimes: {nx} != {tt.shape[1]}") if ny != tt.shape[2]: raise ValueError(f"Input ny is not consistent with traveltimes: {ny} != {tt.shape[2]}") if nz != tt.shape[3]: raise ValueError(f"Input nz is not consistent with traveltimes: {nz} != {tt.shape[3]}") # Check output type if output_type is None: output_type = "max" if output_type not in ["max","mean","sumsq","full"]: raise ValueError(f"Output type is unknown: {output_type}") # Check stacking type if stack_type is None: stack_type = "absolute" if stack_type not in ["absolute","squared","semblance"]: raise ValueError(f"Diffraction stacking type is unknown: {stack_type}") # Check polarity correction if polcor_type is not None: if polcor_type not in ["mti"]: raise ValueError(f"Polarity correction type is unknown: {polcor_type}") if recs is None: raise ValueError(f"Receiver coordinates are required for polarity correction type {polcor_type}") # Reshape tt array ttg = tt.reshape(nr, -1) # Initialise image arrays ds_full = np.zeros((nt,ngrid)) ds_im = np.zeros(ngrid) # Find time sample shifts for all grid points itshifts = np.round((ttg - ttg.min(axis=0))/dt) # Precompute vectorized Green tensor derivatives for grid points if polcor_type is not None: # Check sizes of recs if recs.shape[0] != 3: raise ValueError(f"Size of the receiver coordinates array is not consistent: {recs.shape}") if nr != recs.shape[1]: raise ValueError(f"Number of traces in data is not consistent with array of receiver coordinates: {nr} != {recs.shape[1]}") vgtd_grid = vgtd(x=x,y=y,z=z,recs=recs) gtg_inv_grid = mgtdinv(g=vgtd_grid) # Loop over grid points for igrid in range(ngrid): # Perform event moveout correction for data data_mc = moveout_correction(data=data, itshifts=itshifts[:,igrid]) # Perform polarity correction for data if needed if polcor_type is not None: data_pc = polarity_correction(data=data_mc, polcor_type=polcor_type, g=vgtd_grid[:,:,igrid], gtg_inv=gtg_inv_grid[:,:,igrid] ) else: data_pc = data_mc # Perform stacking based on the type if stack_type == "absolute": ds = np.abs(np.sum(data_pc,axis=0)) elif stack_type == "squared": ds = (np.sum(data_pc,axis=0))**2 elif stack_type == "semblance": ds = semblance_stack(data=data_pc, swsize=swsize) # Fill based on the output type if output_type == "max": ds_im[igrid] = np.max(ds) elif output_type == "mean": ds_im[igrid] = np.mean(ds) elif output_type == "sumsq": ds_im[igrid] = np.sum(ds**2) elif output_type == "full": ds_full[:, igrid] = ds # Construct output based on its type if output_type in ["full"]: # Return full array and location as None hc = None return ds_full, hc else: ds_im_vol = ds_im.reshape(nx, ny, nz) hc, _ = get_max_locs(ds_im_vol, n_max=nforhc, rem_edge=False) # Return 3D array and location return ds_im_vol, hc