def local_contrast_normalise(s, n=7, c=None): """Local contrast normalisation of an image. Perform local contrast normalisation :cite:`jarret-2009-what` of an image, consisting of subtraction of the local mean and division by the local norm. The original image can be reconstructed from the contrast normalised image as (`snrm` * `scn`) + `smn`. Parameters ---------- s : array_like Input image or array of images. n : int, optional (default 7) The size of the local region used for normalisation is :math:`2n+1`. c : float, optional (default None) The smallest value that can be used in the divisive normalisation. If `None`, this value is set to the mean of the local region norms. Returns ------- scn : ndarray Contrast normalised image(s) smn : ndarray Additive normalisation correction snrm : ndarray Multiplicative normalisation correction """ # Construct region weighting filter N = 2 * n + 1 g = gaussian((N, N), sd=1.0) # Compute required image padding pd = ((n, n), ) * 2 if s.ndim > 2: g = g[..., np.newaxis] pd += ((0, 0), ) sp = np.pad(s, pd, mode='symmetric') # Compute local mean and subtract from image smn = np.roll(fftconv(g, sp), (-n, -n), axis=(0, 1)) s1 = sp - smn # Compute local norm snrm = np.roll(np.sqrt(np.clip(fftconv(g, s1**2), 0.0, np.inf)), (-n, -n), axis=(0, 1)) # Set c parameter if not specified if c is None: c = np.mean(snrm, axis=(0, 1), keepdims=True) # Divide mean-subtracted image by corrected local norm snrm = np.maximum(c, snrm) s2 = s1 / snrm # Return contrast normalised image and normalisation components return s2[n:-n, n:-n], smn[n:-n, n:-n], snrm[n:-n, n:-n]
def test_10(self): N = 64 M = 4 Nd = 8 D = np.random.randn(Nd, Nd, M) X0 = np.zeros((N, N, M)) xr = np.random.randn(N, N, M) xp = np.abs(xr) > 3 X0[xp] = np.random.randn(X0[xp].size) S = np.sum(fftconv(D, X0), axis=2) lmbda = 1e-4 rho = 1e-1 opt = cbpdn.ConvBPDN.Options({ 'Verbose': False, 'MaxMainIter': 500, 'RelStopTol': 1e-3, 'rho': rho, 'AutoRho': { 'Enabled': False } }) b = cbpdn.ConvBPDN(D, S, lmbda, opt) b.solve() X1 = b.Y.squeeze() assert rrs(X0, X1) < 5e-5 Sr = b.reconstruct().squeeze() assert rrs(S, Sr) < 1e-4
def test_04(self): x = np.random.randn(5, ) y = np.zeros((12, )) y[4] = 1.0 xy0 = convolve(y, x) xy1 = fft.fftconv(x, y, axes=(0, ), origin=(2, )) assert np.allclose(xy0, xy1)
def get_psf(self, subpixel=True, tkhflt=False, tkhlmb=1e-3): """Get the estimated psf. If parameter `subpixel` is True, the subpixel resolution psf is returned, constructed by interpolation of the psf estimated at the resolution of the input image. """ if subpixel: Hl = lanczos_filters((self.M, self.M), self.K, collapse_axes=False) gp = np.pad(self.g, ((0, self.img.shape[0] - self.gshp[0]), (0, self.img.shape[1] - self.gshp[1])), 'constant') grsp = fftconv(Hl, gp[..., np.newaxis, np.newaxis], origin=(self.K, self.K), axes=(0, 1), )[0:self.gshp[0], 0:self.gshp[1]] shp = tuple(np.array(self.gshp) * self.M) gsub = np.transpose(grsp, (0, 2, 1, 3)).reshape(shp) gsub[gsub < 0.0] = 0.0 if tkhflt: gsub, shp = tikhonov_filter(gsub, tkhlmb) gsub /= np.linalg.norm(gsub) return gsub else: return self.g / np.linalg.norm(self.g)
def setup_method(self, method): np.random.seed(12345) N = 32 M = 4 Nd = 5 self.D0 = cr.normalise(cr.zeromean(np.random.randn(Nd, Nd, M), (Nd, Nd, M), dimN=2), dimN=2) self.X = np.zeros((N, N, M)) xr = np.random.randn(N, N, M) xp = np.abs(xr) > 3 self.X[xp] = np.random.randn(self.X[xp].size) self.S = np.sum(fftconv(self.X, self.D0, axes=(0, 1)).real, axis=2) d0c = np.random.randn(Nd, Nd, M) + 1j * np.random.randn(Nd, Nd, M) self.D0c = cr.normalise(cr.zeromean(d0c, (Nd, Nd, M), dimN=2), dimN=2) self.Xc = np.zeros((N, N, M)) + 1j * np.zeros((N, N, M)) self.Xc[xp] = (np.random.randn(self.Xc[xp].size) + 1j * np.random.randn(self.Xc[xp].size)) self.Sc = np.sum(fftconv(self.Xc, self.D0c, axes=(0, 1)), axis=2)
def solve(self): """Start (or re-start) optimisation.""" # Set up display header if verbose operation enabled if self.opt['Verbose']: hdr = 'Itn DFidX PriResX DuaResX DFidG' + \ ' ResG ' print(hdr) print('-' * len(hdr)) # Main iteration loop for n in range(self.opt['MaxMainIter']): # At start of 2nd iteration, set the numbers of inner # iterations for the X and G solvers from the options # object for the outer solver if n == 1: self.slvX.opt['MaxMainIter'] = self.opt['XslvIter'] self.slvG.opt['MaxMainIter'] = self.opt['GslvIter'] # Run the configured number of iterations of the X (CSC) # solver and assign the result to X self.X = self.slvX.solve() # Compute the sum of the subpixel shifts of X Xhs = np.sum(fftconv(self.H, self.X.squeeze(), axes=(0, 1)), axis=-1) # Set the convolution kernel in the deconvolution solver # to the sum of the subpixel shifts of X self.slvG.setG(Xhs) # Run the configured number of iterations of the G # (deconvolution) solver and crop the result to obtain the # updated g self.g = self.slvG.solve()[0:self.gshp[0], 0:self.gshp[1]] # Construct a new dictionary for the X (CSC) solver from # the updated psf g self.D, self.dn = self.getD(self.g) self.slvX.setdict(self.D[..., np.newaxis, np.newaxis, :]) # Display iteration statistics if verbose operation enabled if self.opt['Verbose']: itsX = self.slvX.getitstat() itsG = self.slvG.getitstat() fmt = '%3d %.3e %.3e %.3e %.3e %.3e' tpl = (n, itsX.DFid[-1], itsX.PrimalRsdl[-1], itsX.DualRsdl[-1], itsG.DFid[-1], itsG.Rsdl[-1]) print(fmt % tpl) # Return the (normalised) psf estimate g return self.g / np.linalg.norm(self.g)
def getD(self, g): """Construct the CSC dictionary corresponding to psf `g`.""" # Zero pad g to avoid boundary effects d = np.pad(g, ((0, self.img.shape[0] - g.shape[0]), (0, self.img.shape[1] - g.shape[1])), 'constant') # Convolve padded g with set of interpolation filters to # construct a set of subpixel shifted versions of g D = fftconv(d[..., np.newaxis], self.H, axes=(0, 1)) # Get dictionary filter norms dn = np.sqrt(np.sum(D**2, axis=(0, 1), keepdims=True)) return D, dn
def interpolate(x, M=5, K=10): """Lanczos interpolation of 1D or 2D array. Parameters ---------- x : ndarray Array to be interpolated M : int, optional Interpolation factor K : int, optional Order of Lanczos filters Returns ------- ndarray Input `x` interpolated to higher resolution """ if x.ndim == 1: Hl = lanczos_filters((M, ), K, collapse_axes=False) xp = np.pad(x, ((0, Hl.shape[0] - 1), ), 'constant') xrsp = fftconv(Hl, xp[..., np.newaxis], axes=(0, ), origin=(K, ))[0:x.shape[0], ] shp = tuple(np.array(x.shape) * M) xsub = xrsp.reshape(shp) else: Hl = lanczos_filters((M, M), K, collapse_axes=False) xp = np.pad(x, ((0, Hl.shape[0] - 1), (0, Hl.shape[1] - 1)), 'constant') xrsp = fftconv(Hl, xp[..., np.newaxis, np.newaxis], axes=(0, 1), origin=(K, K))[0:x.shape[0], 0:x.shape[1]] shp = tuple(np.array(x.shape) * M) xsub = np.transpose(xrsp, (0, 2, 1, 3)).reshape(shp) return xsub
def test_22(self): N = 32 M = 4 Nd = 8 D = np.random.randn(Nd, Nd, M) D /= np.sqrt(np.sum(D**2, axis=(0, 1))) X0 = np.zeros((N, N, M)) xr = np.random.randn(N, N, M) xp = np.abs(xr) > 3 X0[xp] = np.random.randn(X0[xp].size) S = np.sum(fftconv(D, X0), axis=2) lmbda = 1e-3 opt = cbpdn.ConvBPDN.Options({ 'Verbose': False, 'MaxMainIter': 500, 'RelStopTol': 1e-5, 'rho': 5e-1, 'AutoRho': { 'Enabled': False } }) bp = cbpdn.ConvBPDN(D, S, lmbda, opt) Xp = bp.solve() epsilon = np.linalg.norm(bp.reconstruct(Xp).squeeze() - S) opt = cbpdn.ConvMinL1InL2Ball.Options({ 'Verbose': False, 'MaxMainIter': 500, 'RelStopTol': 1e-5, 'rho': 2e2, 'RelaxParam': 1.0, 'AutoRho': { 'Enabled': False } }) bc = cbpdn.ConvMinL1InL2Ball(D, S, epsilon=epsilon, opt=opt) Xc = bc.solve() assert np.linalg.norm(Xp - Xc) / np.linalg.norm(Xp) < 1e-3 assert np.abs( np.linalg.norm(Xp.ravel(), 1) - np.linalg.norm(Xc.ravel(), 1)) < 1e-3
def test_03(self): x = np.array([[0, 1], [2, 3]]) y = np.array([[4, 5], [6, 7]]) xy = np.array([[38, 36], [30, 28]]) assert np.allclose(fft.fftconv(x, y, axes=(0, 1)), xy)
else: id = select_device_by_load() info = gpu_info() if info: print('Running on GPU %d (%s)\n' % (id, info[id].name)) b = pdcsc.ConvProdDictBPDN(np2cp(D), np2cp(B), np2cp(shc), lmbda, opt, dimK=0) X = cp2np(b.solve()) print("ConvProdDictBPDN solve time: %.2fs" % b.timer.elapsed('solve')) """ Compute partial and full reconstructions from sparse representation $X$ with respect to convolutional dictionary $D$ and standard dictionary $B$. The partial reconstructions are $DX$ and $XB$, and the full reconstruction is $DXB$. """ DX = fft.fftconv(D[..., np.newaxis, np.newaxis, :], X, axes=(0, 1)) XB = linalg.dot(B, X, axis=2) shr = cp2np(b.reconstruct().squeeze()) imgr = slc + shr print("Reconstruction PSNR: %.2fdB\n" % metric.psnr(img, imgr)) """ Display original and reconstructed images. """ gamma = lambda x, g: np.sign(x) * (np.abs(x)**g) fig, ax = plot.subplots(nrows=2, ncols=2, figsize=(14, 14)) plot.imview(img, title='Original image', ax=ax[0, 0], fig=fig) plot.imview(slc, title='Lowpass component', ax=ax[0, 1], fig=fig)
'rho': 5e1*lmbda + 1e-1, 'AutoRho': {'Enabled': False, 'StdResiduals': False}}) """ Construct :class:`.admm.cbpdn.AddMaskSim` wrapper for :class:`.admm.cbpdn.ConvBPDN` and solve via wrapper. This example could also have made use of :class:`.admm.cbpdn.ConvBPDNMaskDcpl` (see example `cbpdn_md_gry`), which has similar performance in this application, but :class:`.admm.cbpdn.AddMaskSim` has the advantage of greater flexibility in that the wrapper can be applied to a variety of CSC solver objects. If the ``sporco-cuda`` extension is installed and a GPU is available, use the CUDA implementation of this combination. """ if cuda.device_count() > 0: ams = None print('%s GPU found: running CUDA solver' % cuda.device_name()) tm = util.Timer() with sys_pipes(), util.ContextTimer(tm): X = cuda.cbpdnmsk(D, sh, mskp, lmbda, opt) t = tm.elapsed() imgr = crop(sl + np.sum(fftconv(D, X, axes=(0, 1)), axis=-1)) else: ams = cbpdn.AddMaskSim(cbpdn.ConvBPDN, D, sh, mskp, lmbda, opt=opt) X = ams.solve() t = ams.timer.elapsed('solve') imgr = crop(sl + ams.reconstruct().squeeze()) """ Display solve time and reconstruction performance. """ print("AddMaskSim wrapped ConvBPDN solve time: %.2fs" % t) print("Corrupted image PSNR: %5.2f dB" % metric.psnr(img, imgw)) print("Recovered image PSNR: %5.2f dB" % metric.psnr(img, imgr))
if not cupy_enabled(): print('CuPy/GPU device not available: running without GPU acceleration\n') else: id = select_device_by_load() info = gpu_info() if info: print('Running on GPU %d (%s)\n' % (id, info[id].name)) b = pdcsc.ConvProdDictBPDN(np2cp(D), np2cp(B), np2cp(shc), lmbda, opt, dimK=0) X = cp2np(b.solve()) print("ConvProdDictBPDN solve time: %.2fs" % b.timer.elapsed('solve')) """ Compute partial and full reconstructions from sparse representation $X$ with respect to convolutional dictionary $D$ and standard dictionary $B$. The partial reconstructions are $DX$ and $XB$, and the full reconstruction is $DXB$. """ DX = fft.fftconv(D[..., np.newaxis, np.newaxis, :], X) XB = linalg.dot(B, X, axis=2) shr = cp2np(b.reconstruct().squeeze()) imgr = slc + shr print("Reconstruction PSNR: %.2fdB\n" % metric.psnr(img, imgr)) """ Display original and reconstructed images. """ gamma = lambda x, g: np.sign(x) * (np.abs(x)**g) fig, ax = plot.subplots(nrows=2, ncols=2, figsize=(14, 14)) plot.imview(img, title='Original image', ax=ax[0, 0], fig=fig) plot.imview(slc, title='Lowpass component', ax=ax[0, 1], fig=fig) plot.imview(imgr, title='Reconstructed image', ax=ax[1, 0], fig=fig) plot.imview(gamma(shr, 0.6),
} }) """ Construct :class:`.admm.cbpdn.AddMaskSim` wrapper for :class:`.admm.cbpdn.ConvBPDNGradReg` and solve via wrapper. If the ``sporco-cuda`` extension is installed and a GPU is available, use the CUDA implementation of this combination. """ if cuda.device_count() > 0: opt['L1Weight'] = wl1 opt['GradWeight'] = wgr ams = None print('%s GPU found: running CUDA solver' % cuda.device_name()) tm = util.Timer() with sys_pipes(), util.ContextTimer(tm): X = cuda.cbpdngrdmsk(Di, imgwp, mskp, lmbda, mu, opt) t = tm.elapsed() imgr = crop(np.sum(fftconv(Di, X, axes=(0, 1)), axis=-1)) else: opt['L1Weight'] = wl1i opt['GradWeight'] = wgri ams = cbpdn.AddMaskSim(cbpdn.ConvBPDNGradReg, Di, imgwp, mskp, lmbda, mu, opt=opt) X = ams.solve().squeeze() t = ams.timer.elapsed('solve') imgr = crop(ams.reconstruct().squeeze()) """ Display solve time and reconstruction performance.
with sys_pipes(), util.ContextTimer(tm): X = cuda.cbpdn(D, sh, lmbda, opt) t = tm.elapsed() else: print('GPU not found: running Python solver') c = cbpdn.ConvBPDN(D, sh, lmbda, opt) X = c.solve().squeeze() t = c.timer.elapsed('solve') print('Solve time: %.2f s' % t) """ Reconstruct the image from the sparse representation. """ shr = np.sum(fft.fftconv(D, X, axes=(0, 1)), axis=2) imgr = sl + shr print("Reconstruction PSNR: %.2fdB\n" % metric.psnr(img, imgr)) """ Display representation and reconstructed image. """ fig = plot.figure(figsize=(14, 14)) plot.subplot(2, 2, 1) plot.imview(sl, title='Lowpass component', fig=fig) plot.subplot(2, 2, 2) plot.imview(np.sum(abs(X), axis=2).squeeze(), cmap=plot.cm.Blues, title='Main representation', fig=fig) plot.subplot(2, 2, 3)
'Enabled': False, 'StdResiduals': False } }) """ Construct :class:`.admm.cbpdn.AddMaskSim` wrapper for :class:`.admm.cbpdn.ConvBPDN` and solve via wrapper. This example could also have made use of :class:`.admm.cbpdn.ConvBPDNMaskDcpl` (see example `cbpdn_md_gry`), which has similar performance in this application, but :class:`.admm.cbpdn.AddMaskSim` has the advantage of greater flexibility in that the wrapper can be applied to a variety of CSC solver objects. If the ``sporco-cuda`` extension is installed and a GPU is available, use the CUDA implementation of this combination. """ if cuda.device_count() > 0: ams = None print('%s GPU found: running CUDA solver' % cuda.device_name()) tm = util.Timer() with sys_pipes(), util.ContextTimer(tm): X = cuda.cbpdnmsk(D, sh, mskp, lmbda, opt) t = tm.elapsed() imgr = crop(sl + np.sum(fftconv(D, X), axis=-1)) else: ams = cbpdn.AddMaskSim(cbpdn.ConvBPDN, D, sh, mskp, lmbda, opt=opt) X = ams.solve() t = ams.timer.elapsed('solve') imgr = crop(sl + ams.reconstruct().squeeze()) """ Display solve time and reconstruction performance. """ print("AddMaskSim wrapped ConvBPDN solve time: %.2fs" % t) print("Corrupted image PSNR: %5.2f dB" % metric.psnr(img, imgw)) print("Recovered image PSNR: %5.2f dB" % metric.psnr(img, imgr)) """ Display reference, test, and reconstructed image """
} }) """ Construct :class:`.admm.cbpdn.AddMaskSim` wrapper for :class:`.admm.cbpdn.ConvBPDNGradReg` and solve via wrapper. If the ``sporco-cuda`` extension is installed and a GPU is available, use the CUDA implementation of this combination. """ if cuda.device_count() > 0: opt['L1Weight'] = wl1 opt['GradWeight'] = wgr ams = None print('%s GPU found: running CUDA solver' % cuda.device_name()) tm = util.Timer() with sys_pipes(), util.ContextTimer(tm): X = cuda.cbpdngrdmsk(Di, imgwp, mskp, lmbda, mu, opt) t = tm.elapsed() imgr = crop(np.sum(fftconv(Di, X), axis=-1)) else: opt['L1Weight'] = wl1i opt['GradWeight'] = wgri ams = cbpdn.AddMaskSim(cbpdn.ConvBPDNGradReg, Di, imgwp, mskp, lmbda, mu, opt=opt) X = ams.solve().squeeze() t = ams.timer.elapsed('solve') imgr = crop(ams.reconstruct().squeeze()) """ Display solve time and reconstruction performance.