2d signal module#

This module provides lazy linear operators associated to fast linear transforms commonly used in image processing. ..

This currently includes the Discrete Cosine Transform (dct2d()), Discrete Sine Transform (dst2d()), Modified Discrete Cosine Transform (mdct2d()), Discrete Fourier Transform (dft2d()) and the Discrete Wavelet Transform (dwt2d()), and we also provide a lazy linear operator for convolution with a given (2D) filter.

List of transforms#

  1. lazylinop.signal2d.convolve2d() Convolution with a given (2D) filter.

  2. lazylinop.signal2d.dwt2d() Discrete Wavelet Transform

  3. lazylinop.signal2d.idwt2d() Inverse of Discrete Wavelet transform

  4. lazylinop.signal2d.dft2d() Discrete Fourier Transform

  5. lazylinop.signal2d.dct2d() Discrete Cosine Transform of types I to IV

  6. lazylinop.signal2d.dst2d() Discrete Sine Transform of types I to IV

  7. lazylinop.signal2d.mdct2d() Modified DCT

Input (and output) vector shape #

Applying any of these linear operators L to a single input image X provided as a 2D-array (or a batch of such 2D-arrays) requires prior flattening of images.

A typical usage of 2d signal module would be:

>>> shape2d = X.shape[0], X.shape[1]  # X is an image or a batch of images
>>> L = foo2d(shape2d, ...)
>>> y = L @ colvec(X)                 # Apply L to the flattened version of x
>>> Y = uncolvec(y, shape2d)          # Un-flattened version of y

This requirement is due to the need to ensure compatibility with the LazyLinOp API, where the result of L @ V is specified as the concatenation of the column vectors obtained by applying L to each column of V. Do not try to compute y = L @ X, it would not give the desired result (and would even simply generate an error due to non-matching dimensions of the columns of X with the expected input dimension of the operator L)!

However, note that signal2d functions usually take an in_shape as first argument, where \(\text{in_shape}=\left(M,~N\right)\) the two dimensions of the input image(s).

As a concrete example:

  • if X is a single 2D image, it is needed to perform y = L @ X.ravel().

  • if instead X is a batch of 2D images, where the first two dimension are the 2D image dimension and the third is the batch dimension (as usual with LazyLinOp API), it would need: y = L @ X.reshape(-1, X.shape[2])

