def test_taper2d(par): """Create taper wavelet and check size and values""" tap = taper2d(par["nt"], par["nspat"][0], par["ntap"][0], par["tapertype"]) assert tap.shape == (par["nspat"][0], par["nt"]) assert_array_equal(tap[0], np.zeros(par["nt"])) assert_array_equal(tap[-1], np.zeros(par["nt"])) assert_array_equal(tap[par["ntap"][0] + 1], np.ones(par["nt"])) assert_array_equal(tap[par["nspat"][0] // 2], np.ones(par["nt"]))
def Deghosting( p, nt, nr, dt, dr, vel, zrec, pd=None, win=None, npad=(11, 11), ntaper=(11, 11), restriction=None, sptransf=None, solver=lsqr, dottest=False, dtype="complex128", **kwargs_solver ): r"""Wavefield deghosting. Apply seismic wavefield decomposition from single-component (pressure) data. This process is also generally referred to as model-based deghosting. Parameters ---------- p : :obj:`np.ndarray` Pressure data of of size :math:`\lbrack n_{r_x}\,(\times n_{r_y}) \times n_t \rbrack` (or :math:`\lbrack n_{r_{x,\text{sub}}}\, (\times n_{r_{y,\text{sub}}}) \times n_t \rbrack` in case a ``restriction`` operator is provided. Note that :math:`n_{r_{x,\text{sub}}}` (and :math:`n_{r_{y,\text{sub}}}`) must agree with the size of the output of this operator) nt : :obj:`int` Number of samples along the time axis nr : :obj:`int` or :obj:`tuple` Number of samples along the receiver axis (or axes) dt : :obj:`float` Sampling along the time axis dr : :obj:`float` or :obj:`tuple` Sampling along the receiver array of the separated pressure consituents vel : :obj:`float` Velocity along the receiver array (must be constant) zrec : :obj:`float` Depth of receiver array pd : :obj:`np.ndarray`, optional Direct arrival to be subtracted from ``p`` win : :obj:`np.ndarray`, optional Time window to be applied to ``p`` to remove the direct arrival (if ``pd=None``) ntaper : :obj:`float` or :obj:`tuple`, optional Number of samples of taper applied to propagator to avoid edge effects npad : :obj:`float` or :obj:`tuple`, optional Number of samples of padding applied to propagator to avoid edge effects angle restriction : :obj:`pylops.LinearOperator`, optional Restriction operator sptransf : :obj:`pylops.LinearOperator`, optional Sparsifying operator solver : :obj:`float`, optional Function handle of solver to be used if ``kind='inverse'`` dottest : :obj:`bool`, optional Apply dot-test dtype : :obj:`str`, optional Type of elements in input array. If ``None``, directly inferred from ``p`` **kwargs_solver Arbitrary keyword arguments for chosen ``solver`` Returns ------- pup : :obj:`np.ndarray` Up-going wavefield pdown : :obj:`np.ndarray` Down-going wavefield Notes ----- Up- and down-going components of seismic data :math:`p^-(x, t)` and :math:`p^+(x, t)` can be estimated from single-component data :math:`p(x, t)` using a ghost model. The basic idea [1]_ is that of using a one-way propagator in the f-k domain (also referred to as ghost model) to predict the down-going field from the up-going one (excluded the direct arrival and its source ghost referred here to as :math:`p_d(x, t)`): .. math:: p^+ - p_d = e^{-j k_z 2 z_\text{rec}} p^- where :math:`k_z` is the vertical wavenumber and :math:`z_\text{rec}` is the depth of the array of receivers In a matrix form we can thus write the total wavefield as: .. math:: \mathbf{p} - \mathbf{p_d} = (\mathbf{I} + \Phi) \mathbf{p}^- where :math:`\Phi` is one-way propagator implemented via the :class:`pylops.waveeqprocessing.PhaseShift` operator. .. [1] Amundsen, L., 1993, Wavenumber-based filtering of marine point-source data: GEOPHYSICS, 58, 1335–1348. """ ndims = p.ndim if ndims == 2: dims = (nt, nr) nrs = nr nkx = nr + 2 * npad kx = np.fft.ifftshift(np.fft.fftfreq(nkx, dr)) ky = None else: dims = (nt, nr[0], nr[1]) nrs = nr[0] * nr[1] nkx = nr[0] + 2 * npad[0] kx = np.fft.ifftshift(np.fft.fftfreq(nkx, dr[0])) nky = nr[1] + 2 * npad[1] ky = np.fft.ifftshift(np.fft.fftfreq(nky, dr)) nf = nt freq = np.fft.rfftfreq(nf, dt) # Phase shift operator zprop = 2 * zrec if ndims == 2: taper = taper2d(nt, nr, ntaper).T Padop = Pad(dims, ((0, 0), (npad, npad))) else: taper = taper3d(nt, nr, ntaper).transpose(2, 0, 1) Padop = Pad(dims, ((0, 0), (npad[0], npad[0]), (npad[1], npad[1]))) Pop = ( -Padop.H * PhaseShift(vel, zprop, nt, freq, kx, ky) * Padop * Diagonal(taper.ravel(), dtype=dtype) ) # Decomposition operator Dupop = Identity(nt * nrs, dtype=p.dtype) + Pop if dottest: Dottest(Dupop, nt * nrs, nt * nrs, verb=True) # Add restriction if restriction is not None: Dupop_norestr = Dupop Dupop = restriction * Dupop # Add sparsify transform if sptransf is not None: Dupop_norestr = Dupop_norestr * sptransf Dupop = Dupop * sptransf # Define data if pd is not None: d = p - pd else: d = win * p # Inversion pup = solver(Dupop, d.ravel(), **kwargs_solver)[0] # Apply sparse transform if sptransf is not None: p = Dupop_norestr * pup # reconstruct p at finely sampled spatial axes pup = sptransf * pup p = np.real(p).reshape(dims) # Finalize estimates pup = np.real(pup).reshape(dims) pdown = p - pup return pup, pdown
def Sliding2D(Op, dims, dimsd, nwin, nover, tapertype="hanning", design=False): """2D Sliding transform operator. Apply a transform operator ``Op`` repeatedly to slices of the model vector in forward mode and slices of the data vector in adjoint mode. More specifically, in forward mode the model vector is divided into slices, each slice is transformed, and slices are then recombined in a sliding window fashion. Both model and data are internally reshaped and interpreted as 2-dimensional arrays: each slice contains a portion of the array in the first dimension (and the entire second dimension). This operator can be used to perform local, overlapping transforms (e.g., :obj:`pylops.signalprocessing.FFT2D` or :obj:`pylops.signalprocessing.Radon2D`) on 2-dimensional arrays. .. note:: The shape of the model has to be consistent with the number of windows for this operator not to return an error. As the number of windows depends directly on the choice of ``nwin`` and ``nover``, it is recommended to use ``design=True`` if unsure about the choice ``dims`` and use the number of windows printed on screen to define such input parameter. .. warning:: Depending on the choice of `nwin` and `nover` as well as the size of the data, sliding windows may not cover the entire first dimension. The start and end indices of each window can be displayed using ``design=True`` while defining the best sliding window approach. Parameters ---------- Op : :obj:`pylops.LinearOperator` Transform operator dims : :obj:`tuple` Shape of 2-dimensional model. Note that ``dims[0]`` should be multiple of the model size of the transform in the first dimension dimsd : :obj:`tuple` Shape of 2-dimensional data nwin : :obj:`int` Number of samples of window nover : :obj:`int` Number of samples of overlapping part of window tapertype : :obj:`str`, optional Type of taper (``hanning``, ``cosine``, ``cosinesquare`` or ``None``) design : :obj:`bool`, optional Print number of sliding window (``True``) or not (``False``) Returns ------- Sop : :obj:`pylops.LinearOperator` Sliding operator Raises ------ ValueError Identified number of windows is not consistent with provided model shape (``dims``). """ # model windows mwin_ins, mwin_ends = _slidingsteps(dims[0], Op.shape[1] // dims[1], 0) # data windows dwin_ins, dwin_ends = _slidingsteps(dimsd[0], nwin, nover) nwins = len(dwin_ins) # create tapers if tapertype is not None: tap = taper2d(dimsd[1], nwin, nover, tapertype=tapertype) tapin = tap.copy() tapin[:nover] = 1 tapend = tap.copy() tapend[-nover:] = 1 taps = {} taps[0] = tapin for i in range(1, nwins - 1): taps[i] = tap taps[nwins - 1] = tapend # check that identified number of windows agrees with mode size if design: logging.warning("%d windows required...", nwins) logging.warning("model wins - start:%s, end:%s", str(mwin_ins), str(mwin_ends)) logging.warning("data wins - start:%s, end:%s", str(dwin_ins), str(dwin_ends)) if nwins * Op.shape[1] // dims[1] != dims[0]: raise ValueError("Model shape (dims=%s) is not consistent with chosen " "number of windows. Choose dims[0]=%d for the " "operator to work with estimated number of windows, " "or create the operator with design=True to find " "out the optimal number of windows for the current " "model size..." % (str(dims), nwins * Op.shape[1] // dims[1])) # transform to apply if tapertype is None: OOp = BlockDiag([Op for _ in range(nwins)]) else: OOp = BlockDiag( [Diagonal(taps[itap].ravel()) * Op for itap in range(nwins)]) combining = HStack([ Restriction(np.prod(dimsd), range(win_in, win_end), dims=dimsd, dtype=Op.dtype).H for win_in, win_end in zip(dwin_ins, dwin_ends) ]) Sop = combining * OOp return Sop
def Patch2D(Op, dims, dimsd, nwin, nover, nop, tapertype="hanning", design=False): """2D Patch transform operator. Apply a transform operator ``Op`` repeatedly to patches of the model vector in forward mode and patches of the data vector in adjoint mode. More specifically, in forward mode the model vector is divided into patches, each patch is transformed, and patches are then recombined together. Both model and data are internally reshaped and interpreted as 2-dimensional arrays: each patch contains a portion of the array in both the first and second dimension. This operator can be used to perform local, overlapping transforms (e.g., :obj:`pylops.signalprocessing.FFT2D` or :obj:`pylops.signalprocessing.Radon2D`) on 2-dimensional arrays. .. note:: The shape of the model has to be consistent with the number of windows for this operator not to return an error. As the number of windows depends directly on the choice of ``nwin`` and ``nover``, it is recommended to use ``design=True`` if unsure about the choice ``dims`` and use the number of windows printed on screen to define such input parameter. .. warning:: Depending on the choice of `nwin` and `nover` as well as the size of the data, patches may not cover the entire size of the data. The start and end indices of each window can be displayed using ``design=True`` while defining the best patching approach. Parameters ---------- Op : :obj:`pylops.LinearOperator` Transform operator dims : :obj:`tuple` Shape of 2-dimensional model. Note that ``dims[0]`` and ``dims[1]`` should be multiple of the model size of the transform in their respective dimensions dimsd : :obj:`tuple` Shape of 2-dimensional data nwin : :obj:`tuple` Number of samples of window nover : :obj:`tuple` Number of samples of overlapping part of window nop : :obj:`tuple` Size of model in the transformed domain tapertype : :obj:`str`, optional Type of taper (``hanning``, ``cosine``, ``cosinesquare`` or ``None``) design : :obj:`bool`, optional Print number of sliding window (``True``) or not (``False``) Returns ------- Sop : :obj:`pylops.LinearOperator` Sliding operator Raises ------ ValueError Identified number of windows is not consistent with provided model shape (``dims``). See Also -------- Sliding2d: 2D Sliding transform operator. """ # model windows mwin0_ins, mwin0_ends = _slidingsteps(dims[0], nop[0], 0) mwin1_ins, mwin1_ends = _slidingsteps(dims[1], nop[1], 0) # data windows dwin0_ins, dwin0_ends = _slidingsteps(dimsd[0], nwin[0], nover[0]) dwin1_ins, dwin1_ends = _slidingsteps(dimsd[1], nwin[1], nover[1]) nwins0 = len(dwin0_ins) nwins1 = len(dwin1_ins) nwins = nwins0 * nwins1 # create tapers if tapertype is not None: tap = taper2d(nwin[1], nwin[0], nover, tapertype=tapertype).astype(Op.dtype) taps = {itap: tap for itap in range(nwins)} # topmost tapers taptop = tap.copy() taptop[:nover[0]] = tap[nwin[0] // 2] for itap in range(0, nwins1): taps[itap] = taptop # bottommost tapers tapbottom = tap.copy() tapbottom[-nover[0]:] = tap[nwin[0] // 2] for itap in range(nwins - nwins1, nwins): taps[itap] = tapbottom # leftmost tapers tapleft = tap.copy() tapleft[:, :nover[1]] = tap[:, nwin[1] // 2][:, np.newaxis] for itap in range(0, nwins, nwins1): taps[itap] = tapleft # rightmost tapers tapright = tap.copy() tapright[:, -nover[1]:] = tap[:, nwin[1] // 2][:, np.newaxis] for itap in range(nwins1 - 1, nwins, nwins1): taps[itap] = tapright # lefttopcorner taper taplefttop = tap.copy() taplefttop[:, :nover[1]] = tap[:, nwin[1] // 2][:, np.newaxis] taplefttop[:nover[0]] = taplefttop[nwin[0] // 2] taps[0] = taplefttop # righttopcorner taper taprighttop = tap.copy() taprighttop[:, -nover[1]:] = tap[:, nwin[1] // 2][:, np.newaxis] taprighttop[:nover[0]] = taprighttop[nwin[0] // 2] taps[nwins1 - 1] = taprighttop # leftbottomcorner taper tapleftbottom = tap.copy() tapleftbottom[:, :nover[1]] = tap[:, nwin[1] // 2][:, np.newaxis] tapleftbottom[-nover[0]:] = tapleftbottom[nwin[0] // 2] taps[nwins - nwins1] = tapleftbottom # rightbottomcorner taper taprightbottom = tap.copy() taprightbottom[:, -nover[1]:] = tap[:, nwin[1] // 2][:, np.newaxis] taprightbottom[-nover[0]:] = taprightbottom[nwin[0] // 2] taps[nwins - 1] = taprightbottom # check that identified number of windows agrees with mode size if design: logging.warning("%d-%d windows required...", nwins0, nwins1) logging.warning( "model wins - start:%s, end:%s / start:%s, end:%s", str(mwin0_ins), str(mwin0_ends), str(mwin1_ins), str(mwin1_ends), ) logging.warning( "data wins - start:%s, end:%s / start:%s, end:%s", str(dwin0_ins), str(dwin0_ends), str(dwin1_ins), str(dwin1_ends), ) if nwins0 * nop[0] != dims[0] or nwins1 * nop[1] != dims[1]: raise ValueError("Model shape (dims=%s) is not consistent with chosen " "number of windows. Choose dims[0]=%d and " "dims[1]=%d for the operator to work with " "estimated number of windows, or create " "the operator with design=True to find out the" "optimal number of windows for the current " "model size..." % (str(dims), nwins0 * nop[0], nwins1 * nop[1])) # transform to apply if tapertype is None: OOp = BlockDiag([Op for _ in range(nwins)]) else: OOp = BlockDiag([ Diagonal(taps[itap].ravel(), dtype=Op.dtype) * Op for itap in range(nwins) ]) hstack = HStack([ Restriction( dimsd[1] * nwin[0], range(win_in, win_end), dims=(nwin[0], dimsd[1]), dir=1, dtype=Op.dtype, ).H for win_in, win_end in zip(dwin1_ins, dwin1_ends) ]) combining1 = BlockDiag([hstack] * nwins0) combining0 = HStack([ Restriction( np.prod(dimsd), range(win_in, win_end), dims=dimsd, dir=0, dtype=Op.dtype, ).H for win_in, win_end in zip(dwin0_ins, dwin0_ends) ]) Pop = combining0 * combining1 * OOp return Pop