def test_taper3d(par): """Create taper wavelet and check size and values""" tap = taper3d(par["nt"], par["nspat"], par["ntap"], par["tapertype"]) assert tap.shape == (par["nspat"][0], par["nspat"][1], par["nt"]) assert_array_equal(tap[0][0], np.zeros(par["nt"])) assert_array_equal(tap[-1][-1], np.zeros(par["nt"])) assert_array_equal(tap[par["ntap"][0], par["ntap"][1]], np.ones(par["nt"])) assert_array_equal(tap[par["nspat"][0] // 2, par["nspat"][1] // 2], np.ones(par["nt"]))
def Sliding3D(Op, dims, dimsd, nwin, nover, nop, tapertype='hanning', design=False, nproc=1): """3D Sliding 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 in a sliding window fashion. Both model and data should be 3-dimensional arrays in nature as they are internally reshaped and interpreted as 3-dimensional arrays. Each patch contains in fact a portion of the array in the first and second dimensions (and the entire third dimension). This operator can be used to perform local, overlapping transforms (e.g., :obj:`pylops.signalprocessing.FFTND` or :obj:`pylops.signalprocessing.Radon3D`) of 3-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 and/or second dimensions. The start and end indeces 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 3-dimensional model. Note that ``dims[0]`` and ``dims[1]`` should be multiple of the model sizes of the transform in the first and second dimensions dimsd : :obj:`tuple` Shape of 3-dimensional data nwin : :obj:`tuple` Number of samples of window nover : :obj:`tuple` Number of samples of overlapping part of window nop : :obj:`tuple` Number of samples in axes of transformed domain associated to spatial axes in the data tapertype : :obj:`str`, optional Type of taper (``hanning``, ``cosine``, ``cosinesquare`` or ``None``) design : :obj:`bool`, optional Print number 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 mwin0_ins, mwin0_ends = _slidingsteps(dims[0], Op.shape[1]//(nop[1]*dims[2]), 0) mwin1_ins, mwin1_ends = _slidingsteps(dims[1], Op.shape[1]//(nop[0]*dims[2]), 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 = taper3d(dimsd[2], nwin, nover, tapertype=tapertype) # check that identified number of windows agrees with mode size if design: logging.warning('(%d,%d) windows required...', nwins0, nwins1) logging.warning('model wins - start0:%s, end0:%s, start1:%s, end1:%s', str(mwin0_ins), str(mwin0_ends), str(mwin1_ins), str(mwin1_ends)) logging.warning('data wins - start0:%s, end0:%s, start1:%s, end1:%s', str(dwin0_ins), str(dwin0_ends), str(dwin1_ins), str(dwin1_ends)) if nwins*Op.shape[1]//dims[2] != dims[0]*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*Op.shape[1]//(nop[1]*dims[2]), nwins1 * Op.shape[1]//(nop[0]*dims[2]))) # transform to apply if tapertype is None: OOp = BlockDiag([Op for _ in range(nwins)], nproc=nproc) else: OOp = BlockDiag([Diagonal(tap.flatten()) * Op for _ in range(nwins)], nproc=nproc) hstack = HStack([Restriction(dimsd[1] * dimsd[2] * nwin[0], range(win_in, win_end), dims=(nwin[0], dimsd[1], dimsd[2]), dir=1).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).H for win_in, win_end in zip(dwin0_ins, dwin0_ends)]) Sop = combining0 * combining1 * OOp return Sop
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
"dt": 0.004, "nt": 300, "f0": 20, "nfmax": 200, } t0_m = [0.2] vrms_m = [700.0] amp_m = [1.0] t0_G = [0.2, 0.5, 0.7] vrms_G = [800.0, 1200.0, 1500.0] amp_G = [1.0, 0.6, 0.5] # Taper tap = taper3d(par["nt"], [par["ny"], par["nx"]], (5, 5), tapertype="hanning") # Create axis t, t2, x, y = makeaxis(par) # Create wavelet wav = ricker(t[:41], f0=par["f0"])[0] # Generate model m, mwav = hyperbolic2d(x, t, t0_m, vrms_m, amp_m, wav) # Generate operator G, Gwav = np.zeros((par["ny"], par["nx"], par["nt"])), np.zeros( (par["ny"], par["nx"], par["nt"])) for iy, y0 in enumerate(y): G[iy], Gwav[iy] = hyperbolic2d(x - y0, t, t0_G, vrms_G, amp_G, wav)
'dt': 0.004, 'nt': 300, 'f0': 20, 'nfmax': 200 } t0_m = [0.2] vrms_m = [700.] amp_m = [1.] t0_G = [0.2, 0.5, 0.7] vrms_G = [800., 1200., 1500.] amp_G = [1., 0.6, 0.5] # Taper tap = taper3d(par['nt'], [par['ny'], par['nx']], (5, 5), tapertype='hanning') # Create axis t, t2, x, y = makeaxis(par) # Create wavelet wav = ricker(t[:41], f0=par['f0'])[0] # Generate model m, mwav = hyperbolic2d(x, t, t0_m, vrms_m, amp_m, wav) # Generate operator G, Gwav = np.zeros((par['ny'], par['nx'], par['nt'])), \ np.zeros((par['ny'], par['nx'], par['nt'])) for iy, y0 in enumerate(y): G[iy], Gwav[iy] = hyperbolic2d(x - y0, t, t0_G, vrms_G, amp_G, wav)