For convenience, LazyLinOp provides colvec(X) utility function (colvec()) that performs the required reshaping in both -single or batch of- image situations (internally it implements X.reshape(-1, *(X.shape[2:])).

For operators where it can be expected than the output returns an transformed image (e.g. convolve2d, padder2d,…), the output y is also flattened. For the same reason, y requires reshaping to be manipulated as an image (or an image batch). We also provide an uncolvec(y, in_shape) utility function (uncolvec()) for that purpose.

Padding or cropping#

Traditional implementations of image processing transforms offer the possibility to either crop or zero-pad the analyzed image. We choose not to include this in our implementation, as mimicing this feature is simple with the generic lazylinop interface.

Consider for the example F2 = dft2d(in_shape=(N, N)), the operator associated to the 2D DFT matrix on \(N\times N\) images. To apply it to an image X of size \(N\times N\), we can define G = eye(N, n), G2 = kron(G, G) and observe that F2 @ G2 does exactly what we need:

  • padding: if \(n<N\), by definition of the Kronecker product G2 @ colvec(X) = (G @ X @ G.T) is a (flattened) zero padded version of X, of size \(N\times N\), so that H = F2 @ G2 is the lazy linear operator that computes the 2D DFT after zero padding.

  • cropping: if \(n>N\), similarly G2 @ colvec(X) is a (flattened) cropped version of X of size N x N, and H = F2 @ G2 is again exactly what you need.

For convenience, we provide an operator padder2d(in_shape, width, mode) to pad a 2D array (provided “of course” in a flattened form) with specific boundary conditions zero, periodic, symmetric, antisymmetric and reflect. padder2d() is obtained from the Kronecker product kron(L1, L2) of two operators L1 = padder(in_shape[0], width[0], mode) and L2 = padder(in_shape[1], width[1], mode).

Warning

Do not confuse padder2d with pad (see Construction for more details).

Inverse transforms#

As in the signal module Signal module, most of our 2D transforms are orthonormal, and their inverse is thus their adjoint, e.g., with F the DFT, the adjoint F.H is the lazy linear operator associated to the inverse transform (appropriately taking into account all parameters of the original transform).

The main exceptions are convolve2d(), mdct2d() (this operator satisfies L @ L.T = L @ L.H = Id but not L.T @ L = Id) and dwt2d() (see its documentation for details on parameters ensuring that L = dwt2d(...) satisfies L @ L.T = L.T @ L = Id or only L.T @ L = Id when L is rectangular).

Note

This is because of most our 2D transforms are Kronecker product \(K=L_1\otimes L_2\) of two 1D transforms \(L_1\) and \(L_2\). The matrix product of the adjoint \(K^H\) with \(K\) is given by \(K^HK=\left(L_1^H\otimes L_2^H\right)\left(L_1\otimes L_2\right)\) and we have \(K^HK=\left(L_1^HL\right)\otimes\left(L_2^HL_2\right)\) (mixed-product property). If \(L_1\) and \(L_2\) are orthogonal, \(K^HK\) is equal to the identity matrix. Therefore, the 2D transform associated to \(K\) is orthogonal.

Non-orthonormal versions of these transforms correspond to alternative normalizations described below.

Various normalizations#

Traditional implementations of image processing transforms offer various normalization (e.g., with division by \(N\), \(\sqrt{N}\), or no division). They can all be mimicked (if really needed) by pre- or post-composing the transform F. As an illustration, SciPy’s fft2d() and ifft2d() with the default normalization is mimicked as follows.

>>> import numpy as np
>>> from scipy.fft import fft2 as sp_fft2d
>>> from scipy.fft import ifft2 as sp_ifft2d
>>> from lazylinop.signal2d import dft2d as lz_dft2d
>>> from lazylinop.signal2d import colvec
>>> from lazylinop.signal2d import uncolvec
>>> N = 32
>>> X = np.random.randn(N, N)
>>> F2 = lz_dft2d((N, N))
>>> scale = N
>>> y = scale * F2 @ colvec(X)
>>> Y = sp_fft2d(X)
>>> np.allclose(colvec(y, (N, N)), Y)
>>> True
>>> x_ = F2.H @ y / scale
>>> X_ = sp_ifft2d(Y)
>>> np.allclose(colvec(x_, (N, N)), X_)
>>> True

To mimick SciPy’s DCT/DST called with orthogonalize=True, the same trick holds where scale depends on the transform’s type (I,II,III,IV) and the choice of norm ('backward' or 'forward'; NB: scale = 1 if norm = 'ortho').

Mimicking the DCT/DST with orthogonalize=False requires pre- and/or post-composing by diagonal operators that depend on the type. For example, the default DCT-II behavior (norm = 'ortho'):

>>> from lazylinop.signal2d import dst2d as lz_dst2d
>>> from scipy.fft import dstn as sp_dstn
>>> import numpy as np
>>> M, N = 32, 32
>>> X = np.random.randn(M, N)
>>> L = lz_dst2d(X.shape)
>>> Y = L @ colvec(X)
>>> from lazylinop.basicops import diag
>>> v = np.full(N, 1.0)
>>> v[-1] = np.sqrt(2.0)
>>> D = diag(v)
>>> Z = sp_dstn(X, 2, (M, N), (0, 1), 'ortho', False, 1, orthogonalize=False)
>>> np.allclose(D @ Y.reshape(M, N) @ D, Z)
True

Transforms#

lazylinop.signal2d.convolve2d(in_shape, filter, mode='full', boundary='fill', backend='auto')#

Returns a LazyLinOp L for the 2D convolution of a 2D signal of shape in_shape=(M, N) (provided in flattened version) with a 2D filter.

Shape of L is \((M'N',~MN)\) where (M, N)=in_shape and out_shape=(M', N') depends on mode. After applying the operator as y = L @ colvec(X), a 2D output can be obtained via uncolvec(y, out_shape).

Args:
in_shape: tuple,

Shape \((M,~N)\) of the signal to convolve with kernel.

filter: NumPy array, CuPy array or torch tensor

Kernel to use for the convolution, shape is \((K,~L)\).

mode: str, optional
  • 'full': compute full convolution including at points of non-complete overlapping of inputs (default). Yields out_shape=(M', N') with M'=M + K - 1 and N'=N + L - 1.

  • 'valid': compute only fully overlapping part of the convolution. This is the ‘full’ center part of (output) shape (M - K + 1, N - L + 1) (K <= M and L <= N must be satisfied). Yields out_shape=(M', N') with M' = M - K + 1 and N' = N - L + 1.

  • 'same': compute only the center part of 'full' to obtain an output size M equal to the input size N. Yields out_shape=(M', N') with M' = M and N' = N.

boundary: str, optional
  • 'fill' pads input array with zeros (default).

  • 'wrap' periodic boundary conditions.

  • 'symm' symmetrical boundary conditions.

backend: str, optional
  • 'auto': use the best backend according to the kernel and input array dimensions.

  • 'scipy encapsulation': encapsulate (cupyx).scipy.signal.convolve. If the filter and the input are CuPy arrays the function uses cupyx.scipy.signal.convolve.

  • ‘toeplitz’`: encapsulate (cupyx).scipy.linalg.toeplitz if N < 2048, (cupyx).scipy.linalg.matmul_toeplitz otherwise. Of note, there is no torch implementation for Toeplitz matrix.

  • ``’scipy fft’`: use Fast-Fourier-Transform to compute convolution.

  • 'direct': direct computation using nested for-loops with Numba and parallelization. It does not work if the input is CuPy array or torch tensor.

Returns:

LazyLinOp

Examples:
>>> from lazylinop.signal2d import convolve2d, colvec, uncolvec
>>> import numpy as np
>>> import scipy as sp
>>> X = np.random.randn(6, 6)
>>> H = np.random.randn(3, 3)
>>> L = convolve2d(X.shape, H, mode='same')
>>> y1 = uncolvec(L @ colvec(X), X.shape)
>>> y2 = sp.signal.convolve2d(X, H, mode='same')
>>> np.allclose(y1, y2)
True

See also

reference/generated/scipy.signal.convolve2d.html>`_, - CuPy convolve2d function.

lazylinop.signal2d.dwt2d(in_shape, wavelet='haar', mode='zero', level=None, backend='pywavelets')#

Returns a LazyLinOp L for the 2D Discrete-Wavelet-Transform (DWT) of a 2D signal of shape in_shape = (M, N) (provided in flattened version).

L @ x will return a 1d NumPy/CuPy array or torch tensor as the concatenation of the DWT coefficients in the form [cAn, cHn, cVn, cDn, ..., cH1, cV1, cD1] where n is the decomposition level.

  • cAi are the approximation coefficients for level i.

  • cHi are the horizontal coefficients for level i.

  • cVi are the vertical coefficients for level i.

  • cDi are the detail coefficients for level i.

cAi, cHi, cVi and cDi matrices have been flattened.

Shape of L is \((P,~MN)\) where \(P>=MN\). The value of \(P\) depends on the mode. In general, L is not orthogonal.

Args:
in_shape: tuple

Shape of the 2d input array \((M,~N)\).

wavelet: str or tuple of (dec_lo, dec_hi), optional

  • If a str is provided, the wavelet name from Pywavelets library

  • If a tuple (dec_lo, dec_hi) of two NumPy/CuPy arrays or torch tensors is provided, the low and high-pass filters (for decomposition) used to define the wavelet.

    The dwt2d() function does not test whether these two filters are actually Quadrature-Mirror-Filters.

mode: str, optional

  • 'zero', signal is padded with zeros (default).

  • 'periodic', signal is treated as periodic signal.

  • 'symmetric', use mirroring to pad the signal.

  • 'antisymmetric', signal is extended by mirroring and multiplying elements by minus one.

  • 'reflect', signal is extended by reflecting elements.

  • 'periodization', signal is extended like 'periodic' extension mode. Only the smallest possible number of coefficients is returned. Odd-length signal is extended first by replicating the last value.

level: int, optional

If level is None compute full decomposition (default).

backend: str, optional

'pywavelets' (default) or 'lazylinop' for the underlying computation of the DWT.

Returns:

LazyLinOp

Examples:
>>> from lazylinop.signal2d import dwt2d, colvec
>>> import numpy as np
>>> import pywt
>>> X = np.array([[1., 2.], [3., 4.]])
>>> L = dwt2d(X.shape, wavelet='db1', level=1)
>>> y = L @ colvec(X)
>>> cA, (cH, cV, cD) = pywt.wavedec2(X, wavelet='db1', level=1)
>>> z = np.concatenate([cA, cH, cV, cD], axis=1)
>>> np.allclose(y, z)
True
lazylinop.signal2d.idwt2d(out_shape, wavelet='haar', mode='zero', level=None, backend='pywavelets')#

Returns a LazyLinOp iL for the 2D inverse Discrete-Wavelet-Transform (iDWT) of a 2D signal of shape \((M',~N')\) (provided in flattened version).

If L = dwt2d(out_shape, wavelet, mode, level, backend) is the 2D DWT operator of shape \((M'N',~MN)\) (out_shape is \((M,~N)\)), then iL = idwt2d(out_shape, wavelet, mode, level, backend) is the 2D iDWT operator such that iL @ L = Id. As a result, if y = L @ x is the coefficients at level decomposition level, then the \((M,~N)\)-dimensionnal signal x can be reconstructed from the \((M',~N')\)-dimensionnal vector y as iL @ y.

Shape of iL is \((MN,~M'N')\) where \(M'N'>=MN\). The value of \(M'N'\) depends on the mode. In general, iL is not orthogonal.

Args:
out_shape: tuple

Shape of the output array \((M,~N)\) (i.e., shape of the input array of the associated DWT LazyLinOp, see above).

wavelet: str or tuple of (np.ndarray, np.ndarray), optional

  • If a str is provided, the wavelet name from Pywavelets library

  • If a tuple (rec_lo, rec_hi) of two np.ndarray is provided, the low and high-pass filters (for reconstruction) used to define the wavelet.

    The idwt2d() function does not test whether these two filters are actually Quadrature-Mirror-Filters.

mode: str, optional

  • 'zero', signal is padded with zeros (default).

  • 'periodic', signal is treated as periodic signal.

  • 'symmetric', use mirroring to pad the signal.

  • 'antisymmetric', signal is extended by mirroring and multiplying elements by minus one.

  • 'reflect', signal is extended by reflecting elements.

  • 'periodization', signal is extended like 'periodic' extension mode. Only the smallest possible number of coefficients is returned. Odd-length signal is extended first by replicating the last value.

level: int, optional

If level is None compute full decomposition (default).

backend: str, optional

'pywavelets' (default) or 'lazylinop' for the underlying computation of the DWT.

Returns:

LazyLinOp

Examples:
>>> from lazylinop.signal2d import dwt2d, idwt2d
>>> from lazylinop.signal2d import colvec, uncolvec
>>> import numpy as np
>>> M, N = 2, 3
>>> x = np.arange(M * N).reshape(M, N)
>>> x
array([[0, 1, 2],
       [3, 4, 5]])
>>> W = dwt2d(x.shape, wavelet='db1', level=1)
>>> y = W @ colvec(x)
>>> L = idwt2d(x.shape, wavelet='db1', level=1)
>>> z = L @ y
>>> np.allclose(x, uncolvec(z, (M, N)))
True
lazylinop.signal2d.dft2d(in_shape)#

Returns a LazyLinOp L for the orthogonal 2D Discrete-Fourier-Transform (DFT) of a 2D signal of shape in_shape = (M, N) (provided in flattened version).

Shape of L is \((MN,~MN)\) with \((M,~N)= ext{in_shape}\). After applying the operator as y = L @ colvec(X), a 2D output can be obtained via uncolvec(y, out_shape) with out_shape = in_shape. L is orthogonal.

Args:
in_shape: tuple

Shape of the 2d input array \((M,~N)\).

Returns:

LazyLinOp

Example:
>>> from lazylinop.signal2d import dft2d, colvec, uncolvec
>>> import numpy as np
>>> import scipy as sp
>>> # Check the 2d DFT
>>> N = 32
>>> F = dft2d((N, N))
>>> x = np.random.rand(N, N)
>>> ly = F @ colvec(x)
>>> sy = sp.fft.fft2(x, norm='ortho')
>>> np.allclose(uncolvec(ly, (N, N)), sy)
True
>>> # Check the inverse DFT
>>> lx_ = F.H @ ly
>>> sx_ = sp.fft.ifft2(sy, norm='ortho')
>>> np.allclose(lx_, colvec(sx_))
True
>>> # Orthogonal DFT
>>> A = F.toarray()
>>> H = F.H.toarray()
>>> np.allclose(A @ H, np.eye(N ** 2))
True
lazylinop.signal2d.fft2d(in_shape)#

Alias for dft2d(in_shape: tuple) function.

lazylinop.signal2d.dct2d(in_shape, type=2, backend='scipy')#

Returns a LazyLinOp `L for the 2D Direct Cosine Transform (DCT) of a 2D signal of shape in_shape=(M, N) (provided in flattened version).

Shape of L is \((MN,~MN)\) with \((M,~N)=\text{in_shape}\). After applying the operator as y = L @ colvec(X), a 2D output can be obtained via uncolvec(y, out_shape) with out_shape = in_shape. L is orthogonal.

Args:
in_shape: tuple

Shape of the 2d input array \((M,~N)\).

type: int, optional

1, 2, 3, 4 (I, II, III, IV). Defaut is 2. See SciPy DCT for more details.

backend: str, optional
  • 'scipy' (default) uses scipy.fft.dct to compute the DCT.

  • 'lazylinop' uses a composition of basic Lazylinop operators to compute the DCT (fft(), vstack() etc.).

Returns:

LazyLinOp

Examples:
>>> from lazylinop.signal2d import dct2d as lz_dct2d, colvec, uncolvec
>>> from scipy.fft import dctn as sp_dctn
>>> import numpy as np
>>> M, N = 32, 32
>>> X = np.random.randn(M, N)
>>> L = lz_dct2d(X.shape)
>>> Y = L @ colvec(X)
>>> Z = sp_dctn(X, 2, (M, N), (0, 1), norm='ortho')
>>> np.allclose(Y, colvec(Z))
True
>>> # compute the inverse DCT
>>> X_ = L.T @ Y
>>> np.allclose(X_, colvec(X))
True
>>> # To mimick SciPy DCT II norm='ortho' and orthogonalize=False
>>> # Because of Kronecker vec trick we apply diagonal operator D
>>> # on the left and on the right after reshaping of Y.
>>> from lazylinop.basicops import diag
>>> v = np.full(N, 1.0)
>>> v[0] = np.sqrt(2.0)
>>> D = diag(v)
>>> Z = sp_dctn(X, 2, (M, N), (0, 1), 'ortho', False, 1, orthogonalize=False)
>>> np.allclose(D @ uncolvec(Y, (M, N)) @ D, Z)
True
lazylinop.signal2d.dst2d(in_shape, type=2, backend='scipy')#

Returns a LazyLinOp L for the 2D Direct Sine Transform (DST) of a 2D signal of shape in_shape=(M, N) (provided in flattened version).

Shape of L is \((MN,~MN)\) with \((M,~N)=\text{in_shape}\). After applying the operator as y = L @ colvec(X), a 2D output can be obtained via uncolvec(y, out_shape) with out_shape = in_shape. L is orthogonal.

Args:
in_shape: tuple

Shape of the 2d input array \((M,~N)\).

type: int, optional

1, 2, 3, 4 (I, II, III, IV). Defaut is 2. See SciPy DST for more details.

backend: str, optional
  • 'scipy' (default) uses scipy.fft.dst to compute the DST.

  • 'lazylinop' Uses a composition of basic Lazylinop operators to compute the DST (fft(), vstack() etc.).

Returns:

LazyLinOp

Example:
>>> from lazylinop.signal2d import dst2d as lz_dst2d, colvec, uncolvec
>>> from scipy.fft import dstn as sp_dstn
>>> import numpy as np
>>> M, N = 32, 32
>>> X = np.random.randn(M, N)
>>> L = lz_dst2d(X.shape)
>>> Y = L @ colvec(X)
>>> Z = sp_dstn(X, 2, (M, N), (0, 1), norm='ortho')
>>> np.allclose(Y, colvec(Z))
True
>>> # compute the inverse DST
>>> X_ = L.T @ Y
>>> np.allclose(uncolvec(X_, (M, N)), X)
True
>>> # To mimick SciPy DST II norm='ortho' and orthogonalize=False
>>> # Because of Kronecker vec trick we apply diagonal operator D
>>> # on the left and on the right after reshaping of Y.
>>> from lazylinop.basicops import diag
>>> v = np.full(N, 1.0)
>>> v[-1] = np.sqrt(2.0)
>>> D = diag(v)
>>> Z = sp_dstn(X, 2, (M, N), (0, 1), 'ortho', False, 1, orthogonalize=False)
>>> np.allclose(D @ uncolvec(Y, (M, N)) @ D, Z)
True
lazylinop.signal2d.mdct2d(in_shape, windows=(('vorbis', 128), ('vorbis', 128)), backend='scipy')#

Returns a LazyLinOp L for the 2D Modified Direct Cosine Transform (MDCT) of a 2D signal of shape in_shape=(M, N) (provided in flattened version).

Shape of L is \((PQ,~MN)\) with \(P=n_1\frac{W_1}{2}\) and \(Q=n_2\frac{W_2}{2}\).

\(n_i\) is the number of chunks and \(W_i\) is the window size along axis \(i\).

After applying the operator as y = L @ colvec(X), a 2D output can be obtained via uncolvec(y, out_shape) with out_shape = (P / 2, Q / 2).

L is not orthogonal (as it is rectangular with fewer rows than columns, it is not left invertible) but it is right-invertible and real-valued, with L @ L.T = L @ L.H = Id. Thus, L.T can be used as a right-inverse.

Args:
in_shape: tuple

Shape of the 2d input array \((M,~N)\).

windows: tuple of (str, int) or (str, int, float), optional

Windows, a tuple ((name: str, win_size: int), (name: str, win_size: int)) or ((name: str, win_size: int, beta: float), (name: str, win_size: int, beta: float)). Window size must be a mutliple of 4. Default is (('vorbis', 128), ('vorbis', 128)). beta has no effect excepts for 'kaiser_bessel_derived' window. Possible windows are:

backend: str, optional
  • 'scipy' (default) uses scipy.fft.dct encapsulation for the underlying computation of the DCT.

  • 'lazylinop' uses pre-built Lazylinop operators (Lazylinop fft(), eye(), vstack() etc.) to build the pipeline that will compute the MDCT.

Returns:

LazyLinOp

Example:
>>> from lazylinop.signal2d import mdct2d, colvec
>>> import numpy as np
>>> M, N = 128, 128
>>> X = np.random.randn(M, N)
>>> L = mdct2d(X.shape)
>>> Y = L @ colvec(X)
>>> Y.shape[0] == (M // 2) * (N // 2)
True
>>> X_ = L.T @ Y
>>> X_.shape[0] == M * N
True
References:
  • [1] Xuancheng Shao, Steven G. Johnson, Type-IV DCT, DST, and MDCT algorithms with reduced numbers of arithmetic operations, Signal Processing, Volume 88, Issue 6, 2008, Pages 1313-1326, ISSN 0165-1684, https://doi.org/10.1016/j.sigpro.2007.11.024.

lazylinop.signal2d.padder2d(in_shape, width=((0, 0), (0, 0)), mode='zero')#

Returns a LazyLinOp L that extend a 2D signal of shape in_shape=(M, N) (provided in flattened version) with either zero, periodic, symmetric, antisymmetric or reflect boundary conditions.

Shape of L is \((M'N',~MN)\). \(M'\) and \(N'\) are determined by width. By default \(M'=M\) and \(N'=N\).

After applying the operator as y = L @ colvec(X), a 2D output can be obtained via uncolvec(y, out_shape) with out_shape = (M', N').

padder2d use the pre-built LazyLinOp padder and the Kronecker vector product trick to compute L @ x.

Args:
in_shape: tuple

Shape of the 2D input array \((M,~N)\).

width: tuple, optional

width argument expects a tuple like ((b_0, a_0), (b_1, a_1)). Number of values padded on both side of the first axis is (b_0, a_0). Number of values padded on both side of the second axis is (b_1, a_1). b_0 stands for before while a_0 stands for after. By default it is equal to ((0, 0), (0, 0)).

The size of the output is \(M'N'\) with M'N' = (b_0 + M + a_0) * (b_1 + N + a_1).

mode: str, optional

'zero' (default) or 'wrap'/'periodic' or 'symm'/'symmetric' or 'antisymmetric' or 'reflect' boundary condition. See the documentation of the .lazylinop.basicops.padder for more details.

Returns:

LazyLinOp of shape \((M'N',~MN)\) where \(M'N' = (b_0 + M + a_0)(b_1 + N + a_1)\).

Examples:
>>> from lazylinop.signal2d import padder2d, colvec, uncolvec
>>> import numpy as np
>>> M, N = 2, 2
>>> X = np.arange(4).astype('float').reshape(M, N)
>>> X
array([[0., 1.],
       [2., 3.]])
>>> L = padder2d(X.shape, ((M, M), (N, N)), mode='periodic')
>>> uncolvec(L @ colvec(X), (3 * M, 3 * N))
array([[0., 1., 0., 1., 0., 1.],
       [2., 3., 2., 3., 2., 3.],
       [0., 1., 0., 1., 0., 1.],
       [2., 3., 2., 3., 2., 3.],
       [0., 1., 0., 1., 0., 1.],
       [2., 3., 2., 3., 2., 3.]])
>>> M, N = 3, 2
>>> X = np.arange(1, 7).astype('float').reshape(M, N)
>>> X
array([[1., 2.],
       [3., 4.],
       [5., 6.]])
>>> L = padder2d(X.shape, ((0, 1), (3, 5)), mode='symmetric')
>>> uncolvec(L @ colvec(X), (0 + M + 1, 3 + N + 5))
array([[2., 2., 1., 1., 2., 2., 1., 1., 2., 2.],
       [4., 4., 3., 3., 4., 4., 3., 3., 4., 4.],
       [6., 6., 5., 5., 6., 6., 5., 5., 6., 6.],
       [6., 6., 5., 5., 6., 6., 5., 5., 6., 6.]])

See also

lazylinop.basicops.padder()

Utility functions#

  1. lazylinop.flatten()

  2. lazylinop.unflatten()

  3. lazylinop.signal2d.dwt2d_to_pywt_coeffs()

  4. lazylinop.signal2d.dwt2d_coeffs_shapes()

  5. lazylinop.colvec()

  6. lazylinop.uncolvec()

lazylinop.signal2d.flatten(X)#

Returns a flatten version of X along its first two dimensions.

This utility function is provided to flatten an image or a batch of images to be used as input of LazyLinOp signal2d’s functions.

Args:

X: array with 2 dimensions for a single image or 3 dimensions for a batch of images.

Returns:

Flattened version of X.

Examples:
>>> import numpy as np
>>> from lazylinop.signal2d import flatten
>>> X = np.arange(90).reshape(3, 3, 10)
>>> X.shape
(3, 3, 10)
>>> flatten(X).shape
(9, 10)
lazylinop.signal2d.unflatten(X, shape)#

Returns a un-flatten version of X, where its first dimension is expanded on two dimensions with shape shape.

This utility function is provided to un-flatten an image or a batch of images as returned by LazyLinOp signal2d’s functions, to be manipulated as usual 2D images.

Args:

X: array with 1 dimension for a single image or 2 dimensions for a batch of images. shape: the 2D dimensions of image.

Returns:

Un-flattened version of X

Examples:
>>> import numpy as np
>>> from lazylinop.signal2d import unflatten
>>> X = np.arange(90).reshape(9, 10)
>>> X.shape
(9, 10)
>>> unflatten(X, (3, 3)).shape
(3, 3, 10)
lazylinop.signal2d.dwt2d_to_pywt_coeffs(x, in_shape, wavelet='haar', level=None, mode='zero')#

Returns Pywavelets compatible [cAn, (cHn, cVn, cDn), ..., (cH1, cV1, cD1)] built from the 1d array x of flattened coefficients [cAn, cHn, cVn, cDn, ..., cH1, cV1, cD1] where n is the decomposition level.

Args:
x: np.ndarray

List of coefficients [cAn, cHn, cVn, cDn, ..., cH1, cV1, cD1].

in_shape, wavelet, level, mode:

See dwt2d() for more details.

Returns:

Pywavelets compatible list [cAn, (cHn, cVn, cDn), ..., (cH1, cV1, cD1)].

Examples:
>>> import numpy as np
>>> from lazylinop.signal2d import dwt2d, colvec, dwt2d_to_pywt_coeffs
>>> import pywt
>>> M, N = 5, 6
>>> x = np.arange(M * N).reshape(M, N)
>>> L = dwt2d((M, N), wavelet='haar', level=2, mode='zero')
>>> y = L @ colvec(x)
>>> y = dwt2d_to_pywt_coeffs(y, (M, N), 'haar', level=2, mode='zero')
>>> z = pywt.wavedec2(x, wavelet='haar', level=2, mode='zero')
>>> np.allclose(y[0], z[0])
True
>>> np.allclose(y[0][0], z[0][0])
True
lazylinop.signal2d.dwt2d_coeffs_shapes(in_shape, wavelet='haar', level=None, mode='zero')#

Return a list of tuple that gives the shape of the flattened coefficients [cAn, cHn, cVn, cDn, ..., cH1, cV1, cD1].

Args:
in_shape, wavelet, level, mode:

See dwt2d() for more details.

Returns:

list of tuple.

Examples:
>>> from lazylinop.signal2d import dwt2d_coeffs_shapes
>>> dwt2d_coeffs_shapes((5, 6), 'haar', level=2)
[(2, 2), (2, 2), (2, 2), (2, 2), (3, 3), (3, 3), (3, 3)]
lazylinop.signal2d.colvec(X)#

Returns a flatten version of X along its first two dimensions. Importantly here, colvec(X) stacks the columns of X.

This utility function is provided to flatten an image or a batch of images to be used as input of LazyLinOp signal2d’s functions.

Args:
X: np.ndarray

Array with 2 dimensions for a single image or 3 dimensions for a batch of images.

Returns:

Stack of columns version of X (np.ndarray).

Examples:
>>> import numpy as np
>>> from lazylinop.signal2d import colvec
>>> X = np.arange(90).reshape(3, 3, 10)
>>> X.shape
(3, 3, 10)
>>> colvec(X).shape
(9, 10)
lazylinop.signal2d.uncolvec(X, shape)#

Returns a un-flatten version of X (assuming X has been obtained using colvec function), where its first dimension is expanded on two dimensions with shape shape.

This utility function is provided to un-flatten an image or a batch of images as returned by LazyLinOp signal2d’s functions, to be manipulated as usual 2D images.

If x = colvec(X) is the stack of columns of X, y = uncolvec(x, X.shape) is equal to X.

Args:
X: np.ndarray

Array with 1 dimension for a single image or 2 dimensions for a batch of images.

shape: tuple

The 2D dimensions of image.

Returns:

Un-flattened version of X (np.ndarray).

Examples:
>>> import numpy as np
>>> from lazylinop.signal2d import colvec, uncolvec
>>> X = np.arange(90).reshape(9, 10)
>>> X.shape
(9, 10)
>>> uncolvec(X, (3, 3)).shape
(3, 3, 10)
>>> # Check that X = uncolvec(colvec(X), shape=X.shape).
>>> X = np.arange(2 * 3).reshape(2, 3)
>>> X
array([[0, 1, 2],
       [3, 4, 5]])
>>> x = colvec(X)
>>> Y = uncolvec(x, shape=X.shape)
>>> np.allclose(X, Y)
True