def coldfilt(X, ha, hb, queue=None): """Filter the columns of image X using the two filters ha and hb = reverse(ha). ha operates on the odd samples of X and hb on the even samples. Both filters should be even length, and h should be approx linear phase with a quarter sample advance from its mid pt (i.e. :math:`|h(m/2)| > |h(m/2 + 1)|`). .. code-block:: text ext top edge bottom edge ext Level 1: ! | ! | ! odd filt on . b b b b a a a a a a a a b b b b odd filt on . a a a a b b b b b b b b a a a a Level 2: ! | ! | ! +q filt on x b b a a a a b b -q filt on o a a b b b b a a The output is decimated by two from the input sample rate and the results from the two filters, Ya and Yb, are interleaved to give Y. Symmetric extension with repeated end samples is used on the composite X columns before each filter is applied. Raises ValueError if the number of rows in X is not a multiple of 4, the length of ha does not match hb or the lengths of ha or hb are non-even. .. codeauthor:: Rich Wareham <*****@*****.**>, August 2013 .. codeauthor:: Cian Shaffrey, Cambridge University, August 2000 .. codeauthor:: Nick Kingsbury, Cambridge University, August 2000 """ queue = to_queue(queue) # Make sure all inputs are arrays X = asfarray(X) ha = asfarray(ha) hb = asfarray(hb) r, c = X.shape if r % 4 != 0: raise ValueError('No. of rows in X must be a multiple of 4') if ha.shape != hb.shape: raise ValueError('Shapes of ha and hb must be the same') if ha.shape[0] % 2 != 0: raise ValueError('Lengths of ha and hb must be even') # Perform filtering on columns of extended matrix X(xe,:) in 4 ways. Y = axis_convolve_dfilter(X, ha, queue=queue) return to_array(Y)
def coldfilt(X, ha, hb, queue=None): """Filter the columns of image X using the two filters ha and hb = reverse(ha). ha operates on the odd samples of X and hb on the even samples. Both filters should be even length, and h should be approx linear phase with a quarter sample advance from its mid pt (i.e. :math:`|h(m/2)| > |h(m/2 + 1)|`). .. code-block:: text ext top edge bottom edge ext Level 1: ! | ! | ! odd filt on . b b b b a a a a a a a a b b b b odd filt on . a a a a b b b b b b b b a a a a Level 2: ! | ! | ! +q filt on x b b a a a a b b -q filt on o a a b b b b a a The output is decimated by two from the input sample rate and the results from the two filters, Ya and Yb, are interleaved to give Y. Symmetric extension with repeated end samples is used on the composite X columns before each filter is applied. Raises ValueError if the number of rows in X is not a multiple of 4, the length of ha does not match hb or the lengths of ha or hb are non-even. .. codeauthor:: Rich Wareham <*****@*****.**>, August 2013 .. codeauthor:: Cian Shaffrey, Cambridge University, August 2000 .. codeauthor:: Nick Kingsbury, Cambridge University, August 2000 """ queue = to_queue(queue) # Make sure all inputs are arrays X = asfarray(X) ha = asfarray(ha) hb = asfarray(hb) r, c = X.shape if r % 4 != 0: raise ValueError("No. of rows in X must be a multiple of 4") if ha.shape != hb.shape: raise ValueError("Shapes of ha and hb must be the same") if ha.shape[0] % 2 != 0: raise ValueError("Lengths of ha and hb must be even") # Perform filtering on columns of extended matrix X(xe,:) in 4 ways. Y = axis_convolve_dfilter(X, ha, queue=queue) return to_array(Y)
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 colfilter(X, h): """Filter the columns of image *X* using filter vector *h*, without decimation. If len(h) is odd, each output sample is aligned with each input sample and *Y* is the same size as *X*. If len(h) is even, each output sample is aligned with the mid point of each pair of input samples, and Y.shape = X.shape + [1 0]. :param X: an image whose columns are to be filtered :param h: the filter coefficients. :returns Y: the filtered image. .. codeauthor:: Rich Wareham <*****@*****.**>, August 2013 .. codeauthor:: Cian Shaffrey, Cambridge University, August 2000 .. codeauthor:: Nick Kingsbury, Cambridge University, August 2000 """ # Interpret all inputs as arrays X = asfarray(X) h = as_column_vector(h) r, c = X.shape m = h.shape[0] m2 = np.fix(m * 0.5) # Symmetrically extend with repeat of end samples. # Use 'reflect' so r < m2 works OK. xe = reflect(np.arange(-m2, r + m2, dtype=np.int), -0.5, r - 0.5) # Perform filtering on the columns of the extended matrix X(xe,:), keeping # only the 'valid' output samples, so Y is the same size as X if m is odd. Y = _column_convolve(X[xe, :], h) return Y
def colfilter(X, h): """Filter the columns of image *X* using filter vector *h*, without decimation. If len(h) is odd, each output sample is aligned with each input sample and *Y* is the same size as *X*. If len(h) is even, each output sample is aligned with the mid point of each pair of input samples, and Y.shape = X.shape + [1 0]. :param X: an image whose columns are to be filtered :param h: the filter coefficients. :returns Y: the filtered image. .. codeauthor:: Rich Wareham <*****@*****.**>, August 2013 .. codeauthor:: Cian Shaffrey, Cambridge University, August 2000 .. codeauthor:: Nick Kingsbury, Cambridge University, August 2000 """ # Interpret all inputs as arrays X = asfarray(X) h = as_column_vector(h) r, c = X.shape m = h.shape[0] m2 = np.fix(m*0.5) # Symmetrically extend with repeat of end samples. # Use 'reflect' so r < m2 works OK. xe = reflect(np.arange(-m2, r+m2, dtype=np.int), -0.5, r-0.5) # Perform filtering on the columns of the extended matrix X(xe,:), keeping # only the 'valid' output samples, so Y is the same size as X if m is odd. Y = _column_convolve(X[xe,:], h) return Y
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 colfilter(X, h): """Filter the columns of image *X* using filter vector *h*, without decimation. If len(h) is odd, each output sample is aligned with each input sample and *Y* is the same size as *X*. If len(h) is even, each output sample is aligned with the mid point of each pair of input samples, and Y.shape = X.shape + [1 0]. The filtering will be accelerated via OpenCL. :param X: an image whose columns are to be filtered :param h: the filter coefficients. :returns Y: the filtered image. .. codeauthor:: Rich Wareham <*****@*****.**>, August 2013 .. codeauthor:: Cian Shaffrey, Cambridge University, August 2000 .. codeauthor:: Nick Kingsbury, Cambridge University, August 2000 """ # Interpret all inputs as arrays X = asfarray(X) h = as_column_vector(h) return to_array(axis_convolve(X, h))
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 forward(self, X, nlevels=3, include_scale=False, 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 include_scale: 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 *include_scale* is True the highpass coefficients at level 1 will not 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. .. codeauthor:: Rich Wareham <*****@*****.**>, Aug 2013 .. codeauthor:: Huizhong Chen, Jan 2009 .. codeauthor:: Nick Kingsbury, Cambridge University, July 1999. """ X = np.atleast_3d(asfarray(X)) if len(self.biort) == 4: h0o, g0o, h1o, g1o = self.biort elif len(self.biort) == 6: h0o, g0o, h1o, g1o, h2o, g2o = self.biort else: raise ValueError('Biort wavelet must have 6 or 4 components.') if len(self.qshift) == 8: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b = self.qshift elif len(self.qshift) == 12: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b, h2a, h2b = self.qshift[:10] else: raise ValueError('Qshift wavelet must have 12 or 8 components.') # Check value of ext_mode. TODO: this should really be an enum :S if self.ext_mode != 4 and self.ext_mode != 8: raise ValueError('ext_mode must be one of 4 or 8') Yl = X Yh = [None,] * nlevels if include_scale: # this is only required if the user specifies a third output component. Yscale = [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, self.ext_mode) if include_scale: Yscale[0] = Yl elif level == 0 and not discard_level_1: Yl, Yh[level] = _level1_xfm(Yl, h0o, h1o, self.ext_mode) if include_scale: Yscale[0] = Yl else: Yl, Yh[level] = _level2_xfm(Yl, h0a, h0b, h1a, h1b, self.ext_mode) if include_scale: Yscale[level] = Yl #FIXME: need some way to separate the Yscale component to include the scale when necessary. if include_scale: return Pyramid(Yl, tuple(Yh), tuple(Yscale)) else: return Pyramid(Yl, tuple(Yh)) return Pyramid(Yl, tuple(Yh))
def forward(self, X, nlevels=3, 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 :returns: A :py:class:`dtcwt.Pyramid` compatible object representing the transform-domain signal .. note:: *X* may be a :py:class:`pyopencl.array.Array` instance which has already been copied to the device. In which case, it must be 2D. (I.e. a vector will not be auto-promoted.) .. codeauthor:: Rich Wareham <*****@*****.**>, Aug 2013 .. codeauthor:: Nick Kingsbury, Cambridge University, Sept 2001 .. codeauthor:: Cian Shaffrey, Cambridge University, Sept 2001 """ queue = self.queue if isinstance(X, CLArray): if len(X.shape) != 2: raise ValueError('Input array must be two-dimensional') else: # If not an array, copy to device X = np.atleast_2d(asfarray(X)) # If biort has 6 elements instead of 4, then it's a modified # rotationally symmetric wavelet # FIXME: there's probably a nicer way to do this if len(self.biort) == 4: h0o, g0o, h1o, g1o = self.biort elif len(self.biort) == 6: h0o, g0o, h1o, g1o, h2o, g2o = self.biort else: raise ValueError('Biort wavelet must have 6 or 4 components.') # If qshift has 12 elements instead of 8, then it's a modified # rotationally symmetric wavelet # FIXME: there's probably a nicer way to do this if len(self.qshift) == 8: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b = self.qshift elif len(self.qshift) == 12: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b, h2a, h2b = self.qshift[:10] else: raise ValueError('Qshift wavelet must have 12 or 8 components.') 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 divisible by 2 then we need to extend X by adding a row at the bottom X = to_array(X) 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 divisible by 2 then we need to extend X by adding a col to the left X = to_array(X) X = np.hstack((X, X[:,[-1]])) initial_col_extend = 1 extended_size = X.shape # Copy X to the device if necessary X = to_device(X, queue=queue) if nlevels == 0: if include_scale: return Pyramid(X, (), ()) else: return Pyramid(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 = np.complex64 if nlevels >= 1: # Do odd top-level filters on cols. Lo = axis_convolve(X,h0o,axis=0,queue=queue) Hi = axis_convolve(X,h1o,axis=0,queue=queue) if len(self.biort) >= 6: Ba = axis_convolve(X,h2o,axis=0,queue=queue) # Do odd top-level filters on rows. LoLo = axis_convolve(Lo,h0o,axis=1) if len(self.biort) >= 6: diag = axis_convolve(Ba,h2o,axis=1,queue=queue) else: diag = axis_convolve(Hi,h1o,axis=1,queue=queue) Yh[0] = q2c( axis_convolve(Hi,h0o,axis=1,queue=queue), axis_convolve(Lo,h1o,axis=1,queue=queue), diag, ) 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 divisible by 4 LoLo = to_array(LoLo) LoLo = np.vstack((LoLo[:1,:], LoLo, LoLo[-1:,:])) if col_size % 4 != 0: # Extend by 2 cols if no. of cols of LoLo are not divisible by 4 LoLo = to_array(LoLo) LoLo = np.hstack((LoLo[:,:1], LoLo, LoLo[:,-1:])) # Do even Qshift filters on rows. Lo = axis_convolve_dfilter(LoLo,h0b,axis=0,queue=queue) Hi = axis_convolve_dfilter(LoLo,h1b,axis=0,queue=queue) if len(self.qshift) >= 12: Ba = axis_convolve_dfilter(LoLo,h2b,axis=0,queue=queue) # Do even Qshift filters on columns. LoLo = axis_convolve_dfilter(Lo,h0b,axis=1,queue=queue) if len(self.qshift) >= 12: diag = axis_convolve_dfilter(Ba,h2b,axis=1,queue=queue) else: diag = axis_convolve_dfilter(Hi,h1b,axis=1,queue=queue) Yh[level] = q2c( axis_convolve_dfilter(Hi,h0b,axis=1,queue=queue), axis_convolve_dfilter(Lo,h1b,axis=1,queue=queue), diag, ) if include_scale: Yscale[level] = 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 Pyramid(Yl, tuple(Yh), tuple(Yscale)) else: return Pyramid(Yl, tuple(Yh))
def coldfilt(X, ha, hb): """Filter the columns of image X using the two filters ha and hb = reverse(ha). ha operates on the odd samples of X and hb on the even samples. Both filters should be even length, and h should be approx linear phase with a quarter sample advance from its mid pt (i.e. :math:`|h(m/2)| > |h(m/2 + 1)|`). .. code-block:: text ext top edge bottom edge ext Level 1: ! | ! | ! odd filt on . b b b b a a a a a a a a b b b b odd filt on . a a a a b b b b b b b b a a a a Level 2: ! | ! | ! +q filt on x b b a a a a b b -q filt on o a a b b b b a a The output is decimated by two from the input sample rate and the results from the two filters, Ya and Yb, are interleaved to give Y. Symmetric extension with repeated end samples is used on the composite X columns before each filter is applied. Raises ValueError if the number of rows in X is not a multiple of 4, the length of ha does not match hb or the lengths of ha or hb are non-even. .. codeauthor:: Rich Wareham <*****@*****.**>, August 2013 .. codeauthor:: Cian Shaffrey, Cambridge University, August 2000 .. codeauthor:: Nick Kingsbury, Cambridge University, August 2000 """ # Make sure all inputs are arrays X = asfarray(X) ha = asfarray(ha) hb = asfarray(hb) r, c = X.shape if r % 4 != 0: raise ValueError('No. of rows in X must be a multiple of 4') if ha.shape != hb.shape: raise ValueError('Shapes of ha and hb must be the same') if ha.shape[0] % 2 != 0: raise ValueError('Lengths of ha and hb must be even') m = ha.shape[0] m2 = np.fix(m * 0.5) # Set up vector for symmetric extension of X with repeated end samples. xe = reflect(np.arange(-m, r + m), -0.5, r - 0.5) # Select odd and even samples from ha and hb. Note that due to 0-indexing # 'odd' and 'even' are not perhaps what you might expect them to be. hao = as_column_vector(ha[0:m:2]) hae = as_column_vector(ha[1:m:2]) hbo = as_column_vector(hb[0:m:2]) hbe = as_column_vector(hb[1:m:2]) t = np.arange(5, r + 2 * m - 2, 4) r2 = r // 2 Y = np.zeros((r2, c), dtype=X.dtype) if np.sum(ha * hb) > 0: s1 = slice(0, r2, 2) s2 = slice(1, r2, 2) else: s2 = slice(0, r2, 2) s1 = slice(1, r2, 2) # Perform filtering on columns of extended matrix X(xe,:) in 4 ways. Y[s1, :] = _column_convolve(X[xe[t - 1], :], hao) + _column_convolve( X[xe[t - 3], :], hae) Y[s2, :] = _column_convolve(X[xe[t], :], hbo) + _column_convolve( X[xe[t - 2], :], hbe) return Y
def forward(self, X, nlevels=3, include_scale=False): """ Perform a forward transform on an image. Can provide the forward transform with either an np array (naive usage), or a tensorflow variable or placeholder (designed usage). To transform batches of images, use the :py:meth:`forward_channels` method. :param ndarray X: Input image which you wish to transform. Can be a numpy array, tensorflow Variable or tensorflow placeholder. See comments below. :param int nlevels: Number of levels of the dtcwt transform to calculate. :param bool include_scale: Whether or not to return the lowpass results at each scale of the transform, or only at the highest scale (as is custom for multi-resolution analysis) :returns: A :py:class:`dtcwt.tf.Pyramid` object .. note:: If a numpy array is provided, the forward function will create a tensorflow variable to hold the input image, and then create the graph of the right size to match the input, and then feed the input into the graph and evaluate it. This operation will return a :py:class:`Pyramid` object similar to how running the numpy version would. .. codeauthor:: Fergal Cotter <*****@*****.**>, Feb 2017 .. codeauthor:: Rich Wareham <*****@*****.**>, Aug 2013 .. codeauthor:: Nick Kingsbury, Cambridge University, Sept 2001 .. codeauthor:: Cian Shaffrey, Cambridge University, Sept 2001 """ # Check if a numpy array was provided numpy = False try: dtype = X.dtype except AttributeError: X = asfarray(X) dtype = X.dtype if dtype in np_dtypes: numpy = True X = np.atleast_2d(X) X = tf.Variable(X, dtype=tf.float32, trainable=False) if X.dtype not in tf_dtypes: raise ValueError('I cannot handle the variable you have ' + 'provided of type ' + str(X.dtype) + '. ' + 'Inputs should be a numpy or tf array') X_shape = tuple(X.get_shape().as_list()) if len(X_shape) == 2: # Need to make it a batch for tensorflow X = tf.expand_dims(X, axis=0) elif len(X_shape) >= 3: raise ValueError( 'The entered variable has too many ' + 'dimensions - ' + str(X_shape) + '. For batches of ' + 'images with multiple channels (i.e. 3 or 4 dimensions), ' + 'please either enter each channel separately, or use ' + 'the forward_channels method.') X_shape = tuple(X.get_shape().as_list()) original_size = X_shape[1:] size = '{}x{}'.format(original_size[0], original_size[1]) name = 'dtcwt_fwd_{}'.format(size) with tf.variable_scope(name): Yl, Yh, Yscale = self._forward_ops(X, nlevels) Yl = Yl[0] Yh = tuple(x[0] for x in Yh) Yscale = tuple(x[0] for x in Yscale) if include_scale: return Pyramid(Yl, Yh, Yscale, numpy) else: return Pyramid(Yl, Yh, None, numpy)
def forward(self, X, nlevels=3, 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 :returns: A :py:class:`dtcwt.Pyramid` compatible object representing the transform-domain signal .. codeauthor:: Rich Wareham <*****@*****.**>, Aug 2013 .. codeauthor:: Nick Kingsbury, Cambridge University, Sept 2001 .. codeauthor:: Cian Shaffrey, Cambridge University, Sept 2001 """ # If biort has 6 elements instead of 4, then it's a modified # rotationally symmetric wavelet # FIXME: there's probably a nicer way to do this if len(self.biort) == 4: h0o, g0o, h1o, g1o = self.biort elif len(self.biort) == 6: h0o, g0o, h1o, g1o, h2o, g2o = self.biort else: raise ValueError('Biort wavelet must have 6 or 4 components.') # If qshift has 12 elements instead of 8, then it's a modified # rotationally symmetric wavelet # FIXME: there's probably a nicer way to do this if len(self.qshift) == 8: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b = self.qshift elif len(self.qshift) == 12: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b, h2a, h2b = self.qshift[:10] else: raise ValueError('Qshift wavelet must have 12 or 8 components.') X = np.atleast_2d(asfarray(X)) 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 Pyramid(X, (), ()) else: return Pyramid(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 if len(self.biort) >= 6: Ba = colfilter(X, h2o).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:6:5] = q2c(colfilter(Hi, h0o).T) # Horizontal pair Yh[0][:, :, 2:4:1] = q2c(colfilter(Lo, h1o).T) # Vertical pair if len(self.biort) >= 6: Yh[0][:, :, 1:5:3] = q2c(colfilter(Ba, h2o).T) # Diagonal pair else: Yh[0][:, :, 1:5:3] = 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[:1, :], 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[:, :1], LoLo, LoLo[:, -1:])) # Do even Qshift filters on rows. Lo = coldfilt(LoLo, h0b, h0a).T Hi = coldfilt(LoLo, h1b, h1a).T if len(self.qshift) >= 12: Ba = coldfilt(LoLo, h2b, h2a).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:6:5] = q2c(coldfilt(Hi, h0b, h0a).T) # Horizontal Yh[level][:, :, 2:4:1] = q2c(coldfilt(Lo, h1b, h1a).T) # Vertical if len(self.qshift) >= 12: Yh[level][:, :, 1:5:3] = q2c(coldfilt(Ba, h2b, h2a).T) # Diagonal else: Yh[level][:, :, 1:5:3] = q2c(coldfilt(Hi, h1b, h1a).T) # Diagonal if include_scale: Yscale[level] = 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 Pyramid(Yl, tuple(Yh), tuple(Yscale)) else: return Pyramid(Yl, tuple(Yh))
def forward(self, X, nlevels=3, include_scale=False): """Perform a *n*-level DTCWT decompostion on a 1D column vector *X* (or on the columns of a matrix *X*). Can provide the forward transform with either an np array (naive usage), or a tensorflow variable or placeholder (designed usage). To transform batches of vectors, use the :py:meth:`forward_channels` method. :param X: 1D real array or 2D real array whose columns are to be transformed. :param nlevels: Number of levels of wavelet decomposition :returns: A :py:class:`dtcwt.tf.Pyramid` object representing the transform result. 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). .. codeauthor:: Fergal Cotter <*****@*****.**>, Sep 2017 .. codeauthor:: Rich Wareham <*****@*****.**>, Aug 2013 .. codeauthor:: Nick Kingsbury, Cambridge University, May 2002 .. codeauthor:: Cian Shaffrey, Cambridge University, May 2002 """ # Check if a numpy array was provided numpy = False try: dtype = X.dtype except AttributeError: X = asfarray(X) dtype = X.dtype if dtype in np_dtypes: numpy = True # Need this because colfilter and friends assumes input is 2d if len(X.shape) == 1: X = np.atleast_2d(X).T X = tf.Variable(X, dtype=tf.float32, trainable=False) elif dtype in tf_dtypes: if len(X.get_shape().as_list()) == 1: X = tf.expand_dims(X, axis=-1) else: raise ValueError('I cannot handle the variable you have ' + 'provided of type ' + str(X.dtype) + '. ' + 'Inputs should be a numpy or tf array') X_shape = tuple(X.get_shape().as_list()) size = '{}'.format(X_shape[0]) name = 'dtcwt_fwd_{}'.format(size) if len(X_shape) == 2: # Need to make it a batch for tensorflow X = tf.expand_dims(X, axis=0) elif len(X_shape) >= 3: raise ValueError( 'The entered variable has too many ' + 'dimensions - ' + str(X_shape) + '.') # Do the forward transform with tf.variable_scope(name): Yl, Yh, Yscale = self._forward_ops(X, nlevels) Yl = Yl[0] Yh = tuple(x[0] for x in Yh) Yscale = tuple(x[0] for x in Yscale) if include_scale: return Pyramid(Yl, Yh, Yscale, numpy) else: return Pyramid(Yl, Yh, None, numpy)
def coldfilt(X, ha, hb): """Filter the columns of image X using the two filters ha and hb = reverse(ha). ha operates on the odd samples of X and hb on the even samples. Both filters should be even length, and h should be approx linear phase with a quarter sample advance from its mid pt (i.e. :math:`|h(m/2)| > |h(m/2 + 1)|`). .. code-block:: text ext top edge bottom edge ext Level 1: ! | ! | ! odd filt on . b b b b a a a a a a a a b b b b odd filt on . a a a a b b b b b b b b a a a a Level 2: ! | ! | ! +q filt on x b b a a a a b b -q filt on o a a b b b b a a The output is decimated by two from the input sample rate and the results from the two filters, Ya and Yb, are interleaved to give Y. Symmetric extension with repeated end samples is used on the composite X columns before each filter is applied. Raises ValueError if the number of rows in X is not a multiple of 4, the length of ha does not match hb or the lengths of ha or hb are non-even. .. codeauthor:: Rich Wareham <*****@*****.**>, August 2013 .. codeauthor:: Cian Shaffrey, Cambridge University, August 2000 .. codeauthor:: Nick Kingsbury, Cambridge University, August 2000 """ # Make sure all inputs are arrays X = asfarray(X) ha = asfarray(ha) hb = asfarray(hb) r, c = X.shape if r % 4 != 0: raise ValueError('No. of rows in X must be a multiple of 4') if ha.shape != hb.shape: raise ValueError('Shapes of ha and hb must be the same') if ha.shape[0] % 2 != 0: raise ValueError('Lengths of ha and hb must be even') m = ha.shape[0] m2 = np.fix(m*0.5) # Set up vector for symmetric extension of X with repeated end samples. xe = reflect(np.arange(-m, r+m), -0.5, r-0.5) # Select odd and even samples from ha and hb. Note that due to 0-indexing # 'odd' and 'even' are not perhaps what you might expect them to be. hao = as_column_vector(ha[0:m:2]) hae = as_column_vector(ha[1:m:2]) hbo = as_column_vector(hb[0:m:2]) hbe = as_column_vector(hb[1:m:2]) t = np.arange(5, r+2*m-2, 4) r2 = r/2; Y = np.zeros((r2,c), dtype=X.dtype) if np.sum(ha*hb) > 0: s1 = slice(0, r2, 2) s2 = slice(1, r2, 2) else: s2 = slice(0, r2, 2) s1 = slice(1, r2, 2) # Perform filtering on columns of extended matrix X(xe,:) in 4 ways. Y[s1,:] = _column_convolve(X[xe[t-1],:],hao) + _column_convolve(X[xe[t-3],:],hae) Y[s2,:] = _column_convolve(X[xe[t],:],hbo) + _column_convolve(X[xe[t-2],:],hbe) return Y
def forward(self, X, nlevels=3, 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 :returns: A :py:class:`dtcwt.Pyramid` compatible object representing the transform-domain signal .. note:: *X* may be a :py:class:`pyopencl.array.Array` instance which has already been copied to the device. In which case, it must be 2D. (I.e. a vector will not be auto-promoted.) .. codeauthor:: Rich Wareham <*****@*****.**>, Aug 2013 .. codeauthor:: Nick Kingsbury, Cambridge University, Sept 2001 .. codeauthor:: Cian Shaffrey, Cambridge University, Sept 2001 """ queue = self.queue if isinstance(X, CLArray): if len(X.shape) != 2: raise ValueError('Input array must be two-dimensional') else: # If not an array, copy to device X = np.atleast_2d(asfarray(X)) # If biort has 6 elements instead of 4, then it's a modified # rotationally symmetric wavelet # FIXME: there's probably a nicer way to do this if len(self.biort) == 4: h0o, g0o, h1o, g1o = self.biort elif len(self.biort) == 6: h0o, g0o, h1o, g1o, h2o, g2o = self.biort else: raise ValueError('Biort wavelet must have 6 or 4 components.') # If qshift has 12 elements instead of 8, then it's a modified # rotationally symmetric wavelet # FIXME: there's probably a nicer way to do this if len(self.qshift) == 8: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b = self.qshift elif len(self.qshift) == 12: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b, h2a, h2b = self.qshift[:10] else: raise ValueError('Qshift wavelet must have 12 or 8 components.') 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 divisible by 2 then we need to extend X by adding a row at the bottom X = to_array(X) 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 divisible by 2 then we need to extend X by adding a col to the left X = to_array(X) X = np.hstack((X, X[:,[-1]])) initial_col_extend = 1 extended_size = X.shape # Copy X to the device if necessary X = to_device(X, queue=queue) if nlevels == 0: if include_scale: return Pyramid(X, (), ()) else: return Pyramid(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 = np.complex64 if nlevels >= 1: # Do odd top-level filters on cols. Lo = axis_convolve(X,h0o,axis=0,queue=queue) Hi = axis_convolve(X,h1o,axis=0,queue=queue) if len(self.biort) >= 6: Ba = axis_convolve(X,h2o,axis=0,queue=queue) # Do odd top-level filters on rows. LoLo = axis_convolve(Lo,h0o,axis=1,queue=queue) if len(self.biort) >= 6: diag = axis_convolve(Ba,h2o,axis=1,queue=queue) else: diag = axis_convolve(Hi,h1o,axis=1,queue=queue) Yh[0] = q2c( axis_convolve(Hi,h0o,axis=1,queue=queue), axis_convolve(Lo,h1o,axis=1,queue=queue), diag, queue=queue ) 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 divisible by 4 LoLo = to_array(LoLo) LoLo = np.vstack((LoLo[:1,:], LoLo, LoLo[-1:,:])) if col_size % 4 != 0: # Extend by 2 cols if no. of cols of LoLo are not divisible by 4 LoLo = to_array(LoLo) LoLo = np.hstack((LoLo[:,:1], LoLo, LoLo[:,-1:])) # Do even Qshift filters on rows. Lo = axis_convolve_dfilter(LoLo,h0b,axis=0,queue=queue) Hi = axis_convolve_dfilter(LoLo,h1b,axis=0,queue=queue) if len(self.qshift) >= 12: Ba = axis_convolve_dfilter(LoLo,h2b,axis=0,queue=queue) # Do even Qshift filters on columns. LoLo = axis_convolve_dfilter(Lo,h0b,axis=1,queue=queue) if len(self.qshift) >= 12: diag = axis_convolve_dfilter(Ba,h2b,axis=1,queue=queue) else: diag = axis_convolve_dfilter(Hi,h1b,axis=1,queue=queue) Yh[level] = q2c( axis_convolve_dfilter(Hi,h0b,axis=1,queue=queue), axis_convolve_dfilter(Lo,h1b,axis=1,queue=queue), diag, queue=queue ) if include_scale: Yscale[level] = 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 Pyramid(Yl, tuple(Yh), tuple(Yscale)) else: return Pyramid(Yl, tuple(Yh))
def forward_channels(self, X, nlevels=3, include_scale=False): """Perform a *n*-level DTCWT decompostion on a 3D array *X*. Can provide the forward transform with either an np array (naive usage), or a tensorflow variable or placeholder (designed usage). :param X: 3D real array. Batch of matrices whose columns are to be transformed (i.e. the second dimension). :param nlevels: Number of levels of wavelet decomposition :returns: A :py:class:`dtcwt.tf.Pyramid` object representing the transform result. 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). .. codeauthor:: Fergal Cotter <*****@*****.**>, Sep 2017 .. codeauthor:: Rich Wareham <*****@*****.**>, Aug 2013 .. codeauthor:: Nick Kingsbury, Cambridge University, May 2002 .. codeauthor:: Cian Shaffrey, Cambridge University, May 2002 """ # Check if a numpy array was provided numpy = False try: dtype = X.dtype except AttributeError: X = asfarray(X) dtype = X.dtype if dtype in np_dtypes: numpy = True if len(X.shape) != 3: raise ValueError( 'Incorrect input shape for the forward_channels ' + 'method ' + str(X.shape) + '. For Inputs of 1 or 2 ' + 'dimensions, use the forward method.') # Need this because colfilter and friends assumes input is 2d X = tf.Variable(X, dtype=tf.float32, trainable=False) elif dtype in tf_dtypes: X_shape = X.get_shape().as_list() if len(X.get_shape().as_list()) != 3: raise ValueError( 'Incorrect input shape for the forward_channels ' + 'method ' + str(X_shape) + '. For Inputs of 1 or 2 ' + 'dimensions, use the forward method.') else: raise ValueError('I cannot handle the variable you have ' + 'provided of type ' + str(X.dtype) + '. ' + 'Inputs should be a numpy or tf array') X_shape = tuple(X.get_shape().as_list()) size = '{}'.format(X_shape[1]) name = 'dtcwt_fwd_{}'.format(size) # Do the forward transform with tf.variable_scope(name): Yl, Yh, Yscale = self._forward_ops(X, nlevels) if include_scale: return Pyramid(Yl, Yh, Yscale, numpy) else: return Pyramid(Yl, Yh, None, numpy)
def forward(self, X, nlevels=3, 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 :returns: A :py:class:`dtcwt.Pyramid` compatible object representing the transform-domain signal .. codeauthor:: Rich Wareham <*****@*****.**>, Aug 2013 .. codeauthor:: Nick Kingsbury, Cambridge University, Sept 2001 .. codeauthor:: Cian Shaffrey, Cambridge University, Sept 2001 """ # If biort has 6 elements instead of 4, then it's a modified # rotationally symmetric wavelet # FIXME: there's probably a nicer way to do this if len(self.biort) == 4: h0o, g0o, h1o, g1o = self.biort elif len(self.biort) == 6: h0o, g0o, h1o, g1o, h2o, g2o = self.biort else: raise ValueError('Biort wavelet must have 6 or 4 components.') # If qshift has 12 elements instead of 8, then it's a modified # rotationally symmetric wavelet # FIXME: there's probably a nicer way to do this if len(self.qshift) == 8: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b = self.qshift elif len(self.qshift) == 12: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b, h2a, h2b = self.qshift[:10] else: raise ValueError('Qshift wavelet must have 12 or 8 components.') X = np.atleast_2d(asfarray(X)) original_size = X.shape if len(X.shape) >= 3: raise ValueError('The entered image is {0}, which is invalid '. format('x'.join(list(str(s) for s in X.shape))) + 'for the 2D transform in a numpy backend. ' + 'Please enter each image slice separately.') # 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 Pyramid(X, (), ()) else: return Pyramid(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 if len(self.biort) >= 6: Ba = colfilter(X,h2o).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:6:5] = q2c(colfilter(Hi,h0o).T) # Horizontal pair Yh[0][:,:,2:4:1] = q2c(colfilter(Lo,h1o).T) # Vertical pair if len(self.biort) >= 6: Yh[0][:,:,1:5:3] = q2c(colfilter(Ba,h2o).T) # Diagonal pair else: Yh[0][:,:,1:5:3] = 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[:1,:], 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[:,:1], LoLo, LoLo[:,-1:])) # Do even Qshift filters on rows. Lo = coldfilt(LoLo,h0b,h0a).T Hi = coldfilt(LoLo,h1b,h1a).T if len(self.qshift) >= 12: Ba = coldfilt(LoLo,h2b,h2a).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:6:5] = q2c(coldfilt(Hi,h0b,h0a).T) # Horizontal Yh[level][:,:,2:4:1] = q2c(coldfilt(Lo,h1b,h1a).T) # Vertical if len(self.qshift) >= 12: Yh[level][:,:,1:5:3] = q2c(coldfilt(Ba,h2b,h2a).T) # Diagonal else: Yh[level][:,:,1:5:3] = q2c(coldfilt(Hi,h1b,h1a).T) # Diagonal if include_scale: Yscale[level] = 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 Pyramid(Yl, tuple(Yh), tuple(Yscale)) else: return Pyramid(Yl, tuple(Yh))
def forward(self, X, nlevels=3, include_scale=False, 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:`dtcwt.coeffs.biort`. :param qshift: Level >= 2 wavelets to use. See :py:func:`dtcwt.coeffs.qshift`. :param discard_level_1: True if level 1 high-pass bands are to be discarded. :returns: a :py:class:`dtcwt.Pyramid` instance Each element of the Pyramid *highpasses* tuple is a 4D complex array with the 4th dimension having size 28. The 3D slice ``[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:`dtcwt.coeffs.biort` or :py:func:`dtcwt.coeffs.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 not 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 the first element of the *highpasses* tuple will be `None`. Note that :py:func:`dtcwt.Transform3d.inverse` will accept the first element being `None` and will treat it as being zero. .. codeauthor:: Rich Wareham <*****@*****.**>, Aug 2013 .. codeauthor:: Huizhong Chen, Jan 2009 .. codeauthor:: Nick Kingsbury, Cambridge University, July 1999. """ X = np.atleast_3d(asfarray(X)) # If biort has 6 elements instead of 4, then it's a modified # rotationally symmetric wavelet # FIXME: there's probably a nicer way to do this if len(self.biort) == 4: h0o, g0o, h1o, g1o = self.biort elif len(self.biort) == 6: h0o, g0o, h1o, g1o, h2o, g2o = self.biort else: raise ValueError('Biort wavelet must have 6 or 4 components.') # If qshift has 12 elements instead of 8, then it's a modified # rotationally symmetric wavelet # FIXME: there's probably a nicer way to do this if len(self.qshift) == 8: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b = self.qshift elif len(self.qshift) == 12: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b, h2a, h2b = self.qshift[:10] else: raise ValueError('Qshift wavelet must have 12 or 8 components.') # Check value of ext_mode. TODO: this should really be an enum :S if self.ext_mode != 4 and self.ext_mode != 8: raise ValueError('ext_mode must be one of 4 or 8') Yl = X Yh = [ None, ] * nlevels if include_scale: # this is only required if the user specifies a third output component. Yscale = [ None, ] * nlevels #pdb.set_trace() # 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, self.ext_mode) elif level == 0 and not discard_level_1: Yl, Yh[level] = _level1_xfm(Yl, h0o, h1o, self.ext_mode) else: Yl, Yh[level] = _level2_xfm(Yl, h0a, h0b, h1a, h1b, self.ext_mode) if include_scale: Yscale[level] = Yl.copy() #Yh[nlevels+1]=1 #to throw an error for debugging in nose if include_scale: return Pyramid(Yl, tuple(Yh), tuple(Yscale)) else: return Pyramid(Yl, tuple(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 __init__(self, lowpass, highpasses, scales=None): self.lowpass = asfarray(lowpass) self.highpasses = tuple( asfarray(x) if x is not None else None for x in highpasses) self.scales = tuple(asfarray(x) for x in scales) if scales is not None else None
def forward_channels(self, X, data_format, nlevels=3, include_scale=False): """ Perform a forward transform on an image with multiple channels. Will perform the DTCWT independently on each channel. :param X: Input image which you wish to transform. :param int nlevels: Number of levels of the dtcwt transform to calculate. :param bool include_scale: Whether or not to return the lowpass results at each scale of the transform, or only at the highest scale (as is custom for multiresolution analysis) :param str data_format: An optional string of the form: "nhw" (or "chw"), "hwn" (or "hwc"), "nchw" or "nhwc". Note that for these strings, 'n' is used to indicate where the batch dimension is, 'c' is used to indicate where the image channels are, 'h' is used to indicate where the row dimension is, and 'c' is used to indicate where the columns are. If the data_format is: - "nhw" : the input will be interpreted as a batch of 2D images, with the batch dimension as the first. - "chw" : will function exactly the same as "nhw" but is offered to indicate the input is a 2D image with channels. - "hwn" : the input will be interpreted as a batch of 2D images with the batch dimension as the last. - "hwc" : will function exatly the same as "hwc" but is offered to indicate the input is a 2D image with channels. - "nchw" : the input is a batch of images with channel dimension as the second dimension. Batch dimension is first. - "nhwc" : the input is a batch of images with channel dimension as the last dimension. Batch dimension is first. :returns: A :py:class:`dtcwt.tf.Pyramid` object .. codeauthor:: Fergal Cotter <*****@*****.**>, Feb 2017 .. codeauthor:: Rich Wareham <*****@*****.**>, Aug 2013 .. codeauthor:: Nick Kingsbury, Cambridge University, Sept 2001 .. codeauthor:: Cian Shaffrey, Cambridge University, Sept 2001 """ data_format = data_format.lower() formats_3d = ("nhw", "chw", "hwn", "hwc") formats_4d = ("nchw", "nhwc") formats = formats_3d + formats_4d if data_format not in formats: raise ValueError('The data format must be one of: {}'. format(formats)) try: dtype = X.dtype except AttributeError: X = asfarray(X) dtype = X.dtype numpy = False if dtype in np_dtypes: numpy = True X = np.atleast_2d(X) X = tf.Variable(X, dtype=tf.float32, trainable=False) if X.dtype not in tf_dtypes: raise ValueError('I cannot handle the variable you have ' + 'provided of type ' + str(X.dtype) + '. ' + 'Inputs should be a numpy or tf array.') X_shape = X.get_shape().as_list() if not ((len(X_shape) == 3 and data_format in formats_3d) or (len(X_shape) == 4 and data_format in formats_4d)): raise ValueError( 'The entered variable has incorrect shape - ' + str(X_shape) + ' for the specified data_format ' + data_format + '.') # Reshape the inputs to all be 3d inputs of shape (batch, h, w) if data_format in formats_4d: # Move all of the channels into the batch dimension for the # input. This may involve transposing, depending on the data # format with tf.variable_scope('ch_to_batch'): s = X.get_shape().as_list()[1:] size = '{}x{}'.format(s[0], s[1]) name = 'dtcwt_fwd_{}'.format(size) if data_format == 'nhwc': nch = s[2] X = tf.transpose(X, perm=[0, 3, 1, 2]) X = tf.reshape(X, [-1, s[0], s[1]]) else: nch = s[0] X = tf.reshape(X, [-1, s[1], s[2]]) elif data_format == "hwn" or data_format == "hwc": s = X.get_shape().as_list()[:2] size = '{}x{}'.format(s[0], s[1]) name = 'dtcwt_fwd_{}'.format(size) with tf.variable_scope('ch_to_start'): X = tf.transpose(X, perm=[2,0,1]) else: s = X.get_shape().as_list()[1:3] size = '{}x{}'.format(s[0], s[1]) name = 'dtcwt_fwd_{}'.format(size) # Do the dtcwt, now with a 3 dimensional input with tf.variable_scope(name): Yl, Yh, Yscale = self._forward_ops(X, nlevels) # Reshape it all again to match the input if data_format in formats_4d: # Put the channels back into their correct positions with tf.variable_scope('batch_to_ch'): # Reshape Yl s = Yl.get_shape().as_list()[1:] Yl = tf.reshape(Yl, [-1, nch, s[0], s[1]], name='Yl_reshape') if data_format == 'nhwc': Yl = tf.transpose(Yl, [0, 2, 3, 1], name='Yl_ch_to_end') # Reshape Yh with tf.variable_scope('Yh'): Yh_new = [None,] * nlevels for i in range(nlevels): s = Yh[i].get_shape().as_list()[1:] Yh_new[i] = tf.reshape( Yh[i], [-1, nch, s[0], s[1], s[2]], name='scale{}_reshape'.format(i)) if data_format == 'nhwc': Yh_new[i] = tf.transpose( Yh_new[i], [0, 2, 3, 1, 4], name='scale{}_ch_to_end'.format(i)) Yh = tuple(Yh_new) # Reshape Yscale if include_scale: with tf.variable_scope('Yscale'): Yscale_new = [None,] * nlevels for i in range(nlevels): s = Yscale[i].get_shape().as_list()[1:] Yscale_new[i] = tf.reshape( Yscale[i], [-1, nch, s[0], s[1]], name='scale{}_reshape'.format(i)) if data_format == 'nhwc': Yscale_new[i] = tf.transpose( Yscale_new[i], [0, 2, 3, 1], name='scale{}_ch_to_end'.format(i)) Yscale = tuple(Yscale_new) elif data_format == "hwn" or data_format == "hwc": with tf.variable_scope('ch_to_end'): Yl = tf.transpose(Yl, perm=[1,2,0], name='Yl') Yh = tuple( tf.transpose(x, [1, 2, 0, 3], name='Yh{}'.format(i)) for i,x in enumerate(Yh)) if include_scale: Yscale = tuple( tf.transpose(x, [1, 2, 0], name='Yscale{}'.format(i)) for i,x in enumerate(Yscale)) # Return the pyramid if include_scale: return Pyramid(Yl, Yh, Yscale, numpy) else: return Pyramid(Yl, Yh, None, numpy)
def forward(self, X, nlevels=3, 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 :returns: A :py:class:`DTCWT.Pyramid`-like object representing the transform result. 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). .. codeauthor:: Rich Wareham <*****@*****.**>, Aug 2013 .. codeauthor:: Nick Kingsbury, Cambridge University, May 2002 .. codeauthor:: Cian Shaffrey, Cambridge University, May 2002 """ # Which wavelets are to be used? biort = self.biort qshift = self.qshift # 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 Pyramid(X, (), ()) else: return Pyramid(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 Pyramid(Yl, Yh, Yscale) else: return Pyramid(Yl, Yh)
def colifilt(X, ha, hb): """ Filter the columns of image X using the two filters ha and hb = reverse(ha). ha operates on the odd samples of X and hb on the even samples. Both filters should be even length, and h should be approx linear phase with a quarter sample advance from its mid pt (i.e `:math:`|h(m/2)| > |h(m/2 + 1)|`). .. code-block:: text ext left edge right edge ext Level 2: ! | ! | ! +q filt on x b b a a a a b b -q filt on o a a b b b b a a Level 1: ! | ! | ! odd filt on . b b b b a a a a a a a a b b b b odd filt on . a a a a b b b b b b b b a a a a The output is interpolated by two from the input sample rate and the results from the two filters, Ya and Yb, are interleaved to give Y. Symmetric extension with repeated end samples is used on the composite X columns before each filter is applied. .. codeauthor:: Rich Wareham <*****@*****.**>, August 2013 .. codeauthor:: Cian Shaffrey, Cambridge University, August 2000 .. codeauthor:: Nick Kingsbury, Cambridge University, August 2000 """ # Make sure all inputs are arrays X = asfarray(X) ha = asfarray(ha) hb = asfarray(hb) r, c = X.shape if r % 2 != 0: raise ValueError('No. of rows in X must be a multiple of 2') if ha.shape != hb.shape: raise ValueError('Shapes of ha and hb must be the same') if ha.shape[0] % 2 != 0: raise ValueError('Lengths of ha and hb must be even') m = ha.shape[0] m2 = np.fix(m*0.5) Y = np.zeros((r*2,c), dtype=X.dtype) if not np.any(np.nonzero(X[:])[0]): return Y if m2 % 2 == 0: # m/2 is even, so set up t to start on d samples. # Set up vector for symmetric extension of X with repeated end samples. # Use 'reflect' so r < m2 works OK. xe = reflect(np.arange(-m2, r+m2, dtype=np.int), -0.5, r-0.5) t = np.arange(3, r+m, 2) if np.sum(ha*hb) > 0: ta = t tb = t - 1 else: ta = t - 1 tb = t # Select odd and even samples from ha and hb. Note that due to 0-indexing # 'odd' and 'even' are not perhaps what you might expect them to be. hao = as_column_vector(ha[0:m:2]) hae = as_column_vector(ha[1:m:2]) hbo = as_column_vector(hb[0:m:2]) hbe = as_column_vector(hb[1:m:2]) s = np.arange(0,r*2,4) Y[s,:] = _column_convolve(X[xe[tb-2],:],hae) Y[s+1,:] = _column_convolve(X[xe[ta-2],:],hbe) Y[s+2,:] = _column_convolve(X[xe[tb ],:],hao) Y[s+3,:] = _column_convolve(X[xe[ta ],:],hbo) else: # m/2 is odd, so set up t to start on b samples. # Set up vector for symmetric extension of X with repeated end samples. # Use 'reflect' so r < m2 works OK. xe = reflect(np.arange(-m2, r+m2, dtype=np.int), -0.5, r-0.5) t = np.arange(2, r+m-1, 2) if np.sum(ha*hb) > 0: ta = t tb = t - 1 else: ta = t - 1 tb = t # Select odd and even samples from ha and hb. Note that due to 0-indexing # 'odd' and 'even' are not perhaps what you might expect them to be. hao = as_column_vector(ha[0:m:2]) hae = as_column_vector(ha[1:m:2]) hbo = as_column_vector(hb[0:m:2]) hbe = as_column_vector(hb[1:m:2]) s = np.arange(0,r*2,4) Y[s,:] = _column_convolve(X[xe[tb],:],hao) Y[s+1,:] = _column_convolve(X[xe[ta],:],hbo) Y[s+2,:] = _column_convolve(X[xe[tb],:],hae) Y[s+3,:] = _column_convolve(X[xe[ta],:],hbe) return Y
def forward(self, X, nlevels=3, include_scale=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 :returns: a :py:class:`dtcwt.Pyramid` instance Each element of the Pyramid *highpasses* tuple is a 4D complex array with the 4th dimension having size 28. The 3D slice ``[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:`dtcwt.coeffs.biort` or :py:func:`dtcwt.coeffs.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 not 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 the first element of the *highpasses* tuple will be `None`. Note that :py:func:`dtcwt.Transform3d.inverse` will accept the first element being `None` and will treat it as being zero. .. codeauthor:: Rich Wareham <*****@*****.**>, Aug 2013 .. codeauthor:: Huizhong Chen, Jan 2009 .. codeauthor:: Nick Kingsbury, Cambridge University, July 1999. """ X = np.atleast_3d(asfarray(X)) # If biort has 6 elements instead of 4, then it's a modified # rotationally symmetric wavelet # FIXME: there's probably a nicer way to do this if len(self.biort) == 4: h0o, g0o, h1o, g1o = self.biort elif len(self.biort) == 6: h0o, g0o, h1o, g1o, h2o, g2o = self.biort else: raise ValueError('Biort wavelet must have 6 or 4 components.') # If qshift has 12 elements instead of 8, then it's a modified # rotationally symmetric wavelet # FIXME: there's probably a nicer way to do this if len(self.qshift) == 8: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b = self.qshift elif len(self.qshift) == 12: h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b, h2a, h2b = self.qshift[:10] else: raise ValueError('Qshift wavelet must have 12 or 8 components.') # Check value of ext_mode. TODO: this should really be an enum :S if self.ext_mode != 4 and self.ext_mode != 8: raise ValueError('ext_mode must be one of 4 or 8') Yl = X Yh = [None,] * nlevels if include_scale: # this is only required if the user specifies a third output component. Yscale = [None,] * nlevels # level is 0-indexed for level in xrange(nlevels): # Transform if level == 0 and self.discard_level_1: Yl = _level1_xfm_no_highpass(Yl, h0o, h1o, self.ext_mode) elif level == 0 and not self.discard_level_1: Yl, Yh[level] = _level1_xfm(Yl, h0o, h1o, self.ext_mode) else: Yl, Yh[level] = _level2_xfm(Yl, h0a, h0b, h1a, h1b, self.ext_mode) if include_scale: Yscale[level] = Yl.copy() if include_scale: return Pyramid(Yl, tuple(Yh), tuple(Yscale)) else: return Pyramid(Yl, tuple(Yh))
def forward(self, X, nlevels=3, 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 :returns: A :py:class:`dtcwt.Pyramid`-like object representing the transform result. 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). .. codeauthor:: Rich Wareham <*****@*****.**>, Aug 2013 .. codeauthor:: Nick Kingsbury, Cambridge University, May 2002 .. codeauthor:: Cian Shaffrey, Cambridge University, May 2002 """ # Which wavelets are to be used? biort = self.biort qshift = self.qshift # 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 Pyramid(X, (), ()) else: return Pyramid(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 Pyramid(Yl, Yh, Yscale) else: return Pyramid(Yl, Yh)
def __init__(self, lowpass, highpasses, scales=None): self.lowpass = asfarray(lowpass) self.highpasses = tuple(asfarray(x) if x is not None else None for x in highpasses) self.scales = tuple(asfarray(x) for x in scales) if scales is not None else None
def colifilt(X, ha, hb): """ Filter the columns of image X using the two filters ha and hb = reverse(ha). ha operates on the odd samples of X and hb on the even samples. Both filters should be even length, and h should be approx linear phase with a quarter sample advance from its mid pt (i.e `:math:`|h(m/2)| > |h(m/2 + 1)|`). .. code-block:: text ext left edge right edge ext Level 2: ! | ! | ! +q filt on x b b a a a a b b -q filt on o a a b b b b a a Level 1: ! | ! | ! odd filt on . b b b b a a a a a a a a b b b b odd filt on . a a a a b b b b b b b b a a a a The output is interpolated by two from the input sample rate and the results from the two filters, Ya and Yb, are interleaved to give Y. Symmetric extension with repeated end samples is used on the composite X columns before each filter is applied. .. codeauthor:: Rich Wareham <*****@*****.**>, August 2013 .. codeauthor:: Cian Shaffrey, Cambridge University, August 2000 .. codeauthor:: Nick Kingsbury, Cambridge University, August 2000 """ # Make sure all inputs are arrays X = asfarray(X) ha = asfarray(ha) hb = asfarray(hb) r, c = X.shape if r % 2 != 0: raise ValueError('No. of rows in X must be a multiple of 2') if ha.shape != hb.shape: raise ValueError('Shapes of ha and hb must be the same') if ha.shape[0] % 2 != 0: raise ValueError('Lengths of ha and hb must be even') m = ha.shape[0] m2 = np.fix(m * 0.5) Y = np.zeros((r * 2, c), dtype=X.dtype) if not np.any(np.nonzero(X[:])[0]): return Y if m2 % 2 == 0: # m/2 is even, so set up t to start on d samples. # Set up vector for symmetric extension of X with repeated end samples. # Use 'reflect' so r < m2 works OK. xe = reflect(np.arange(-m2, r + m2, dtype=np.int), -0.5, r - 0.5) t = np.arange(3, r + m, 2) if np.sum(ha * hb) > 0: ta = t tb = t - 1 else: ta = t - 1 tb = t # Select odd and even samples from ha and hb. Note that due to 0-indexing # 'odd' and 'even' are not perhaps what you might expect them to be. hao = as_column_vector(ha[0:m:2]) hae = as_column_vector(ha[1:m:2]) hbo = as_column_vector(hb[0:m:2]) hbe = as_column_vector(hb[1:m:2]) s = np.arange(0, r * 2, 4) Y[s, :] = _column_convolve(X[xe[tb - 2], :], hae) Y[s + 1, :] = _column_convolve(X[xe[ta - 2], :], hbe) Y[s + 2, :] = _column_convolve(X[xe[tb], :], hao) Y[s + 3, :] = _column_convolve(X[xe[ta], :], hbo) else: # m/2 is odd, so set up t to start on b samples. # Set up vector for symmetric extension of X with repeated end samples. # Use 'reflect' so r < m2 works OK. xe = reflect(np.arange(-m2, r + m2, dtype=np.int), -0.5, r - 0.5) t = np.arange(2, r + m - 1, 2) if np.sum(ha * hb) > 0: ta = t tb = t - 1 else: ta = t - 1 tb = t # Select odd and even samples from ha and hb. Note that due to 0-indexing # 'odd' and 'even' are not perhaps what you might expect them to be. hao = as_column_vector(ha[0:m:2]) hae = as_column_vector(ha[1:m:2]) hbo = as_column_vector(hb[0:m:2]) hbe = as_column_vector(hb[1:m:2]) s = np.arange(0, r * 2, 4) Y[s, :] = _column_convolve(X[xe[tb], :], hao) Y[s + 1, :] = _column_convolve(X[xe[ta], :], hbo) Y[s + 2, :] = _column_convolve(X[xe[tb], :], hae) Y[s + 3, :] = _column_convolve(X[xe[ta], :], hbe) return Y