From 5e84d36d39f54880c4eae0016ff6ee3062ce534d Mon Sep 17 00:00:00 2001 From: mrava87 Date: Fri, 31 Jul 2026 22:19:15 +0100 Subject: [PATCH 1/3] feat: pad_to next methods --- pylops/signalprocessing/patch2d.py | 62 +++++++++++++++++++++++++++ pylops/signalprocessing/patch3d.py | 64 ++++++++++++++++++++++++++++ pylops/signalprocessing/sliding1d.py | 64 +++++++++++++++++++++++++--- pylops/signalprocessing/sliding2d.py | 56 ++++++++++++++++++++++++ pylops/signalprocessing/sliding3d.py | 64 +++++++++++++++++++++++++++- pytests/test_patching.py | 52 +++++++++++++++++++++- pytests/test_sliding.py | 62 +++++++++++++++++++++++++-- 7 files changed, 413 insertions(+), 11 deletions(-) diff --git a/pylops/signalprocessing/patch2d.py b/pylops/signalprocessing/patch2d.py index 8f36769f..f93a870b 100644 --- a/pylops/signalprocessing/patch2d.py +++ b/pylops/signalprocessing/patch2d.py @@ -1,5 +1,6 @@ __all__ = [ "patch2d_design", + "patch2d_pad_to_next", "Patch2D", ] @@ -101,6 +102,67 @@ def patch2d_design( return nwins, dims, mwins_inends, dwins_inends +def patch2d_pad_to_next( + inpt: NDArray, + nwin: tuple[int, int], + nover: tuple[int, int], + nop: tuple[int, int], +) -> tuple[ + NDArray, + tuple[int, int], + tuple[int, int], + tuple[tuple[NDArray, NDArray], tuple[NDArray, NDArray]], + tuple[tuple[NDArray, NDArray], tuple[NDArray, NDArray]], +]: + """Pad input to next set of patches + + Pad ``inpt`` to the next set of patches such that the padded input is completely + filled by overlapping patches. + + Parameters + ---------- + inpt : :obj:`numpy.ndarray` + 3-dimensional input 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. + + Returns + ------- + inpt_pad : :obj:`numpy.ndarray` + 3-dimensional input data after padding along the first and second dimensions + nwins : :obj:`tuple` + Number of windows. + dims : :obj:`tuple` + Shape of 2-dimensional model. + mwins_inends : :obj:`tuple` + Start and end indices for model patches (stored as tuple of tuples). + dwins_inends : :obj:`tuple` + Start and end indices for data patches (stored as tuple of tuples). + + """ + # Identify current sliding design + dimsd = inpt.shape + nwins, dims, mwins_inends, dwins_inends = patch2d_design(dimsd, nwin, nover, nop) + + # Pad to next slice + pad = [0, 0] + if dwins_inends[0][1][-1] != dimsd[0]: + pad[0] = dwins_inends[0][1][-1] - nover[0] + nwin[0] - dimsd[0] + if dwins_inends[1][1][-1] != dimsd[1]: + pad[1] = dwins_inends[1][1][-1] - nover[1] + nwin[1] - dimsd[1] + inpt_pad = np.pad(inpt, ((0, pad[0]), (0, pad[1]))) + if pad[0] > 0 or pad[1] > 0: + dimsd_pad = inpt_pad.shape + nwins, dims, mwins_inends, dwins_inends = patch2d_design( + dimsd_pad, nwin, nover, nop + ) + return inpt_pad, nwins, dims, mwins_inends, dwins_inends + + class Patch2D(LinearOperator): """2D Patch transform operator. diff --git a/pylops/signalprocessing/patch3d.py b/pylops/signalprocessing/patch3d.py index 4bf27b10..3f75f24f 100644 --- a/pylops/signalprocessing/patch3d.py +++ b/pylops/signalprocessing/patch3d.py @@ -1,5 +1,6 @@ __all__ = [ "patch3d_design", + "patch3d_pad_to_next", "Patch3D", ] @@ -116,6 +117,69 @@ def patch3d_design( return nwins, dims, mwins_inends, dwins_inends +def patch3d_pad_to_next( + inpt: NDArray, + nwin: tuple[int, int, int], + nover: tuple[int, int, int], + nop: tuple[int, int, int], +) -> tuple[ + NDArray, + tuple[int, int, int], + tuple[int, int, int], + tuple[tuple[NDArray, NDArray], tuple[NDArray, NDArray], tuple[NDArray, NDArray]], + tuple[tuple[NDArray, NDArray], tuple[NDArray, NDArray], tuple[NDArray, NDArray]], +]: + """Pad input to next set of patches + + Pad ``inpt`` to the next set of patches such that the padded input is completely + filled by overlapping patches. + + Parameters + ---------- + inpt : :obj:`numpy.ndarray` + 3-dimensional input 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. + + Returns + ------- + inpt_pad : :obj:`numpy.ndarray` + 3-dimensional input data after padding along all dimensions + nwins : :obj:`tuple` + Number of windows. + dims : :obj:`tuple` + Shape of 3-dimensional model. + mwins_inends : :obj:`tuple` + Start and end indices for model patches (stored as tuple of tuples). + dwins_inends : :obj:`tuple` + Start and end indices for data patches (stored as tuple of tuples). + + """ + # Identify current sliding design + dimsd = inpt.shape + nwins, dims, mwins_inends, dwins_inends = patch3d_design(dimsd, nwin, nover, nop) + + # Pad to next slice + pad = [0, 0, 0] + if dwins_inends[0][1][-1] != dimsd[0]: + pad[0] = dwins_inends[0][1][-1] - nover[0] + nwin[0] - dimsd[0] + if dwins_inends[1][1][-1] != dimsd[1]: + pad[1] = dwins_inends[1][1][-1] - nover[1] + nwin[1] - dimsd[1] + if dwins_inends[2][1][-1] != dimsd[2]: + pad[2] = dwins_inends[2][1][-1] - nover[2] + nwin[2] - dimsd[2] + inpt_pad = np.pad(inpt, ((0, pad[0]), (0, pad[1]), (0, pad[2]))) + if pad[0] > 0 or pad[1] > 0 or pad[2] > 0: + dimsd_pad = inpt_pad.shape + nwins, dims, mwins_inends, dwins_inends = patch3d_design( + dimsd_pad, nwin, nover, nop + ) + return inpt_pad, nwins, dims, mwins_inends, dwins_inends + + class Patch3D(LinearOperator): """3D Patch transform operator. diff --git a/pylops/signalprocessing/sliding1d.py b/pylops/signalprocessing/sliding1d.py index aac6e04d..d74e9f87 100644 --- a/pylops/signalprocessing/sliding1d.py +++ b/pylops/signalprocessing/sliding1d.py @@ -1,4 +1,5 @@ __all__ = [ + "sliding1d_design", "sliding1d_design", "Sliding1D", ] @@ -38,13 +39,13 @@ def sliding1d_design( Parameters ---------- - dimsd : :obj:`tuple` - Shape of 2-dimensional data. - nwin : :obj:`tuple` + dimd : :obj:`int` + Shape of 1-dimensional data. + nwin : :obj:`int` Number of samples of window. - nover : :obj:`tuple` + nover : :obj:`int` Number of samples of overlapping part of window. - nop : :obj:`tuple` + nop : :obj:`int` Size of model in the transformed domain. verb : :obj:`bool`, optional *Deprecated*, will be removed in v3.0.0. Simply kept for @@ -87,6 +88,59 @@ def sliding1d_design( return nwins, dim, mwins_inends, dwins_inends +def sliding1d_pad_to_next( + inpt: NDArray, + nwin: int, + nover: int, + nop: int, +) -> tuple[NDArray, int, int, tuple[NDArray, NDArray], tuple[NDArray, NDArray]]: + """Pad input to next slice + + Pad ``inpt`` to the next slice such that the padded input is completely + filled by overlapping slices. + + Parameters + ---------- + inpt : :obj:`numpy.ndarray` + 1-dimensional input data. + nwin : :obj:`tuple` + Number of samples of window. + nover : :obj:`int` + Number of samples of overlapping part of window. + nop : :obj:`int` + Size of model in the transformed domain. + + Returns + ------- + inpt_pad : :obj:`numpy.ndarray` + 1-dimensional input data after padding + nwins : :obj:`int` + Number of windows of padded input. + dim : :obj:`int` + Shape of 2-dimensional model of padded input. + mwins_inends : :obj:`tuple` + Start and end indices for model patches of padded input. + dwins_inends : :obj:`tuple` + Start and end indices for data patches of padded input. + + """ + # Identify current sliding design + dimd = inpt.size + nwins, dim, mwins_inends, dwins_inends = sliding1d_design(dimd, nwin, nover, nop) + + # Pad to next slice + if dwins_inends[1][-1] != dimd: + pad = dwins_inends[1][-1] - nover + nwin - dimd + inpt_pad = np.pad(inpt, (0, pad)) + dimd_pad = inpt_pad.size + nwins, dim, mwins_inends, dwins_inends = sliding1d_design( + dimd_pad, nwin, nover, nop + ) + else: + inpt_pad = inpt + return inpt_pad, nwins, dim, mwins_inends, dwins_inends + + class Sliding1D(LinearOperator): r"""1D Sliding transform operator. diff --git a/pylops/signalprocessing/sliding2d.py b/pylops/signalprocessing/sliding2d.py index 25bfd7ba..f27b8e17 100644 --- a/pylops/signalprocessing/sliding2d.py +++ b/pylops/signalprocessing/sliding2d.py @@ -1,5 +1,6 @@ __all__ = [ "sliding2d_design", + "sliding2d_pad_to_next", "Sliding2D", ] @@ -120,6 +121,61 @@ def sliding2d_design( return nwins, dims, mwins_inends, dwins_inends +def sliding2d_pad_to_next( + inpt: NDArray, + nwin: int, + nover: int, + nop: tuple[int, int], +) -> tuple[ + NDArray, int, tuple[int, int], tuple[NDArray, NDArray], tuple[NDArray, NDArray] +]: + """Pad input to next slice + + Pad ``inpt`` to the next slice such that the padded input is completely + filled by overlapping slices. + + Parameters + ---------- + inpt : :obj:`numpy.ndarray` + 2-dimensional input data. + nwin : :obj:`tuple` + Number of samples of window. + nover : :obj:`int` + Number of samples of overlapping part of window. + nop : :obj:`tuple` + Size of model in the transformed domain. + + Returns + ------- + inpt_pad : :obj:`numpy.ndarray` + 2-dimensional input data after padding along the first dimension + nwins : :obj:`int` + Number of windows of padded input. + dims : :obj:`tuple` + Shape of 2-dimensional model of padded input. + mwins_inends : :obj:`tuple` + Start and end indices for model patches of padded input (stored as tuple of tuples). + dwins_inends : :obj:`tuple` + Start and end indices for data patches of padded input (stored as tuple of tuples). + + """ + # Identify current sliding design + dimsd = inpt.shape + nwins, dims, mwins_inends, dwins_inends = sliding2d_design(dimsd, nwin, nover, nop) + + # Pad to next slice + if dwins_inends[1][-1] != dimsd[0]: + pad = dwins_inends[1][-1] - nover + nwin - dimsd[0] + inpt_pad = np.pad(inpt, ((0, pad), (0, 0))) + dimsd_pad = inpt_pad.shape + nwins, dims, mwins_inends, dwins_inends = sliding2d_design( + dimsd_pad, nwin, nover, nop + ) + else: + inpt_pad = inpt + return inpt_pad, nwins, dims, mwins_inends, dwins_inends + + class Sliding2D(LinearOperator): """2D Sliding transform operator. diff --git a/pylops/signalprocessing/sliding3d.py b/pylops/signalprocessing/sliding3d.py index b62d1a8e..b9acc62b 100644 --- a/pylops/signalprocessing/sliding3d.py +++ b/pylops/signalprocessing/sliding3d.py @@ -1,5 +1,6 @@ __all__ = [ "sliding3d_design", + "sliding3d_pad_to_next", "Sliding3D", ] @@ -44,7 +45,7 @@ def sliding3d_design( Parameters ---------- dimsd : :obj:`tuple` - Shape of 2-dimensional data. + Shape of 3-dimensional data. nwin : :obj:`tuple` Number of samples of window. nover : :obj:`tuple` @@ -100,6 +101,67 @@ def sliding3d_design( return nwins, dims, mwins_inends, dwins_inends +def sliding3d_pad_to_next( + inpt: NDArray, + nwin: tuple[int, int], + nover: tuple[int, int], + nop: tuple[int, int, int], +) -> tuple[ + NDArray, + tuple[int, int], + tuple[int, int, int], + tuple[tuple[NDArray, NDArray], tuple[NDArray, NDArray]], + tuple[tuple[NDArray, NDArray], tuple[NDArray, NDArray]], +]: + """Pad input to next set of slices + + Pad ``inpt`` to the next set of slices such that the padded input is completely + filled by overlapping slices. + + Parameters + ---------- + inpt : :obj:`numpy.ndarray` + 3-dimensional input 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. + + Returns + ------- + inpt_pad : :obj:`numpy.ndarray` + 3-dimensional input data after padding along the first and second dimensions + nwins : :obj:`tuple` + Number of windows of padded input. + dims : :obj:`tuple` + Shape of 3-dimensional model of padded input. + mwins_inends : :obj:`tuple` + Start and end indices for model patches of padded input (stored as tuple of tuples). + dwins_inends : :obj:`tuple` + Start and end indices for data patches of padded input (stored as tuple of tuples). + + """ + # Identify current sliding design + dimsd = inpt.shape + nwins, dims, mwins_inends, dwins_inends = sliding3d_design(dimsd, nwin, nover, nop) + + # Pad to next slice + pad = [0, 0] + if dwins_inends[0][1][-1] != dimsd[0]: + pad[0] = dwins_inends[0][1][-1] - nover[0] + nwin[0] - dimsd[0] + if dwins_inends[1][1][-1] != dimsd[1]: + pad[1] = dwins_inends[1][1][-1] - nover[1] + nwin[1] - dimsd[1] + inpt_pad = np.pad(inpt, ((0, pad[0]), (0, pad[1]), (0, 0))) + if pad[0] > 0 or pad[1] > 0: + dimsd_pad = inpt_pad.shape + nwins, dims, mwins_inends, dwins_inends = sliding3d_design( + dimsd_pad, nwin, nover, nop + ) + return inpt_pad, nwins, dims, mwins_inends, dwins_inends + + class Sliding3D(LinearOperator): """3D Sliding transform operator.w diff --git a/pytests/test_patching.py b/pytests/test_patching.py index bb40ed23..62d98505 100644 --- a/pytests/test_patching.py +++ b/pytests/test_patching.py @@ -15,8 +15,8 @@ from pylops.basicoperators import MatrixMult from pylops.optimization.basic import cgls from pylops.signalprocessing import Patch2D, Patch3D -from pylops.signalprocessing.patch2d import patch2d_design -from pylops.signalprocessing.patch3d import patch3d_design +from pylops.signalprocessing.patch2d import patch2d_design, patch2d_pad_to_next +from pylops.signalprocessing.patch3d import patch3d_design, patch3d_pad_to_next from pylops.utils import dottest par1 = { @@ -135,6 +135,54 @@ } # overlap, with taper (non saved) +@pytest.mark.parametrize( + "par", + [ + (par1), + ], +) +def test_patch2d_pad_to_next(par): + """Check pad_to_next returns padded input that is fully covered + by patches""" + for pad0 in range(0, 20): + for pad1 in range(0, 20): + inpt = np.ones((par["npy"] + pad0, par["npx"] + pad1)) + inpt_pad, _, _, _, dwin_inends = patch2d_pad_to_next( + inpt, + (par["nwiny"], par["nwinx"]), + (par["novery"], par["noverx"]), + (par["ny"], par["nx"]), + ) + assert inpt_pad.shape[0] == dwin_inends[0][1][-1] + assert inpt_pad.shape[1] == dwin_inends[1][1][-1] + + +@pytest.mark.parametrize( + "par", + [ + (par1), + ], +) +def test_patch3d_pad_to_next(par): + """Check pad_to_next returns padded input that is fully covered + by patches""" + for pad0 in range(0, 20): + for pad1 in range(0, 20): + for pad2 in range(0, 20): + inpt = np.ones( + (par["npy"] + pad0, par["npx"] + pad1, par["npt"] + pad2) + ) + inpt_pad, _, _, _, dwin_inends = patch3d_pad_to_next( + inpt, + (par["nwiny"], par["nwinx"], par["nwint"]), + (par["novery"], par["noverx"], par["novert"]), + (par["ny"], par["nx"], par["nt"]), + ) + assert inpt_pad.shape[0] == dwin_inends[0][1][-1] + assert inpt_pad.shape[1] == dwin_inends[1][1][-1] + assert inpt_pad.shape[2] == dwin_inends[2][1][-1] + + @pytest.mark.parametrize("par", [(par1), (par2), (par3), (par4), (par5), (par6)]) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_Patch2D(par, dtype): diff --git a/pytests/test_sliding.py b/pytests/test_sliding.py index 73e5404f..d3e68be6 100644 --- a/pytests/test_sliding.py +++ b/pytests/test_sliding.py @@ -15,9 +15,9 @@ from pylops.basicoperators import Identity, MatrixMult from pylops.optimization.basic import cgls from pylops.signalprocessing import Sliding1D, Sliding2D, Sliding3D -from pylops.signalprocessing.sliding1d import sliding1d_design -from pylops.signalprocessing.sliding2d import sliding2d_design -from pylops.signalprocessing.sliding3d import sliding3d_design +from pylops.signalprocessing.sliding1d import sliding1d_design, sliding1d_pad_to_next +from pylops.signalprocessing.sliding2d import sliding2d_design, sliding2d_pad_to_next +from pylops.signalprocessing.sliding3d import sliding3d_design, sliding3d_pad_to_next from pylops.utils import dottest par1 = { @@ -112,6 +112,62 @@ } # overlap, with taper (non saved) +@pytest.mark.parametrize( + "par", + [ + (par1), + ], +) +def test_sliding1d_pad_to_next(par): + """Check pad_to_next returns padded input that is fully covered + by sliding windows""" + for pad in range(0, 20): + inpt = np.ones(par["npy"] + pad) + inpt_pad, _, _, _, dwin_inends = sliding1d_pad_to_next( + inpt, par["nwiny"], par["novery"], par["ny"] + ) + assert inpt_pad.size == dwin_inends[1][-1] + + +@pytest.mark.parametrize( + "par", + [ + (par1), + ], +) +def test_sliding2d_pad_to_next(par): + """Check pad_to_next returns padded input that is fully covered + by sliding windows""" + for pad in range(0, 20): + inpt = np.ones((par["npy"] + pad, par["npx"])) + inpt_pad, _, _, _, dwin_inends = sliding2d_pad_to_next( + inpt, par["nwiny"], par["novery"], (par["ny"], par["nx"]) + ) + assert inpt_pad.shape[0] == dwin_inends[1][-1] + + +@pytest.mark.parametrize( + "par", + [ + (par1), + ], +) +def test_sliding3d_pad_to_next(par): + """Check pad_to_next returns padded input that is fully covered + by sliding windows""" + for pad0 in range(0, 20): + for pad1 in range(0, 20): + inpt = np.ones((par["npy"] + pad0, par["npx"] + pad1, par["npx"])) + inpt_pad, _, _, _, dwin_inends = sliding3d_pad_to_next( + inpt, + (par["nwiny"], par["nwinx"]), + (par["novery"], par["noverx"]), + (par["ny"], par["nx"], par["nx"]), + ) + assert inpt_pad.shape[0] == dwin_inends[0][1][-1] + assert inpt_pad.shape[1] == dwin_inends[1][1][-1] + + @pytest.mark.parametrize("par", [(par1), (par2), (par3), (par4), (par5), (par6)]) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_Sliding1D(par, dtype): From 9945f899bbbc6ed436ae085e2c7ea5201a8813f0 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Fri, 31 Jul 2026 22:53:43 +0100 Subject: [PATCH 2/3] docs: added example with pad_to_next --- docs/source/api/others.rst | 6 +- examples/plot_patching.py | 183 +++++++++++++++++++++++-------------- 2 files changed, 118 insertions(+), 71 deletions(-) diff --git a/docs/source/api/others.rst b/docs/source/api/others.rst index 470740fa..e676c0ca 100755 --- a/docs/source/api/others.rst +++ b/docs/source/api/others.rst @@ -102,7 +102,11 @@ Sliding and Patching sliding3d.sliding3d_design patch2d.patch2d_design patch3d.patch3d_design - + sliding1d.sliding1d_pad_to_next + sliding2d.sliding2d_pad_to_next + sliding3d.sliding3d_pad_to_next + patch2d.patch2d_pad_to_next + patch3d.patch3d_pad_to_next Synthetics diff --git a/examples/plot_patching.py b/examples/plot_patching.py index e1b18bcb..3bd6924a 100644 --- a/examples/plot_patching.py +++ b/examples/plot_patching.py @@ -11,6 +11,7 @@ that are 2- or 3-dimensional in nature, respectively. """ + import matplotlib.pyplot as plt import numpy as np @@ -107,6 +108,47 @@ ############################################################################### # We repeat now the same exercise in 3d + + +def plot_3d(data, par, x, y, t, title): + fig, axs = plt.subplots(1, 3, figsize=(12, 5)) + fig.suptitle(title, fontsize=12, fontweight="bold", y=0.95) + axs[0].imshow( + data[par["ny"] // 2].T, + aspect="auto", + interpolation="nearest", + vmin=-2, + vmax=2, + cmap="gray", + extent=(x.min(), x.max(), t.max(), t.min()), + ) + axs[0].set_xlabel(r"$x(m)$") + axs[0].set_ylabel(r"$t(s)$") + axs[1].imshow( + data[:, par["nx"] // 2].T, + aspect="auto", + interpolation="nearest", + vmin=-2, + vmax=2, + cmap="gray", + extent=(y.min(), y.max(), t.max(), t.min()), + ) + axs[1].set_xlabel(r"$y(m)$") + axs[1].set_ylabel(r"$t(s)$") + axs[2].imshow( + data[:, :, par["nt"] // 2], + aspect="auto", + interpolation="nearest", + vmin=-2, + vmax=2, + cmap="gray", + extent=(x.min(), x.max(), y.max(), x.min()), + ) + axs[2].set_xlabel(r"$x(m)$") + axs[2].set_ylabel(r"$y(m)$") + plt.tight_layout() + + par = { "oy": -60, "dy": 2, @@ -134,43 +176,7 @@ # Generate model _, data = pylops.utils.seismicevents.hyperbolic3d(x, y, t, t0, vrms, vrms, amp, wav) - -fig, axs = plt.subplots(1, 3, figsize=(12, 5)) -fig.suptitle("Original data", fontsize=12, fontweight="bold", y=0.95) -axs[0].imshow( - data[par["ny"] // 2].T, - aspect="auto", - interpolation="nearest", - vmin=-2, - vmax=2, - cmap="gray", - extent=(x.min(), x.max(), t.max(), t.min()), -) -axs[0].set_xlabel(r"$x(m)$") -axs[0].set_ylabel(r"$t(s)$") -axs[1].imshow( - data[:, par["nx"] // 2].T, - aspect="auto", - interpolation="nearest", - vmin=-2, - vmax=2, - cmap="gray", - extent=(y.min(), y.max(), t.max(), t.min()), -) -axs[1].set_xlabel(r"$y(m)$") -axs[1].set_ylabel(r"$t(s)$") -axs[2].imshow( - data[:, :, par["nt"] // 2], - aspect="auto", - interpolation="nearest", - vmin=-2, - vmax=2, - cmap="gray", - extent=(x.min(), x.max(), y.max(), x.min()), -) -axs[2].set_xlabel(r"$x(m)$") -axs[2].set_ylabel(r"$y(m)$") -plt.tight_layout() +plot_3d(data, par, x, y, t, "Original data") ############################################################################### # Let's create now the :py:class:`pylops.signalprocessing.Patch3D` operator @@ -201,39 +207,76 @@ ) reconstructed_data = np.real(Patch * fftdata) -fig, axs = plt.subplots(1, 3, figsize=(12, 5)) -fig.suptitle("Reconstructed data", fontsize=12, fontweight="bold", y=0.95) -axs[0].imshow( - reconstructed_data[par["ny"] // 2].T, - aspect="auto", - interpolation="nearest", - vmin=-2, - vmax=2, - cmap="gray", - extent=(x.min(), x.max(), t.max(), t.min()), +plot_3d(reconstructed_data, par, x, y, t, "Reconstructed data") + +############################################################################### +# However, in practice the shapes of data to be patched do not always conform +# with the patch design, meaning that some of the edges do not fit within the +# patches. In this case, we will use the +# :func:`pylops.signalprocessing.patch3d_pad_to_next` method to pad the input +# before feeding it to the patching operator. +par = { + "oy": -66, + "dy": 2, + "ny": 66, + "ox": -54, + "dx": 2, + "nx": 54, + "ot": 0, + "dt": 0.004, + "nt": 100, + "f0": 20, +} + +v = 1500 +t0 = [0.05, 0.2, 0.3] +vrms = [500, 700, 1700] +amp = [1.0, -2, 0.5] + +# Create axis +t, t2, x, y = pylops.utils.seismicevents.makeaxis(par) + +# Create wavelet +wav = pylops.utils.wavelets.ricker(t[:41], f0=par["f0"])[0] + +# Generate model +_, data = pylops.utils.seismicevents.hyperbolic3d(x, y, t, t0, vrms, vrms, amp, wav) + +plot_3d(reconstructed_data, par, x, y, t, "Data") + +# Patching operator +dimsd = data.shape +nwins, dims, mwin_inends, dwin_inends = pylops.signalprocessing.patch3d_design( + dimsd, nwin, nover, (128, 128, 65) ) -axs[0].set_xlabel(r"$x(m)$") -axs[0].set_ylabel(r"$t(s)$") -axs[1].imshow( - reconstructed_data[:, par["nx"] // 2].T, - aspect="auto", - interpolation="nearest", - vmin=-2, - vmax=2, - cmap="gray", - extent=(y.min(), y.max(), t.max(), t.min()), +Patch = pylops.signalprocessing.Patch3D( + Op.H, dims, dimsd, nwin, nover, nop, tapertype=None ) -axs[1].set_xlabel(r"$y(m)$") -axs[1].set_ylabel(r"$t(s)$") -axs[2].imshow( - reconstructed_data[:, :, par["nt"] // 2], - aspect="auto", - interpolation="nearest", - vmin=-2, - vmax=2, - cmap="gray", - extent=(x.min(), x.max(), y.max(), x.min()), +fftdata = Patch.H * data + +Patch = pylops.signalprocessing.Patch3D( + Op.H, dims, dimsd, nwin, nover, nop, tapertype="hanning" ) -axs[2].set_xlabel(r"$x(m)$") -axs[2].set_ylabel(r"$y(m)$") -plt.tight_layout() +reconstructed_data = np.real(Patch * fftdata) + +plot_3d(reconstructed_data, par, x, y, t, "Reconstructed data without padding") + +# Pad to next +data_pad, nwins, dims, mwin_inends, dwin_inends = ( + pylops.signalprocessing.patch3d.patch3d_pad_to_next( + data, nwin, nover, (128, 128, 65) + ) +) + +dimsd_pad = data_pad.shape +Patch = pylops.signalprocessing.Patch3D( + Op.H, dims, dimsd_pad, nwin, nover, nop, tapertype=None +) +fftdata = Patch.H * data_pad + +Patch = pylops.signalprocessing.Patch3D( + Op.H, dims, dimsd_pad, nwin, nover, nop, tapertype="hanning" +) +reconstructed_data = np.real(Patch * fftdata)[: dimsd[0], : dimsd[1], : dimsd[2]] + +plot_3d(reconstructed_data, par, x, y, t, "Reconstructed data with padding") From 5011988edaf66f347491cb8b39f34f462ca71d0d Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sat, 1 Aug 2026 15:27:00 +0100 Subject: [PATCH 3/3] minor: docstring minor improvements --- pylops/signalprocessing/patch2d.py | 10 +++++----- pylops/signalprocessing/patch3d.py | 10 +++++----- pylops/signalprocessing/sliding1d.py | 10 +++++----- pylops/signalprocessing/sliding2d.py | 10 +++++----- pylops/signalprocessing/sliding3d.py | 10 +++++----- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/pylops/signalprocessing/patch2d.py b/pylops/signalprocessing/patch2d.py index f93a870b..13c96f88 100644 --- a/pylops/signalprocessing/patch2d.py +++ b/pylops/signalprocessing/patch2d.py @@ -46,7 +46,7 @@ def patch2d_design( Parameters ---------- dimsd : :obj:`tuple` - Shape of 2-dimensional data. + Shape of the 2-dimensional data. nwin : :obj:`tuple` Number of samples of window. nover : :obj:`tuple` @@ -62,7 +62,7 @@ def patch2d_design( nwins : :obj:`tuple` Number of windows. dims : :obj:`tuple` - Shape of 2-dimensional model. + Shape of the 2-dimensional model. mwins_inends : :obj:`tuple` Start and end indices for model patches (stored as tuple of tuples). dwins_inends : :obj:`tuple` @@ -137,7 +137,7 @@ def patch2d_pad_to_next( nwins : :obj:`tuple` Number of windows. dims : :obj:`tuple` - Shape of 2-dimensional model. + Shape of the 2-dimensional model. mwins_inends : :obj:`tuple` Start and end indices for model patches (stored as tuple of tuples). dwins_inends : :obj:`tuple` @@ -194,11 +194,11 @@ class Patch2D(LinearOperator): Op : :obj:`pylops.LinearOperator` Transform operator dims : :obj:`tuple` - Shape of 2-dimensional model. Note that ``dims[0]`` and ``dims[1]`` + Shape of the 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 + Shape of the 2-dimensional data nwin : :obj:`tuple` Number of samples of window nover : :obj:`tuple` diff --git a/pylops/signalprocessing/patch3d.py b/pylops/signalprocessing/patch3d.py index 3f75f24f..0463eb41 100644 --- a/pylops/signalprocessing/patch3d.py +++ b/pylops/signalprocessing/patch3d.py @@ -46,7 +46,7 @@ def patch3d_design( Parameters ---------- dimsd : :obj:`tuple` - Shape of 3-dimensional data. + Shape of the 3-dimensional data. nwin : :obj:`tuple` Number of samples of window. nover : :obj:`tuple` @@ -62,7 +62,7 @@ def patch3d_design( nwins : :obj:`tuple` Number of windows. dims : :obj:`tuple` - Shape of 3-dimensional model. + Shape of the 3-dimensional model mwins_inends : :obj:`tuple` Start and end indices for model patches (stored as tuple of tuples). dwins_inends : :obj:`tuple` @@ -152,7 +152,7 @@ def patch3d_pad_to_next( nwins : :obj:`tuple` Number of windows. dims : :obj:`tuple` - Shape of 3-dimensional model. + Shape of the 3-dimensional model mwins_inends : :obj:`tuple` Start and end indices for model patches (stored as tuple of tuples). dwins_inends : :obj:`tuple` @@ -211,11 +211,11 @@ class Patch3D(LinearOperator): Op : :obj:`pylops.LinearOperator` Transform operator dims : :obj:`tuple` - Shape of 3-dimensional model. Note that ``dims[0]``, ``dims[1]`` + Shape of the 3-dimensional model. Note that ``dims[0]``, ``dims[1]`` and ``dims[2]`` should be multiple of the model size of the transform in their respective dimensions dimsd : :obj:`tuple` - Shape of 3-dimensional data + Shape of the 3-dimensional data nwin : :obj:`tuple` Number of samples of window nover : :obj:`tuple` diff --git a/pylops/signalprocessing/sliding1d.py b/pylops/signalprocessing/sliding1d.py index d74e9f87..514a617b 100644 --- a/pylops/signalprocessing/sliding1d.py +++ b/pylops/signalprocessing/sliding1d.py @@ -40,7 +40,7 @@ def sliding1d_design( Parameters ---------- dimd : :obj:`int` - Shape of 1-dimensional data. + Shape of the 1-dimensional data. nwin : :obj:`int` Number of samples of window. nover : :obj:`int` @@ -56,7 +56,7 @@ def sliding1d_design( nwins : :obj:`int` Number of windows. dim : :obj:`int` - Shape of 2-dimensional model. + Shape of the 1-dimensional model. mwins_inends : :obj:`tuple` Start and end indices for model patches. dwins_inends : :obj:`tuple` @@ -117,7 +117,7 @@ def sliding1d_pad_to_next( nwins : :obj:`int` Number of windows of padded input. dim : :obj:`int` - Shape of 2-dimensional model of padded input. + Shape of the 1-dimensional model of padded input. mwins_inends : :obj:`tuple` Start and end indices for model patches of padded input. dwins_inends : :obj:`tuple` @@ -175,9 +175,9 @@ class Sliding1D(LinearOperator): Op : :obj:`pylops.LinearOperator` Transform operator dim : :obj:`tuple` - Shape of 1-dimensional model. + Shape of the 1-dimensional model dimd : :obj:`tuple` - Shape of 1-dimensional data + Shape of the 1-dimensional data nwin : :obj:`int` Number of samples of window nover : :obj:`int` diff --git a/pylops/signalprocessing/sliding2d.py b/pylops/signalprocessing/sliding2d.py index f27b8e17..130f7bcf 100644 --- a/pylops/signalprocessing/sliding2d.py +++ b/pylops/signalprocessing/sliding2d.py @@ -73,7 +73,7 @@ def sliding2d_design( Parameters ---------- dimsd : :obj:`tuple` - Shape of 2-dimensional data. + Shape of the 2-dimensional data. nwin : :obj:`int` Number of samples of window. nover : :obj:`int` @@ -152,7 +152,7 @@ def sliding2d_pad_to_next( nwins : :obj:`int` Number of windows of padded input. dims : :obj:`tuple` - Shape of 2-dimensional model of padded input. + Shape of the 2-dimensional model of padded input. mwins_inends : :obj:`tuple` Start and end indices for model patches of padded input (stored as tuple of tuples). dwins_inends : :obj:`tuple` @@ -213,10 +213,10 @@ class Sliding2D(LinearOperator): 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 + Shape of the 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 + Shape of the 2-dimensional data nwin : :obj:`int` Number of samples of window nover : :obj:`int` diff --git a/pylops/signalprocessing/sliding3d.py b/pylops/signalprocessing/sliding3d.py index b9acc62b..81de28f4 100644 --- a/pylops/signalprocessing/sliding3d.py +++ b/pylops/signalprocessing/sliding3d.py @@ -45,7 +45,7 @@ def sliding3d_design( Parameters ---------- dimsd : :obj:`tuple` - Shape of 3-dimensional data. + Shape of the 3-dimensional data. nwin : :obj:`tuple` Number of samples of window. nover : :obj:`tuple` @@ -61,7 +61,7 @@ def sliding3d_design( nwins : :obj:`tuple` Number of windows. dims : :obj:`tuple` - Shape of 2-dimensional model. + Shape of the 3-dimensional model. mwins_inends : :obj:`tuple` Start and end indices for model patches (stored as tuple of tuples). dwins_inends : :obj:`tuple` @@ -136,7 +136,7 @@ def sliding3d_pad_to_next( nwins : :obj:`tuple` Number of windows of padded input. dims : :obj:`tuple` - Shape of 3-dimensional model of padded input. + Shape of the 3-dimensional model of padded input. mwins_inends : :obj:`tuple` Start and end indices for model patches of padded input (stored as tuple of tuples). dwins_inends : :obj:`tuple` @@ -200,11 +200,11 @@ class Sliding3D(LinearOperator): Op : :obj:`pylops.LinearOperator` Transform operator dims : :obj:`tuple` - Shape of 3-dimensional model. Note that ``dims[0]`` and ``dims[1]`` + Shape of the 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 + Shape of the 3-dimensional data nwin : :obj:`tuple` Number of samples of window nover : :obj:`tuple`