Exemple #1
0
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"]))
Exemple #2
0
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
Exemple #3
0
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
Exemple #4
0
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