def test_sincinterp(): """Check accuracy of sinc interpolation of subsampled version of input signal """ nt = 81 dt = 0.004 t = np.arange(nt) * dt ntsub = 10 dtsub = dt / ntsub tsub = np.arange(nt * ntsub) * dtsub tsub = tsub[:np.where(tsub == t[-1])[0][0] + 1] x = np.sin(2 * np.pi * 10 * t) + \ 0.4 * np.sin(2 * np.pi * 20 * t) - \ 2 * np.sin(2 * np.pi * 5 * t) xsub = np.sin(2 * np.pi * 10 * tsub) + \ 0.4 * np.sin(2 * np.pi * 20 * tsub) - \ 2 * np.sin(2 * np.pi * 5 * tsub) iava = tsub[20:-20] / (dtsub * ntsub) # exclude edges SI1op, iava = Interp(nt, iava, kind='sinc', dtype='float64') y = SI1op * x print(np.max(np.abs(xsub[20:-20] - y))) assert_array_almost_equal(xsub[20:-20], y, decimal=1)
def test_Interp_1dsignal(par): """Dot-test and forward for Interp operator for 1d signal """ np.random.seed(1) x = np.random.normal(0, 1, par['nx']) + \ par['imag'] * np.random.normal(0, 1, par['nx']) Nsub = int(np.round(par['nx'] * perc_subsampling)) iava = np.sort(np.random.permutation(np.arange(par['nx']))[:Nsub]) # fixed indeces Iop, _ = Interp(par['nx'], iava, kind=par['kind'], dtype=par['dtype']) assert dottest(Iop, Nsub, par['nx'], complexflag=0 if par['imag'] == 0 else 3) # decimal indeces Idecop, _ = Interp(par['nx'], iava + 0.3, kind=par['kind'], dtype=par['dtype']) assert dottest(Iop, Nsub, par['nx'], complexflag=0 if par['imag'] == 0 else 3) # repeated indeces with pytest.raises(ValueError): iava_rep = iava.copy() iava_rep[-2] = 0 iava_rep[-1] = 0 _, _ = Interp(par['nx'], iava_rep + 0.3, kind=par['kind'], dtype=par['dtype']) # forward y = Iop * x ydec = Idecop * x assert_array_almost_equal(y, x[iava]) if par['kind'] == 'nearest': assert_array_almost_equal(ydec, x[iava])
def SeismicInterpolation(data, nrec, iava, iava1=None, kind='fk', nffts=None, sampling=None, spataxis=None, spat1axis=None, taxis=None, paxis=None, p1axis=None, centeredh=True, nwins=None, nwin=None, nover=None, design=False, engine='numba', dottest=False, **kwargs_solver): r"""Seismic interpolation (or regularization). Interpolate seismic data from irregular to regular spatial grid. Depending on the size of the input ``data``, interpolation is either 2- or 3-dimensional. In case of 3-dimensional interpolation, data can be irregularly sampled in either one or both spatial directions. Parameters ---------- data : :obj:`np.ndarray` Irregularly sampled seismic data of size :math:`[n_{r_y} (\times n_{r_x} \times n_t)]` nrec : :obj:`int` or :obj:`tuple` Number of elements in the regularly sampled (reconstructed) spatial array, :math:`n_{R_y}` for 2-dimensional data and :math:`(n_{R_y}, n_{R_x})` for 3-dimensional data iava : :obj:`list` or :obj:`numpy.ndarray` Integer (or floating) indices of locations of available samples in first dimension of regularly sampled spatial grid of interpolated signal. The :class:`pylops.basicoperators.Restriction` operator is used in case of integer indices, while the :class:`pylops.signalprocessing.Iterp` operator is used in case of floating indices. iava1 : :obj:`list` or :obj:`numpy.ndarray`, optional Integer (or floating) indices of locations of available samples in second dimension of regularly sampled spatial grid of interpolated signal. Can be used only in case of 3-dimensional data. kind : :obj:`str`, optional Type of inversion: ``fk`` (default), ``spatial``, ``radon-linear``, ``chirpradon-linear``, ``radon-parabolic`` or , ``radon-hyperbolic`` and ``sliding`` nffts : :obj:`int` or :obj:`tuple`, optional nffts : :obj:`tuple`, optional Number of samples in Fourier Transform for each direction. Required if ``kind='fk'`` sampling : :obj:`tuple`, optional Sampling steps ``dy`` (, ``dx``) and ``dt``. Required if ``kind='fk'`` or ``kind='radon-linear'`` spataxis : :obj:`np.ndarray`, optional First spatial axis. Required for ``kind='radon-linear'``, ``kind='chirpradon-linear'``, ``kind='radon-parabolic'``, ``kind='radon-hyperbolic'``, can also be provided instead of ``sampling`` for ``kind='fk'`` spat1axis : :obj:`np.ndarray`, optional Second spatial axis. Required for ``kind='radon-linear'``, ``kind='chirpradon-linear'``, ``kind='radon-parabolic'``, ``kind='radon-hyperbolic'``, can also be provided instead of ``sampling`` for ``kind='fk'`` taxis : :obj:`np.ndarray`, optional Time axis. Required for ``kind='radon-linear'``, ``kind='chirpradon-linear'``, ``kind='radon-parabolic'``, ``kind='radon-hyperbolic'``, can also be provided instead of ``sampling`` for ``kind='fk'`` paxis : :obj:`np.ndarray`, optional First Radon axis. Required for ``kind='radon-linear'``, ``kind='chirpradon-linear'``, ``kind='radon-parabolic'``, ``kind='radon-hyperbolic'`` and ``kind='sliding'`` p1axis : :obj:`np.ndarray`, optional Second Radon axis. Required for ``kind='radon-linear'``, ``kind='chirpradon-linear'``, ``kind='radon-parabolic'``, ``kind='radon-hyperbolic'`` and ``kind='sliding'`` centeredh : :obj:`bool`, optional Assume centered spatial axis (``True``) or not (``False``). Required for ``kind='radon-linear'``, ``kind='radon-parabolic'`` and ``kind='radon-hyperbolic'`` nwins : :obj:`int` or :obj:`tuple`, optional Number of windows. Required for ``kind='sliding'`` nwin : :obj:`int` or :obj:`tuple`, optional Number of samples of window. Required for ``kind='sliding'`` nover : :obj:`int` or :obj:`tuple`, optional Number of samples of overlapping part of window. Required for ``kind='sliding'`` design : :obj:`bool`, optional Print number of sliding window (``True``) or not (``False``) when using ``kind='sliding'`` engine : :obj:`str`, optional Engine used for Radon computations (``numpy/numba`` for ``Radon2D`` and ``Radon3D`` or ``numpy/fftw`` for ``ChirpRadon2D`` and ``ChirpRadon3D`` or ) dottest : :obj:`bool`, optional Apply dot-test **kwargs_solver Arbitrary keyword arguments for :py:func:`pylops.optimization.leastsquares.RegularizedInversion` solver if ``kind='spatial'`` or :py:func:`pylops.optimization.sparsity.FISTA` solver otherwise Returns ------- recdata : :obj:`np.ndarray` Reconstructed data of size :math:`[n_{R_y} (\times n_{R_x} \times n_t)]` recprec : :obj:`np.ndarray` Reconstructed data in the sparse or preconditioned domain in case of ``kind='fk'``, ``kind='radon-linear'``, ``kind='radon-parabolic'``, ``kind='radon-hyperbolic'`` and ``kind='sliding'`` cost : :obj:`np.ndarray` Cost function norm Raises ------ KeyError If ``kind`` is neither ``spatial``, ``fl``, ``radon-linear``, ``radon-parabolic``, ``radon-hyperbolic`` nor ``sliding`` Notes ----- The problem of seismic data interpolation (or regularization) can be formally written as .. math:: \mathbf{y} = \mathbf{R} \mathbf{x} where a restriction or interpolation operator is applied along the spatial direction(s). Here :math:`\mathbf{y} = [\mathbf{y}_{R1}^T, \mathbf{y}_{R2}^T,..., \mathbf{y}_{RN^T}]^T` where each vector :math:`\mathbf{y}_{Ri}` contains all time samples recorded in the seismic data at the specific receiver :math:`R_i`. Similarly, :math:`\mathbf{x} = [\mathbf{x}_{r1}^T, \mathbf{x}_{r2}^T,..., \mathbf{x}_{rM}^T]`, contains all traces at the regularly and finely sampled receiver locations :math:`r_i`. Several alternative approaches can be taken to solve such a problem. They mostly differ in the choice of the regularization (or preconditining) used to mitigate the ill-posedness of the problem: * ``spatial``: least-squares inversion in the original time-space domain with an additional spatial smoothing regularization term, corresponding to the cost function :math:`J = ||\mathbf{y} - \mathbf{R} \mathbf{x}||_2 + \epsilon_\nabla \nabla ||\mathbf{x}||_2` where :math:`\nabla` is a second order space derivative implemented via :class:`pylops.basicoperators.SecondDerivative` in 2-dimensional case and :class:`pylops.basicoperators.Laplacian` in 3-dimensional case * ``fk``: L1 inversion in frequency-wavenumber preconditioned domain corresponding to the cost function :math:`J = ||\mathbf{y} - \mathbf{R} \mathbf{F} \mathbf{x}||_2` where :math:`\mathbf{F}` is frequency-wavenumber transform implemented via :class:`pylops.signalprocessing.FFT2D` in 2-dimensional case and :class:`pylops.signalprocessing.FFTND` in 3-dimensional case * ``radon-linear``: L1 inversion in linear Radon preconditioned domain using the same cost function as ``fk`` but with :math:`\mathbf{F}` being a Radon transform implemented via :class:`pylops.signalprocessing.Radon2D` in 2-dimensional case and :class:`pylops.signalprocessing.Radon3D` in 3-dimensional case * ``radon-parabolic``: L1 inversion in parabolic Radon preconditioned domain * ``radon-hyperbolic``: L1 inversion in hyperbolic Radon preconditioned domain * ``sliding``: L1 inversion in sliding-linear Radon preconditioned domain using the same cost function as ``fk`` but with :math:`\mathbf{F}` being a sliding Radon transform implemented via :class:`pylops.signalprocessing.Sliding2D` in 2-dimensional case and :class:`pylops.signalprocessing.Sliding3D` in 3-dimensional case """ ncp = get_array_module(data) dtype = data.dtype ndims = data.ndim if ndims == 1 or ndims > 3: raise ValueError('data must have 2 or 3 dimensions') if ndims == 2: dimsd = data.shape dims = (nrec, dimsd[1]) else: dimsd = data.shape dims = (nrec[0], nrec[1], dimsd[2]) # sampling if taxis is not None: dt = taxis[1] - taxis[0] if spataxis is not None: dspat = np.abs(spataxis[1] - spataxis[0]) if spat1axis is not None: dspat1 = np.abs(spat1axis[1] - spat1axis[0]) # create restriction/interpolation operator if iava.dtype == float: Rop = Interp(np.prod(dims), iava, dims=dims, dir=0, kind='linear', dtype=dtype) if ndims == 3 and iava1 is not None: dims1 = (len(iava), nrec[1], dimsd[2]) Rop1 = Interp(np.prod(dims1), iava1, dims=dims1, dir=1, kind='linear', dtype=dtype) Rop = Rop1 * Rop else: Rop = Restriction(np.prod(dims), iava, dims=dims, dir=0, dtype=dtype) if ndims == 3 and iava1 is not None: dims1 = (len(iava), nrec[1], dimsd[2]) Rop1 = Restriction(np.prod(dims1), iava1, dims=dims1, dir=1, dtype=dtype) Rop = Rop1 * Rop # create other operators for inversion if kind == 'spatial': prec = False dotcflag = 0 if ndims == 3 and iava1 is not None: Regop = Laplacian(dims=dims, dirs=(0, 1), dtype=dtype) else: Regop = SecondDerivative(np.prod(dims), dims=(dims), dir=0, dtype=dtype) SIop = Rop elif kind == 'fk': prec = True dimsp = nffts dotcflag = 1 if ndims == 3: if sampling is None: if spataxis is None or spat1axis is None or taxis is None: raise ValueError('Provide either sampling or spataxis, ' 'spat1axis and taxis for kind=%s' % kind) else: sampling = (np.abs(spataxis[1] - spataxis[1]), np.abs(spat1axis[1] - spat1axis[1]), np.abs(taxis[1] - taxis[1])) Pop = FFTND(dims=dims, nffts=nffts, sampling=sampling) Pop = Pop.H else: if sampling is None: if spataxis is None or taxis is None: raise ValueError('Provide either sampling or spataxis, ' 'and taxis for kind=%s' % kind) else: sampling = (np.abs(spataxis[1] - spataxis[1]), np.abs(taxis[1] - taxis[1])) Pop = FFT2D(dims=dims, nffts=nffts, sampling=sampling) Pop = Pop.H SIop = Rop * Pop elif 'chirpradon' in kind: prec = True dotcflag = 0 if ndims == 3: Pop = ChirpRadon3D( taxis, spataxis, spat1axis, (np.max(paxis) * dspat / dt, np.max(p1axis) * dspat1 / dt)).H dimsp = (spataxis.size, spat1axis.size, taxis.size) else: Pop = ChirpRadon2D(taxis, spataxis, np.max(paxis) * dspat / dt).H dimsp = (spataxis.size, taxis.size) SIop = Rop * Pop elif 'radon' in kind: prec = True dotcflag = 0 kindradon = kind.split('-')[-1] if ndims == 3: Pop = Radon3D(taxis, spataxis, spat1axis, paxis, p1axis, centeredh=centeredh, kind=kindradon, engine=engine) dimsp = (paxis.size, p1axis.size, taxis.size) else: Pop = Radon2D(taxis, spataxis, paxis, centeredh=centeredh, kind=kindradon, engine=engine) dimsp = (paxis.size, taxis.size) SIop = Rop * Pop elif kind == 'sliding': prec = True dotcflag = 0 if ndims == 3: nspat, nspat1 = spataxis.size, spat1axis.size spataxis_local = np.linspace(-dspat * nwin[0] // 2, dspat * nwin[0] // 2, nwin[0]) spat1axis_local = np.linspace(-dspat1 * nwin[1] // 2, dspat1 * nwin[1] // 2, nwin[1]) dimsslid = (nspat, nspat1, taxis.size) if ncp == np: npaxis, np1axis = paxis.size, p1axis.size Op = Radon3D(taxis, spataxis_local, spat1axis_local, paxis, p1axis, centeredh=True, kind='linear', engine=engine) else: npaxis, np1axis = nwin[0], nwin[1] Op = ChirpRadon3D(taxis, spataxis_local, spat1axis_local, (np.max(paxis) * dspat / dt, np.max(p1axis) * dspat1 / dt)).H dimsp = (nwins[0] * npaxis, nwins[1] * np1axis, dimsslid[2]) Pop = Sliding3D(Op, dimsp, dimsslid, nwin, nover, (npaxis, np1axis), tapertype='cosine') # to be able to reshape correctly the preconditioned model dimsp = (nwins[0], nwins[1], npaxis, np1axis, dimsslid[2]) else: nspat = spataxis.size spataxis_local = np.linspace(-dspat * nwin // 2, dspat * nwin // 2, nwin) dimsslid = (nspat, taxis.size) if ncp == np: npaxis = paxis.size Op = Radon2D(taxis, spataxis_local, paxis, centeredh=True, kind='linear', engine=engine) else: npaxis = nwin Op = ChirpRadon2D(taxis, spataxis_local, np.max(paxis) * dspat / dt).H dimsp = (nwins * npaxis, dimsslid[1]) Pop = Sliding2D(Op, dimsp, dimsslid, nwin, nover, tapertype='cosine', design=design) SIop = Rop * Pop else: raise KeyError('kind must be spatial, fk, radon-linear, ' 'radon-parabolic, radon-hyperbolic or sliding') # dot-test if dottest: Dottest(SIop, np.prod(dimsd), np.prod(dimsp) if prec else np.prod(dims), complexflag=dotcflag, raiseerror=True, verb=True) # inversion if kind == 'spatial': recdata = \ RegularizedInversion(SIop, [Regop], data.flatten(), **kwargs_solver) if isinstance(recdata, tuple): recdata = recdata[0] recdata = recdata.reshape(dims) recprec = None cost = None else: recprec = FISTA(SIop, data.flatten(), **kwargs_solver) if len(recprec) == 3: cost = recprec[2] else: cost = None recprec = recprec[0] recdata = np.real(Pop * recprec) recprec = recprec.reshape(dimsp) recdata = recdata.reshape(dims) return recdata, recprec, cost
def test_Interp_3dsignal(par): """Dot-test and forward for Interp operator for 3d signal """ np.random.seed(1) x = np.random.normal(0, 1, (par['ny'], par['nx'], par['nt'])) + \ par['imag'] * np.random.normal(0, 1, (par['ny'], par['nx'], par['nt'])) # 1st direction Nsub = int(np.round(par['ny'] * perc_subsampling)) iava = np.sort(np.random.permutation(np.arange(par['ny']))[:Nsub]) # fixed indeces Iop, _ = Interp(par['ny'] * par['nx'] * par['nt'], iava, dims=(par['ny'], par['nx'], par['nt']), dir=0, kind=par['kind'], dtype=par['dtype']) assert dottest(Iop, Nsub * par['nx'] * par['nt'], par['ny'] * par['nx'] * par['nt'], complexflag=0 if par['imag'] == 0 else 3) # decimal indeces Idecop, _ = Interp(par['ny'] * par['nx'] * par['nt'], iava + 0.3, dims=(par['ny'], par['nx'], par['nt']), dir=0, kind=par['kind'], dtype=par['dtype']) assert dottest(Idecop, Nsub * par['nx'] * par['nt'], par['ny'] * par['nx'] * par['nt'], complexflag=0 if par['imag'] == 0 else 3) # repeated indeces with pytest.raises(ValueError): iava_rep = iava.copy() iava_rep[-2] = 0 iava_rep[-1] = 0 _, _ = Interp(par['ny'] * par['nx'] * par['nt'], iava_rep + 0.3, dims=(par['ny'], par['nx'], par['nt']), dir=0, kind=par['kind'], dtype=par['dtype']) y = (Iop * x.ravel()).reshape(Nsub, par['nx'], par['nt']) ydec = (Idecop * x.ravel()).reshape(Nsub, par['nx'], par['nt']) assert_array_almost_equal(y, x[iava]) if par['kind'] == 'nearest': assert_array_almost_equal(ydec, x[iava]) # 2nd direction Nsub = int(np.round(par['nx'] * perc_subsampling)) iava = np.sort(np.random.permutation(np.arange(par['nx']))[:Nsub]) # fixed indeces Iop, _ = Interp(par['ny'] * par['nx'] * par['nt'], iava, dims=(par['ny'], par['nx'], par['nt']), dir=1, kind=par['kind'], dtype=par['dtype']) assert dottest(Iop, par['ny'] * Nsub * par['nt'], par['ny'] * par['nx'] * par['nt'], complexflag=0 if par['imag'] == 0 else 3) # decimal indeces Idecop, _ = Interp(par['ny'] * par['nx'] * par['nt'], iava + 0.3, dims=(par['ny'], par['nx'], par['nt']), dir=1, kind=par['kind'], dtype=par['dtype']) assert dottest(Idecop, par['ny'] * Nsub * par['nt'], par['ny'] * par['nx'] * par['nt'], complexflag=0 if par['imag'] == 0 else 3) y = (Iop * x.ravel()).reshape(par['ny'], Nsub, par['nt']) ydec = (Idecop * x.ravel()).reshape(par['ny'], Nsub, par['nt']) assert_array_almost_equal(y, x[:, iava]) if par['kind'] == 'nearest': assert_array_almost_equal(ydec, x[:, iava]) # 3rd direction Nsub = int(np.round(par['nt'] * perc_subsampling)) iava = np.sort(np.random.permutation(np.arange(par['nt']))[:Nsub]) # fixed indeces Iop, _ = Interp(par['ny'] * par['nx'] * par['nt'], iava, dims=(par['ny'], par['nx'], par['nt']), dir=2, kind=par['kind'], dtype=par['dtype']) assert dottest(Iop, par['ny'] * par['nx'] * Nsub, par['ny'] * par['nx'] * par['nt'], complexflag=0 if par['imag'] == 0 else 3) # decimal indeces Idecop, _ = Interp(par['ny'] * par['nx'] * par['nt'], iava + 0.3, dims=(par['ny'], par['nx'], par['nt']), dir=2, kind=par['kind'], dtype=par['dtype']) assert dottest(Idecop, par['ny'] * par['nx'] * Nsub, par['ny'] * par['nx'] * par['nt'], complexflag=0 if par['imag'] == 0 else 3) y = (Iop * x.ravel()).reshape(par['ny'], par['nx'], Nsub) ydec = (Idecop * x.ravel()).reshape(par['ny'], par['nx'], Nsub) assert_array_almost_equal(y, x[:, :, iava]) if par['kind'] == 'nearest': assert_array_almost_equal(ydec, x[:, :, iava])
def test_Interp_2dsignal(par): """Dot-test and forward for Restriction operator for 2d signal""" np.random.seed(1) x = np.random.normal( 0, 1, (par["nx"], par["nt"])) + par["imag"] * np.random.normal(0, 1, (par["nx"], par["nt"])) # 1st direction Nsub = int(np.round(par["nx"] * perc_subsampling)) iava = np.sort(np.random.permutation(np.arange(par["nx"]))[:Nsub]) # fixed indeces Iop, _ = Interp( par["nx"] * par["nt"], iava, dims=(par["nx"], par["nt"]), dir=0, kind=par["kind"], dtype=par["dtype"], ) assert dottest( Iop, Nsub * par["nt"], par["nx"] * par["nt"], complexflag=0 if par["imag"] == 0 else 3, ) # decimal indeces Idecop, _ = Interp( par["nx"] * par["nt"], iava + 0.3, dims=(par["nx"], par["nt"]), dir=0, kind=par["kind"], dtype=par["dtype"], ) # repeated indeces with pytest.raises(ValueError): iava_rep = iava.copy() iava_rep[-2] = 0 iava_rep[-1] = 0 _, _ = Interp( par["nx"] * par["nt"], iava_rep + 0.3, dims=(par["nx"], par["nt"]), dir=0, kind=par["kind"], dtype=par["dtype"], ) y = (Iop * x.ravel()).reshape(Nsub, par["nt"]) ydec = (Idecop * x.ravel()).reshape(Nsub, par["nt"]) assert_array_almost_equal(y, x[iava]) if par["kind"] == "nearest": assert_array_almost_equal(ydec, x[iava]) # 2nd direction Nsub = int(np.round(par["nt"] * perc_subsampling)) iava = np.sort(np.random.permutation(np.arange(par["nt"]))[:Nsub]) # fixed indeces Iop, _ = Interp( par["nx"] * par["nt"], iava, dims=(par["nx"], par["nt"]), dir=1, kind=par["kind"], dtype=par["dtype"], ) assert dottest( Iop, par["nx"] * Nsub, par["nx"] * par["nt"], complexflag=0 if par["imag"] == 0 else 3, ) # decimal indeces Idecop, _ = Interp( par["nx"] * par["nt"], iava + 0.3, dims=(par["nx"], par["nt"]), dir=1, kind=par["kind"], dtype=par["dtype"], ) assert dottest( Idecop, par["nx"] * Nsub, par["nx"] * par["nt"], complexflag=0 if par["imag"] == 0 else 3, ) y = (Iop * x.ravel()).reshape(par["nx"], Nsub) ydec = (Idecop * x.ravel()).reshape(par["nx"], Nsub) assert_array_almost_equal(y, x[:, iava]) if par["kind"] == "nearest": assert_array_almost_equal(ydec, x[:, iava])