def upsample(image, method=None): """Specialised function to upsample an image by a factor of two using a specified sampling method. If *image* is an array of shape (NxMx...) then the output will have shape (2Nx2Mx...). Only rows and columns are upsampled, depth axes and greater are interpolated but are not upsampled. :param image: an array containing the image to upsample :param method: if non-None, a string specifying the sampling method to use. If *method* is ``None``, the default sampling method ``'lanczos'`` is used. The following sampling methods are supported: =========== =========== Name Description =========== =========== nearest Nearest-neighbour sampling bilinear Bilinear sampling lanczos Lanczos sampling with window radius of 3 =========== =========== """ image = np.atleast_2d(asfarray(image)) # The default '.T' operator doesn't quite do what we want since it # reverses the axes rather than only swapping the first two def _t(X): axes = np.arange(len(X.shape)) axes[:2] = (1,0) return np.transpose(X, axes) return _upsample_columns(_t(_upsample_columns(_t(image), method)), method)
def upsample(image, method=None): """Specialised function to upsample an image by a factor of two using a specified sampling method. If *image* is an array of shape (NxMx...) then the output will have shape (2Nx2Mx...). Only rows and columns are upsampled, depth axes and greater are interpolated but are not upsampled. :param image: an array containing the image to upsample :param method: if non-None, a string specifying the sampling method to use. If *method* is ``None``, the default sampling method ``'lanczos'`` is used. The following sampling methods are supported: =========== =========== Name Description =========== =========== nearest Nearest-neighbour sampling bilinear Bilinear sampling lanczos Lanczos sampling with window radius of 3 =========== =========== """ image = np.atleast_2d(asfarray(image)) # The default '.T' operator doesn't quite do what we want since it # reverses the axes rather than only swapping the first two def _t(X): axes = np.arange(len(X.shape)) axes[:2] = (1, 0) return np.transpose(X, axes) return _upsample_columns(_t(_upsample_columns(_t(image), method)), method)
def _upsample_columns(X, method=None): """ The centre of columns of X, an M-columned matrix, are assumed to have co-ordinates { 0, 1, 2, ... , M-1 } which means that the up-sampled matrix's columns should sample from { -0.25, 0.25, 0.75, ... , M-1.25 }. We can view that as an interleaved set of teo *convolutions* of X. The first, A, using a kernel equivalent to sampling the { -0.25, 0.75, 1.75, 2.75, ... M-1.25 } columns and the second, B, sampling the { 0.25, 1.25, ... , M-0.75 } columns. """ if method is None: method = 'lanczos' X = np.atleast_2d(asfarray(X)) out_shape = list(X.shape) out_shape[1] *= 2 output = np.zeros(out_shape, dtype=X.dtype) # Centres of sampling for A and B convolutions M = X.shape[1] A_columns = np.linspace(-0.25, M-1.25, M) B_columns = A_columns + 0.5 # For A columns sample at x = ceil(x) - 0.25 with ceil(x) = { 0, 1, 2, ..., M-1 } # For B columns sample at x = floor(x) + 0.25 with floor(x) = { 0, 1, 2, ..., M-1 } int_columns = np.linspace(0, M-1, M) if method == 'lanczos': # Lanczos kernel width a = 3.0 sample_offsets = np.arange(-a, a+1) # For A: if i = ceil(x) + di, => ceil(x) - i = -0.25 - di # For B: if i = floor(x) + di, => floor(x) - i = 0.25 - di l_as = np.sinc(-0.25-sample_offsets)*np.sinc((-0.25-sample_offsets)/a) l_bs = np.sinc(0.25-sample_offsets)*np.sinc((0.25-sample_offsets)/a) elif method == 'nearest': # Nearest neighbour kernel width is 1 sample_offsets = [0,] l_as = l_bs = [1,] elif method == 'bilinear': # Bilinear kernel width is technically 2 but we need to offset the kernels differently # for A and B columns: sample_offsets = [-1,0,1] l_as = [0.25, 0.75, 0] l_bs = [0, 0.75, 0.25] else: raise ValueError('Unknown interpolation mode: {0}'.format(mode)) # Convolve for di, l_a, l_b in zip(sample_offsets, l_as, l_bs): columns = reflect(int_columns + di, -0.5, M-0.5).astype(np.int) output[:,0::2,...] += l_a * X[:,columns,...] output[:,1::2,...] += l_b * X[:,columns,...] return output
def upsample_highpass(im, method=None): """As :py:func:`upsample` except that the highpass image is first phase rolled so that the filter has approximate DC centre frequency. The upshot is that this is the function to use when re-sampling complex subband images. """ im = np.atleast_2d(asfarray(im)) # Sampled co-ordinates dxs, dys = np.meshgrid(np.arange(im.shape[1]*2), np.arange(im.shape[0]*2)) sxs = 0.5 * (dxs + 0.5) - 0.5 sys = 0.5 * (dys + 0.5) - 0.5 # phase unwrap X, Y = np.meshgrid(np.arange(im.shape[1]), np.arange(im.shape[0])) im_unwrap = im * _phase_image(X, Y, True) # sample im_sampled = upsample(im_unwrap, method) # re-wrap return im_sampled * _phase_image(sxs, sys, False)
def upsample_highpass(im, method=None): """As :py:func:`upsample` except that the highpass image is first phase rolled so that the filter has approximate DC centre frequency. The upshot is that this is the function to use when re-sampling complex subband images. """ im = np.atleast_2d(asfarray(im)) # Sampled co-ordinates dxs, dys = np.meshgrid(np.arange(im.shape[1] * 2), np.arange(im.shape[0] * 2)) sxs = 0.5 * (dxs + 0.5) - 0.5 sys = 0.5 * (dys + 0.5) - 0.5 # phase unwrap X, Y = np.meshgrid(np.arange(im.shape[1]), np.arange(im.shape[0])) im_unwrap = im * _phase_image(X, Y, True) # sample im_sampled = upsample(im_unwrap, method) # re-wrap return im_sampled * _phase_image(sxs, sys, False)
def dtwavexfm(X, nlevels=3, biort=DEFAULT_BIORT, qshift=DEFAULT_QSHIFT, include_scale=False): """Perform a *n*-level DTCWT decompostion on a 1D column vector *X* (or on the columns of a matrix *X*). :param X: 1D real array or 2D real array whose columns are to be transformed :param nlevels: Number of levels of wavelet decomposition :param biort: Level 1 wavelets to use. See :py:func:`biort`. :param qshift: Level >= 2 wavelets to use. See :py:func:`qshift`. :returns Yl: The real lowpass image from the final level :returns Yh: A tuple containing the (N, M, 6) shape complex highpass subimages for each level. :returns Yscale: If *include_scale* is True, a tuple containing real lowpass coefficients for every scale. If *biort* or *qshift* are strings, they are used as an argument to the :py:func:`biort` or :py:func:`qshift` functions. Otherwise, they are interpreted as tuples of vectors giving filter coefficients. In the *biort* case, this should be (h0o, g0o, h1o, g1o). In the *qshift* case, this should be (h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b). Example:: # Performs a 5-level transform on the real image X using the 13,19-tap # filters for level 1 and the Q-shift 14-tap filters for levels >= 2. Yl, Yh = dtwavexfm(X,5,'near_sym_b','qshift_b') .. codeauthor:: Rich Wareham <*****@*****.**>, Aug 2013 .. codeauthor:: Nick Kingsbury, Cambridge University, May 2002 .. codeauthor:: Cian Shaffrey, Cambridge University, May 2002 """ # Need this because colfilter and friends assumes input is 2d X = asfarray(X) if len(X.shape) == 1: X = np.atleast_2d(X).T # Try to load coefficients if biort is a string parameter try: h0o, g0o, h1o, g1o = _biort(biort) except TypeError: h0o, g0o, h1o, g1o = biort # Try to load coefficients if qshift is a string parameter try: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b = _qshift(qshift) except TypeError: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b = qshift L = np.asanyarray(X.shape) # ensure that X is an even length, thus enabling it to be extended if needs be. if X.shape[0] % 2 != 0: raise ValueError('Size of input X must be a multiple of 2') if nlevels == 0: if include_scale: return X, (), () else: return X, () # initialise Yh = [ None, ] * nlevels if include_scale: # This is only required if the user specifies scales are to be outputted Yscale = [ None, ] * nlevels # Level 1. Hi = colfilter(X, h1o) Lo = colfilter(X, h0o) Yh[0] = Hi[::2, :] + 1j * Hi[1::2, :] # Convert Hi to complex form. if include_scale: Yscale[0] = Lo # Levels 2 and above. for level in xrange(1, nlevels): # Check to see if height of Lo is divisable by 4, if not extend. if Lo.shape[0] % 4 != 0: Lo = np.vstack((Lo[0, :], Lo, Lo[-1, :])) Hi = coldfilt(Lo, h1b, h1a) Lo = coldfilt(Lo, h0b, h0a) Yh[level] = Hi[::2, :] + 1j * Hi[1:: 2, :] # Convert Hi to complex form. if include_scale: Yscale[level] = Lo Yl = Lo if include_scale: return Yl, Yh, Yscale else: return Yl, Yh
def dtwavexfm3(X, nlevels=3, biort=DEFAULT_BIORT, qshift=DEFAULT_QSHIFT, ext_mode=4, discard_level_1=False): """Perform a *n*-level DTCWT-3D decompostion on a 3D matrix *X*. :param X: 3D real array-like object :param nlevels: Number of levels of wavelet decomposition :param biort: Level 1 wavelets to use. See :py:func:`biort`. :param qshift: Level >= 2 wavelets to use. See :py:func:`qshift`. :param ext_mode: Extension mode. See below. :param discard_level_1: True if level 1 high-pass bands are to be discarded. :returns Yl: The real lowpass image from the final level :returns Yh: A tuple containing the complex highpass subimages for each level. Each element of *Yh* is a 4D complex array with the 4th dimension having size 28. The 3D slice ``Yh[l][:,:,:,d]`` corresponds to the complex higpass coefficients for direction d at level l where d and l are both 0-indexed. If *biort* or *qshift* are strings, they are used as an argument to the :py:func:`biort` or :py:func:`qshift` functions. Otherwise, they are interpreted as tuples of vectors giving filter coefficients. In the *biort* case, this should be (h0o, g0o, h1o, g1o). In the *qshift* case, this should be (h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b). There are two values for *ext_mode*, either 4 or 8. If *ext_mode* = 4, check whether 1st level is divisible by 2 (if not we raise a ``ValueError``). Also check whether from 2nd level onwards, the coefs can be divided by 4. If any dimension size is not a multiple of 4, append extra coefs by repeating the edges. If *ext_mode* = 8, check whether 1st level is divisible by 4 (if not we raise a ``ValueError``). Also check whether from 2nd level onwards, the coeffs can be divided by 8. If any dimension size is not a multiple of 8, append extra coeffs by repeating the edges twice. If *discard_level_1* is True the highpass coefficients at level 1 will be discarded. (And, in fact, will never be calculated.) This turns the transform from being 8:1 redundant to being 1:1 redundant at the cost of no-longer allowing perfect reconstruction. If this option is selected then `Yh[0]` will be `None`. Note that :py:func:`dtwaveifm3` will accepts `Yh[0]` being `None` and will treat it as being zero. Example:: # Performs a 3-level transform on the real 3D array X using the 13,19-tap # filters for level 1 and the Q-shift 14-tap filters for levels >= 2. Yl, Yh = dtwavexfm3(X, 3, 'near_sym_b', 'qshift_b') .. codeauthor:: Rich Wareham <*****@*****.**>, Aug 2013 .. codeauthor:: Huizhong Chen, Jan 2009 .. codeauthor:: Nick Kingsbury, Cambridge University, July 1999. """ X = np.atleast_3d(asfarray(X)) # Try to load coefficients if biort is a string parameter try: h0o, g0o, h1o, g1o = _biort(biort) except TypeError: h0o, g0o, h1o, g1o = biort # Try to load coefficients if qshift is a string parameter try: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b = _qshift(qshift) except TypeError: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b = qshift # Check value of ext_mode. TODO: this should really be an enum :S if ext_mode != 4 and ext_mode != 8: raise ValueError('ext_mode must be one of 4 or 8') Yl = X Yh = [None,] * nlevels # level is 0-indexed for level in xrange(nlevels): # Transform if level == 0 and discard_level_1: Yl = _level1_xfm_no_highpass(Yl, h0o, h1o, ext_mode) elif level == 0 and not discard_level_1: Yl, Yh[level] = _level1_xfm(Yl, h0o, h1o, ext_mode) else: Yl, Yh[level] = _level2_xfm(Yl, h0a, h0b, h1a, h1b, ext_mode) return Yl, tuple(Yh)
def dtwavexfm(X, nlevels=3, biort=DEFAULT_BIORT, qshift=DEFAULT_QSHIFT, include_scale=False): """Perform a *n*-level DTCWT decompostion on a 1D column vector *X* (or on the columns of a matrix *X*). :param X: 1D real array or 2D real array whose columns are to be transformed :param nlevels: Number of levels of wavelet decomposition :param biort: Level 1 wavelets to use. See :py:func:`biort`. :param qshift: Level >= 2 wavelets to use. See :py:func:`qshift`. :returns Yl: The real lowpass image from the final level :returns Yh: A tuple containing the (N, M, 6) shape complex highpass subimages for each level. :returns Yscale: If *include_scale* is True, a tuple containing real lowpass coefficients for every scale. If *biort* or *qshift* are strings, they are used as an argument to the :py:func:`biort` or :py:func:`qshift` functions. Otherwise, they are interpreted as tuples of vectors giving filter coefficients. In the *biort* case, this should be (h0o, g0o, h1o, g1o). In the *qshift* case, this should be (h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b). Example:: # Performs a 5-level transform on the real image X using the 13,19-tap # filters for level 1 and the Q-shift 14-tap filters for levels >= 2. Yl, Yh = dtwavexfm(X,5,'near_sym_b','qshift_b') .. codeauthor:: Rich Wareham <*****@*****.**>, Aug 2013 .. codeauthor:: Nick Kingsbury, Cambridge University, May 2002 .. codeauthor:: Cian Shaffrey, Cambridge University, May 2002 """ # Need this because colfilter and friends assumes input is 2d X = asfarray(X) if len(X.shape) == 1: X = np.atleast_2d(X).T # Try to load coefficients if biort is a string parameter try: h0o, g0o, h1o, g1o = _biort(biort) except TypeError: h0o, g0o, h1o, g1o = biort # Try to load coefficients if qshift is a string parameter try: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b = _qshift(qshift) except TypeError: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b = qshift L = np.asanyarray(X.shape) # ensure that X is an even length, thus enabling it to be extended if needs be. if X.shape[0] % 2 != 0: raise ValueError('Size of input X must be a multiple of 2') if nlevels == 0: if include_scale: return X, (), () else: return X, () # initialise Yh = [None,] * nlevels if include_scale: # This is only required if the user specifies scales are to be outputted Yscale = [None,] * nlevels # Level 1. Hi = colfilter(X, h1o) Lo = colfilter(X, h0o) Yh[0] = Hi[::2,:] + 1j*Hi[1::2,:] # Convert Hi to complex form. if include_scale: Yscale[0] = Lo # Levels 2 and above. for level in xrange(1, nlevels): # Check to see if height of Lo is divisable by 4, if not extend. if Lo.shape[0] % 4 != 0: Lo = np.vstack((Lo[0,:], Lo, Lo[-1,:])) Hi = coldfilt(Lo,h1b,h1a) Lo = coldfilt(Lo,h0b,h0a) Yh[level] = Hi[::2,:] + 1j*Hi[1::2,:] # Convert Hi to complex form. if include_scale: Yscale[level] = Lo Yl = Lo if include_scale: return Yl, Yh, Yscale else: return Yl, Yh
def _upsample_columns(X, method=None): """ The centre of columns of X, an M-columned matrix, are assumed to have co-ordinates { 0, 1, 2, ... , M-1 } which means that the up-sampled matrix's columns should sample from { -0.25, 0.25, 0.75, ... , M-1.25 }. We can view that as an interleaved set of teo *convolutions* of X. The first, A, using a kernel equivalent to sampling the { -0.25, 0.75, 1.75, 2.75, ... M-1.25 } columns and the second, B, sampling the { 0.25, 1.25, ... , M-0.75 } columns. """ if method is None: method = 'lanczos' X = np.atleast_2d(asfarray(X)) out_shape = list(X.shape) out_shape[1] *= 2 output = np.zeros(out_shape, dtype=X.dtype) # Centres of sampling for A and B convolutions M = X.shape[1] A_columns = np.linspace(-0.25, M - 1.25, M) B_columns = A_columns + 0.5 # For A columns sample at x = ceil(x) - 0.25 with ceil(x) = { 0, 1, 2, ..., M-1 } # For B columns sample at x = floor(x) + 0.25 with floor(x) = { 0, 1, 2, ..., M-1 } int_columns = np.linspace(0, M - 1, M) if method == 'lanczos': # Lanczos kernel width a = 3.0 sample_offsets = np.arange(-a, a + 1) # For A: if i = ceil(x) + di, => ceil(x) - i = -0.25 - di # For B: if i = floor(x) + di, => floor(x) - i = 0.25 - di l_as = np.sinc(-0.25 - sample_offsets) * np.sinc( (-0.25 - sample_offsets) / a) l_bs = np.sinc(0.25 - sample_offsets) * np.sinc( (0.25 - sample_offsets) / a) elif method == 'nearest': # Nearest neighbour kernel width is 1 sample_offsets = [ 0, ] l_as = l_bs = [ 1, ] elif method == 'bilinear': # Bilinear kernel width is technically 2 but we need to offset the kernels differently # for A and B columns: sample_offsets = [-1, 0, 1] l_as = [0.25, 0.75, 0] l_bs = [0, 0.75, 0.25] else: raise ValueError('Unknown interpolation mode: {0}'.format(mode)) # Convolve for di, l_a, l_b in zip(sample_offsets, l_as, l_bs): columns = reflect(int_columns + di, -0.5, M - 0.5).astype(np.int) output[:, 0::2, ...] += l_a * X[:, columns, ...] output[:, 1::2, ...] += l_b * X[:, columns, ...] return output
def dtwavexfm2(X, nlevels=3, biort=DEFAULT_BIORT, qshift=DEFAULT_QSHIFT, include_scale=False): """Perform a *n*-level DTCWT-2D decompostion on a 2D matrix *X*. :param X: 2D real array :param nlevels: Number of levels of wavelet decomposition :param biort: Level 1 wavelets to use. See :py:func:`biort`. :param qshift: Level >= 2 wavelets to use. See :py:func:`qshift`. :returns Yl: The real lowpass image from the final level :returns Yh: A tuple containing the complex highpass subimages for each level. :returns Yscale: If *include_scale* is True, a tuple containing real lowpass coefficients for every scale. If *biort* or *qshift* are strings, they are used as an argument to the :py:func:`biort` or :py:func:`qshift` functions. Otherwise, they are interpreted as tuples of vectors giving filter coefficients. In the *biort* case, this should be (h0o, g0o, h1o, g1o). In the *qshift* case, this should be (h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b). Example:: # Performs a 3-level transform on the real image X using the 13,19-tap # filters for level 1 and the Q-shift 14-tap filters for levels >= 2. Yl, Yh = dtwavexfm2(X, 3, 'near_sym_b', 'qshift_b') .. codeauthor:: Rich Wareham <*****@*****.**>, Aug 2013 .. codeauthor:: Nick Kingsbury, Cambridge University, Sept 2001 .. codeauthor:: Cian Shaffrey, Cambridge University, Sept 2001 """ X = np.atleast_2d(asfarray(X)) # Try to load coefficients if biort is a string parameter try: h0o, g0o, h1o, g1o = _biort(biort) except TypeError: h0o, g0o, h1o, g1o = biort # Try to load coefficients if qshift is a string parameter try: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b = _qshift(qshift) except TypeError: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b = qshift original_size = X.shape if len(X.shape) >= 3: raise ValueError('The entered image is {0}, please enter each image slice separately.'. format('x'.join(list(str(s) for s in X.shape)))) # The next few lines of code check to see if the image is odd in size, if so an extra ... # row/column will be added to the bottom/right of the image initial_row_extend = 0 #initialise initial_col_extend = 0 if original_size[0] % 2 != 0: # if X.shape[0] is not divisable by 2 then we need to extend X by adding a row at the bottom X = np.vstack((X, X[[-1],:])) # Any further extension will be done in due course. initial_row_extend = 1 if original_size[1] % 2 != 0: # if X.shape[1] is not divisable by 2 then we need to extend X by adding a col to the left X = np.hstack((X, X[:,[-1]])) initial_col_extend = 1 extended_size = X.shape if nlevels == 0: if include_scale: return X, (), () else: return X, () # initialise Yh = [None,] * nlevels if include_scale: # this is only required if the user specifies a third output component. Yscale = [None,] * nlevels complex_dtype = appropriate_complex_type_for(X) if nlevels >= 1: # Do odd top-level filters on cols. Lo = colfilter(X,h0o).T Hi = colfilter(X,h1o).T # Do odd top-level filters on rows. LoLo = colfilter(Lo,h0o).T Yh[0] = np.zeros((LoLo.shape[0] >> 1, LoLo.shape[1] >> 1, 6), dtype=complex_dtype) Yh[0][:,:,[0, 5]] = q2c(colfilter(Hi,h0o).T) # Horizontal pair Yh[0][:,:,[2, 3]] = q2c(colfilter(Lo,h1o).T) # Vertical pair Yh[0][:,:,[1, 4]] = q2c(colfilter(Hi,h1o).T) # Diagonal pair if include_scale: Yscale[0] = LoLo for level in xrange(1, nlevels): row_size, col_size = LoLo.shape if row_size % 4 != 0: # Extend by 2 rows if no. of rows of LoLo are not divisable by 4 LoLo = np.vstack((LoLo[[0],:], LoLo, LoLo[[-1],:])) if col_size % 4 != 0: # Extend by 2 cols if no. of cols of LoLo are not divisable by 4 LoLo = np.hstack((LoLo[:,[0]], LoLo, LoLo[:,[-1]])) # Do even Qshift filters on rows. Lo = coldfilt(LoLo,h0b,h0a).T Hi = coldfilt(LoLo,h1b,h1a).T # Do even Qshift filters on columns. LoLo = coldfilt(Lo,h0b,h0a).T Yh[level] = np.zeros((LoLo.shape[0]>>1, LoLo.shape[1]>>1, 6), dtype=complex_dtype) Yh[level][:,:,[0, 5]] = q2c(coldfilt(Hi,h0b,h0a).T) # Horizontal Yh[level][:,:,[2, 3]] = q2c(coldfilt(Lo,h1b,h1a).T) # Vertical Yh[level][:,:,[1, 4]] = q2c(coldfilt(Hi,h1b,h1a).T) # Diagonal if include_scale: Yscale[0] = LoLo Yl = LoLo if initial_row_extend == 1 and initial_col_extend == 1: logging.warn('The image entered is now a {0} NOT a {1}.'.format( 'x'.join(list(str(s) for s in extended_size)), 'x'.join(list(str(s) for s in original_size)))) logging.warn( 'The bottom row and rightmost column have been duplicated, prior to decomposition.') if initial_row_extend == 1 and initial_col_extend == 0: logging.warn('The image entered is now a {0} NOT a {1}.'.format( 'x'.join(list(str(s) for s in extended_size)), 'x'.join(list(str(s) for s in original_size)))) logging.warn( 'The bottom row has been duplicated, prior to decomposition.') if initial_row_extend == 0 and initial_col_extend == 1: logging.warn('The image entered is now a {0} NOT a {1}.'.format( 'x'.join(list(str(s) for s in extended_size)), 'x'.join(list(str(s) for s in original_size)))) logging.warn( 'The rightmost column has been duplicated, prior to decomposition.') if include_scale: return Yl, tuple(Yh), tuple(Yscale) else: return Yl, tuple(Yh)
def dtwavexfm2(X, nlevels=3, biort=DEFAULT_BIORT, qshift=DEFAULT_QSHIFT, include_scale=False): """Perform a *n*-level DTCWT-2D decompostion on a 2D matrix *X*. :param X: 2D real array :param nlevels: Number of levels of wavelet decomposition :param biort: Level 1 wavelets to use. See :py:func:`biort`. :param qshift: Level >= 2 wavelets to use. See :py:func:`qshift`. :returns Yl: The real lowpass image from the final level :returns Yh: A tuple containing the complex highpass subimages for each level. :returns Yscale: If *include_scale* is True, a tuple containing real lowpass coefficients for every scale. If *biort* or *qshift* are strings, they are used as an argument to the :py:func:`biort` or :py:func:`qshift` functions. Otherwise, they are interpreted as tuples of vectors giving filter coefficients. In the *biort* case, this should be (h0o, g0o, h1o, g1o). In the *qshift* case, this should be (h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b). Example:: # Performs a 3-level transform on the real image X using the 13,19-tap # filters for level 1 and the Q-shift 14-tap filters for levels >= 2. Yl, Yh = dtwavexfm2(X, 3, 'near_sym_b', 'qshift_b') .. codeauthor:: Rich Wareham <*****@*****.**>, Aug 2013 .. codeauthor:: Nick Kingsbury, Cambridge University, Sept 2001 .. codeauthor:: Cian Shaffrey, Cambridge University, Sept 2001 """ X = np.atleast_2d(asfarray(X)) # Try to load coefficients if biort is a string parameter try: h0o, g0o, h1o, g1o = _biort(biort) except TypeError: h0o, g0o, h1o, g1o = biort # Try to load coefficients if qshift is a string parameter try: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b = _qshift(qshift) except TypeError: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b = qshift original_size = X.shape if len(X.shape) >= 3: raise ValueError( "The entered image is {0}, please enter each image slice separately.".format( "x".join(list(str(s) for s in X.shape)) ) ) # The next few lines of code check to see if the image is odd in size, if so an extra ... # row/column will be added to the bottom/right of the image initial_row_extend = 0 # initialise initial_col_extend = 0 if original_size[0] % 2 != 0: # if X.shape[0] is not divisable by 2 then we need to extend X by adding a row at the bottom X = np.vstack((X, X[[-1], :])) # Any further extension will be done in due course. initial_row_extend = 1 if original_size[1] % 2 != 0: # if X.shape[1] is not divisable by 2 then we need to extend X by adding a col to the left X = np.hstack((X, X[:, [-1]])) initial_col_extend = 1 extended_size = X.shape if nlevels == 0: if include_scale: return X, (), () else: return X, () # initialise Yh = [None] * nlevels if include_scale: # this is only required if the user specifies a third output component. Yscale = [None] * nlevels complex_dtype = appropriate_complex_type_for(X) if nlevels >= 1: # Do odd top-level filters on cols. Lo = colfilter(X, h0o).T Hi = colfilter(X, h1o).T # Do odd top-level filters on rows. LoLo = colfilter(Lo, h0o).T Yh[0] = np.zeros((LoLo.shape[0] >> 1, LoLo.shape[1] >> 1, 6), dtype=complex_dtype) Yh[0][:, :, [0, 5]] = q2c(colfilter(Hi, h0o).T) # Horizontal pair Yh[0][:, :, [2, 3]] = q2c(colfilter(Lo, h1o).T) # Vertical pair Yh[0][:, :, [1, 4]] = q2c(colfilter(Hi, h1o).T) # Diagonal pair if include_scale: Yscale[0] = LoLo for level in xrange(1, nlevels): row_size, col_size = LoLo.shape if row_size % 4 != 0: # Extend by 2 rows if no. of rows of LoLo are not divisable by 4 LoLo = np.vstack((LoLo[[0], :], LoLo, LoLo[[-1], :])) if col_size % 4 != 0: # Extend by 2 cols if no. of cols of LoLo are not divisable by 4 LoLo = np.hstack((LoLo[:, [0]], LoLo, LoLo[:, [-1]])) # Do even Qshift filters on rows. Lo = coldfilt(LoLo, h0b, h0a).T Hi = coldfilt(LoLo, h1b, h1a).T # Do even Qshift filters on columns. LoLo = coldfilt(Lo, h0b, h0a).T Yh[level] = np.zeros((LoLo.shape[0] >> 1, LoLo.shape[1] >> 1, 6), dtype=complex_dtype) Yh[level][:, :, [0, 5]] = q2c(coldfilt(Hi, h0b, h0a).T) # Horizontal Yh[level][:, :, [2, 3]] = q2c(coldfilt(Lo, h1b, h1a).T) # Vertical Yh[level][:, :, [1, 4]] = q2c(coldfilt(Hi, h1b, h1a).T) # Diagonal if include_scale: Yscale[0] = LoLo Yl = LoLo if initial_row_extend == 1 and initial_col_extend == 1: logging.warn( "The image entered is now a {0} NOT a {1}.".format( "x".join(list(str(s) for s in extended_size)), "x".join(list(str(s) for s in original_size)) ) ) logging.warn("The bottom row and rightmost column have been duplicated, prior to decomposition.") if initial_row_extend == 1 and initial_col_extend == 0: logging.warn( "The image entered is now a {0} NOT a {1}.".format( "x".join(list(str(s) for s in extended_size)), "x".join(list(str(s) for s in original_size)) ) ) logging.warn("The bottom row has been duplicated, prior to decomposition.") if initial_row_extend == 0 and initial_col_extend == 1: logging.warn( "The image entered is now a {0} NOT a {1}.".format( "x".join(list(str(s) for s in extended_size)), "x".join(list(str(s) for s in original_size)) ) ) logging.warn("The rightmost column has been duplicated, prior to decomposition.") if include_scale: return Yl, tuple(Yh), tuple(Yscale) else: return Yl, tuple(Yh)