# -*- coding: utf-8 -*- import numpy as np import scipy from scipy import fftpack import torch from math import cos, sin from numpy import zeros, ones, prod, array, pi, log, min, mod, arange, sum, mgrid, exp, pad, round from numpy.random import randn, rand from scipy.signal import convolve2d import cv2 import random # import utils_image as util ''' modified by Kai Zhang (github: https://github.com/cszn) 03/03/2019 ''' def get_uperleft_denominator(img, kernel): ''' img: HxWxC kernel: hxw denominator: HxWx1 upperleft: HxWxC ''' V = psf2otf(kernel, img.shape[:2]) denominator = np.expand_dims(np.abs(V)**2, axis=2) upperleft = np.expand_dims(np.conj(V), axis=2) * np.fft.fft2(img, axes=[0, 1]) return upperleft, denominator def get_uperleft_denominator_pytorch(img, kernel): ''' img: NxCxHxW kernel: Nx1xhxw denominator: Nx1xHxW upperleft: NxCxHxWx2 ''' V = p2o(kernel, img.shape[-2:]) # Nx1xHxWx2 denominator = V[..., 0]**2+V[..., 1]**2 # Nx1xHxW upperleft = cmul(cconj(V), rfft(img)) # Nx1xHxWx2 * NxCxHxWx2 return upperleft, denominator def c2c(x): return torch.from_numpy(np.stack([np.float32(x.real), np.float32(x.imag)], axis=-1)) def r2c(x): return torch.stack([x, torch.zeros_like(x)], -1) def cdiv(x, y): a, b = x[..., 0], x[..., 1] c, d = y[..., 0], y[..., 1] cd2 = c**2 + d**2 return torch.stack([(a*c+b*d)/cd2, (b*c-a*d)/cd2], -1) def cabs(x): return torch.pow(x[..., 0]**2+x[..., 1]**2, 0.5) def cmul(t1, t2): ''' complex multiplication t1: NxCxHxWx2 output: NxCxHxWx2 ''' real1, imag1 = t1[..., 0], t1[..., 1] real2, imag2 = t2[..., 0], t2[..., 1] return torch.stack([real1 * real2 - imag1 * imag2, real1 * imag2 + imag1 * real2], dim=-1) def cconj(t, inplace=False): ''' # complex's conjugation t: NxCxHxWx2 output: NxCxHxWx2 ''' c = t.clone() if not inplace else t c[..., 1] *= -1 return c def rfft(t): return torch.rfft(t, 2, onesided=False) def irfft(t): return torch.irfft(t, 2, onesided=False) def fft(t): return torch.fft(t, 2) def ifft(t): return torch.ifft(t, 2) def p2o(psf, shape): ''' # psf: NxCxhxw # shape: [H,W] # otf: NxCxHxWx2 ''' otf = torch.zeros(psf.shape[:-2] + shape).type_as(psf) otf[...,:psf.shape[2],:psf.shape[3]].copy_(psf) for axis, axis_size in enumerate(psf.shape[2:]): otf = torch.roll(otf, -int(axis_size / 2), dims=axis+2) otf = torch.rfft(otf, 2, onesided=False) n_ops = torch.sum(torch.tensor(psf.shape).type_as(psf) * torch.log2(torch.tensor(psf.shape).type_as(psf))) otf[...,1][torch.abs(otf[...,1])<n_ops*2.22e-16] = torch.tensor(0).type_as(psf) return otf # otf2psf: not sure where I got this one from. Maybe translated from Octave source code or whatever. It's just math. def otf2psf(otf, outsize=None): insize = np.array(otf.shape) psf = np.fft.ifftn(otf, axes=(0, 1)) for axis, axis_size in enumerate(insize): psf = np.roll(psf, np.floor(axis_size / 2).astype(int), axis=axis) if type(outsize) != type(None): insize = np.array(otf.shape) outsize = np.array(outsize) n = max(np.size(outsize), np.size(insize)) # outsize = postpad(outsize(:), n, 1); # insize = postpad(insize(:) , n, 1); colvec_out = outsize.flatten().reshape((np.size(outsize), 1)) colvec_in = insize.flatten().reshape((np.size(insize), 1)) outsize = np.pad(colvec_out, ((0, max(0, n - np.size(colvec_out))), (0, 0)), mode="constant") insize = np.pad(colvec_in, ((0, max(0, n - np.size(colvec_in))), (0, 0)), mode="constant") pad = (insize - outsize) / 2 if np.any(pad < 0): print("otf2psf error: OUTSIZE must be smaller than or equal than OTF size") prepad = np.floor(pad) postpad = np.ceil(pad) dims_start = prepad.astype(int) dims_end = (insize - postpad).astype(int) for i in range(len(dims_start.shape)): psf = np.take(psf, range(dims_start[i][0], dims_end[i][0]), axis=i) n_ops = np.sum(otf.size * np.log2(otf.shape)) psf = np.real_if_close(psf, tol=n_ops) return psf # psf2otf copied/modified from https://github.com/aboucaud/pypher/blob/master/pypher/pypher.py def psf2otf(psf, shape=None): """ Convert point-spread function to optical transfer function. Compute the Fast Fourier Transform (FFT) of the point-spread function (PSF) array and creates the optical transfer function (OTF) array that is not influenced by the PSF off-centering. By default, the OTF array is the same size as the PSF array. To ensure that the OTF is not altered due to PSF off-centering, PSF2OTF post-pads the PSF array (down or to the right) with zeros to match dimensions specified in OUTSIZE, then circularly shifts the values of the PSF array up (or to the left) until the central pixel reaches (1,1) position. Parameters ---------- psf : `numpy.ndarray` PSF array shape : int Output shape of the OTF array Returns ------- otf : `numpy.ndarray` OTF array Notes ----- Adapted from MATLAB psf2otf function """ if type(shape) == type(None): shape = psf.shape shape = np.array(shape) if np.all(psf == 0): # return np.zeros_like(psf) return np.zeros(shape) if len(psf.shape) == 1: psf = psf.reshape((1, psf.shape[0])) inshape = psf.shape psf = zero_pad(psf, shape, position='corner') for axis, axis_size in enumerate(inshape): psf = np.roll(psf, -int(axis_size / 2), axis=axis) # Compute the OTF otf = np.fft.fft2(psf, axes=(0, 1)) # Estimate the rough number of operations involved in the FFT # and discard the PSF imaginary part if within roundoff error # roundoff error = machine epsilon = sys.float_info.epsilon # or np.finfo().eps n_ops = np.sum(psf.size * np.log2(psf.shape)) otf = np.real_if_close(otf, tol=n_ops) return otf def zero_pad(image, shape, position='corner'): """ Extends image to a certain size with zeros Parameters ---------- image: real 2d `numpy.ndarray` Input image shape: tuple of int Desired output shape of the image position : str, optional The position of the input image in the output one: * 'corner' top-left corner (default) * 'center' centered Returns ------- padded_img: real `numpy.ndarray` The zero-padded image """ shape = np.asarray(shape, dtype=int) imshape = np.asarray(image.shape, dtype=int) if np.alltrue(imshape == shape): return image if np.any(shape <= 0): raise ValueError("ZERO_PAD: null or negative shape given") dshape = shape - imshape if np.any(dshape < 0): raise ValueError("ZERO_PAD: target size smaller than source one") pad_img = np.zeros(shape, dtype=image.dtype) idx, idy = np.indices(imshape) if position == 'center': if np.any(dshape % 2 != 0): raise ValueError("ZERO_PAD: source and target shapes " "have different parity.") offx, offy = dshape // 2 else: offx, offy = (0, 0) pad_img[idx + offx, idy + offy] = image return pad_img ''' Reducing boundary artifacts ''' def opt_fft_size(n): ''' Kai Zhang (github: https://github.com/cszn) 03/03/2019 # opt_fft_size.m # compute an optimal data length for Fourier transforms # written by Sunghyun Cho (sodomau@postech.ac.kr) # persistent opt_fft_size_LUT; ''' LUT_size = 2048 # print("generate opt_fft_size_LUT") opt_fft_size_LUT = np.zeros(LUT_size) e2 = 1 while e2 <= LUT_size: e3 = e2 while e3 <= LUT_size: e5 = e3 while e5 <= LUT_size: e7 = e5 while e7 <= LUT_size: if e7 <= LUT_size: opt_fft_size_LUT[e7-1] = e7 if e7*11 <= LUT_size: opt_fft_size_LUT[e7*11-1] = e7*11 if e7*13 <= LUT_size: opt_fft_size_LUT[e7*13-1] = e7*13 e7 = e7 * 7 e5 = e5 * 5 e3 = e3 * 3 e2 = e2 * 2 nn = 0 for i in range(LUT_size, 0, -1): if opt_fft_size_LUT[i-1] != 0: nn = i-1 else: opt_fft_size_LUT[i-1] = nn+1 m = np.zeros(len(n)) for c in range(len(n)): nn = n[c] if nn <= LUT_size: m[c] = opt_fft_size_LUT[nn-1] else: m[c] = -1 return m def wrap_boundary_liu(img, img_size): """ Reducing boundary artifacts in image deconvolution Renting Liu, Jiaya Jia ICIP 2008 """ if img.ndim == 2: ret = wrap_boundary(img, img_size) elif img.ndim == 3: ret = [wrap_boundary(img[:, :, i], img_size) for i in range(3)] ret = np.stack(ret, 2) return ret def wrap_boundary(img, img_size): """ python code from: https://github.com/ys-koshelev/nla_deblur/blob/90fe0ab98c26c791dcbdf231fe6f938fca80e2a0/boundaries.py Reducing boundary artifacts in image deconvolution Renting Liu, Jiaya Jia ICIP 2008 """ (H, W) = np.shape(img) H_w = int(img_size[0]) - H W_w = int(img_size[1]) - W # ret = np.zeros((img_size[0], img_size[1])); alpha = 1 HG = img[:, :] r_A = np.zeros((alpha*2+H_w, W)) r_A[:alpha, :] = HG[-alpha:, :] r_A[-alpha:, :] = HG[:alpha, :] a = np.arange(H_w)/(H_w-1) # r_A(alpha+1:end-alpha, 1) = (1-a)*r_A(alpha,1) + a*r_A(end-alpha+1,1) r_A[alpha:-alpha, 0] = (1-a)*r_A[alpha-1, 0] + a*r_A[-alpha, 0] # r_A(alpha+1:end-alpha, end) = (1-a)*r_A(alpha,end) + a*r_A(end-alpha+1,end) r_A[alpha:-alpha, -1] = (1-a)*r_A[alpha-1, -1] + a*r_A[-alpha, -1] r_B = np.zeros((H, alpha*2+W_w)) r_B[:, :alpha] = HG[:, -alpha:] r_B[:, -alpha:] = HG[:, :alpha] a = np.arange(W_w)/(W_w-1) r_B[0, alpha:-alpha] = (1-a)*r_B[0, alpha-1] + a*r_B[0, -alpha] r_B[-1, alpha:-alpha] = (1-a)*r_B[-1, alpha-1] + a*r_B[-1, -alpha] if alpha == 1: A2 = solve_min_laplacian(r_A[alpha-1:, :]) B2 = solve_min_laplacian(r_B[:, alpha-1:]) r_A[alpha-1:, :] = A2 r_B[:, alpha-1:] = B2 else: A2 = solve_min_laplacian(r_A[alpha-1:-alpha+1, :]) r_A[alpha-1:-alpha+1, :] = A2 B2 = solve_min_laplacian(r_B[:, alpha-1:-alpha+1]) r_B[:, alpha-1:-alpha+1] = B2 A = r_A B = r_B r_C = np.zeros((alpha*2+H_w, alpha*2+W_w)) r_C[:alpha, :] = B[-alpha:, :] r_C[-alpha:, :] = B[:alpha, :] r_C[:, :alpha] = A[:, -alpha:] r_C[:, -alpha:] = A[:, :alpha] if alpha == 1: C2 = C2 = solve_min_laplacian(r_C[alpha-1:, alpha-1:]) r_C[alpha-1:, alpha-1:] = C2 else: C2 = solve_min_laplacian(r_C[alpha-1:-alpha+1, alpha-1:-alpha+1]) r_C[alpha-1:-alpha+1, alpha-1:-alpha+1] = C2 C = r_C # return C A = A[alpha-1:-alpha-1, :] B = B[:, alpha:-alpha] C = C[alpha:-alpha, alpha:-alpha] ret = np.vstack((np.hstack((img, B)), np.hstack((A, C)))) return ret def solve_min_laplacian(boundary_image): (H, W) = np.shape(boundary_image) # Laplacian f = np.zeros((H, W)) # boundary image contains image intensities at boundaries boundary_image[1:-1, 1:-1] = 0 j = np.arange(2, H)-1 k = np.arange(2, W)-1 f_bp = np.zeros((H, W)) f_bp[np.ix_(j, k)] = -4*boundary_image[np.ix_(j, k)] + boundary_image[np.ix_(j, k+1)] + boundary_image[np.ix_(j, k-1)] + boundary_image[np.ix_(j-1, k)] + boundary_image[np.ix_(j+1, k)] del(j, k) f1 = f - f_bp # subtract boundary points contribution del(f_bp, f) # DST Sine Transform algo starts here f2 = f1[1:-1,1:-1] del(f1) # compute sine tranform if f2.shape[1] == 1: tt = fftpack.dst(f2, type=1, axis=0)/2 else: tt = fftpack.dst(f2, type=1)/2 if tt.shape[0] == 1: f2sin = np.transpose(fftpack.dst(np.transpose(tt), type=1, axis=0)/2) else: f2sin = np.transpose(fftpack.dst(np.transpose(tt), type=1)/2) del(f2) # compute Eigen Values [x, y] = np.meshgrid(np.arange(1, W-1), np.arange(1, H-1)) denom = (2*np.cos(np.pi*x/(W-1))-2) + (2*np.cos(np.pi*y/(H-1)) - 2) # divide f3 = f2sin/denom del(f2sin, x, y) # compute Inverse Sine Transform if f3.shape[0] == 1: tt = fftpack.idst(f3*2, type=1, axis=1)/(2*(f3.shape[1]+1)) else: tt = fftpack.idst(f3*2, type=1, axis=0)/(2*(f3.shape[0]+1)) del(f3) if tt.shape[1] == 1: img_tt = np.transpose(fftpack.idst(np.transpose(tt)*2, type=1)/(2*(tt.shape[0]+1))) else: img_tt = np.transpose(fftpack.idst(np.transpose(tt)*2, type=1, axis=0)/(2*(tt.shape[1]+1))) del(tt) # put solution in inner points; outer points obtained from boundary image img_direct = boundary_image img_direct[1:-1, 1:-1] = 0 img_direct[1:-1, 1:-1] = img_tt return img_direct """ Created on Thu Jan 18 15:36:32 2018 @author: italo https://github.com/ronaldosena/imagens-medicas-2/blob/40171a6c259edec7827a6693a93955de2bd39e76/Aulas/aula_2_-_uniform_filter/matlab_fspecial.py """ """ Syntax h = fspecial(type) h = fspecial('average',hsize) h = fspecial('disk',radius) h = fspecial('gaussian',hsize,sigma) h = fspecial('laplacian',alpha) h = fspecial('log',hsize,sigma) h = fspecial('motion',len,theta) h = fspecial('prewitt') h = fspecial('sobel') """ def fspecial_average(hsize=3): """Smoothing filter""" return np.ones((hsize, hsize))/hsize**2 def fspecial_disk(radius): """Disk filter""" raise(NotImplemented) rad = 0.6 crad = np.ceil(rad-0.5) [x, y] = np.meshgrid(np.arange(-crad, crad+1), np.arange(-crad, crad+1)) maxxy = np.zeros(x.shape) maxxy[abs(x) >= abs(y)] = abs(x)[abs(x) >= abs(y)] maxxy[abs(y) >= abs(x)] = abs(y)[abs(y) >= abs(x)] minxy = np.zeros(x.shape) minxy[abs(x) <= abs(y)] = abs(x)[abs(x) <= abs(y)] minxy[abs(y) <= abs(x)] = abs(y)[abs(y) <= abs(x)] m1 = (rad**2 < (maxxy+0.5)**2 + (minxy-0.5)**2)*(minxy-0.5) +\ (rad**2 >= (maxxy+0.5)**2 + (minxy-0.5)**2)*\ np.sqrt((rad**2 + 0j) - (maxxy + 0.5)**2) m2 = (rad**2 > (maxxy-0.5)**2 + (minxy+0.5)**2)*(minxy+0.5) +\ (rad**2 <= (maxxy-0.5)**2 + (minxy+0.5)**2)*\ np.sqrt((rad**2 + 0j) - (maxxy - 0.5)**2) h = None return h def fspecial_gaussian(hsize, sigma): hsize = [hsize, hsize] siz = [(hsize[0]-1.0)/2.0, (hsize[1]-1.0)/2.0] std = sigma [x, y] = np.meshgrid(np.arange(-siz[1], siz[1]+1), np.arange(-siz[0], siz[0]+1)) arg = -(x*x + y*y)/(2*std*std) h = np.exp(arg) h[h < scipy.finfo(float).eps * h.max()] = 0 sumh = h.sum() if sumh != 0: h = h/sumh return h def fspecial_laplacian(alpha): alpha = max([0, min([alpha,1])]) h1 = alpha/(alpha+1) h2 = (1-alpha)/(alpha+1) h = [[h1, h2, h1], [h2, -4/(alpha+1), h2], [h1, h2, h1]] h = np.array(h) return h def fspecial_log(hsize, sigma): raise(NotImplemented) def fspecial_motion(motion_len, theta): raise(NotImplemented) def fspecial_prewitt(): return np.array([[1, 1, 1], [0, 0, 0], [-1, -1, -1]]) def fspecial_sobel(): return np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]]) def fspecial(filter_type, *args, **kwargs): ''' python code from: https://github.com/ronaldosena/imagens-medicas-2/blob/40171a6c259edec7827a6693a93955de2bd39e76/Aulas/aula_2_-_uniform_filter/matlab_fspecial.py ''' if filter_type == 'average': return fspecial_average(*args, **kwargs) if filter_type == 'disk': return fspecial_disk(*args, **kwargs) if filter_type == 'gaussian': return fspecial_gaussian(*args, **kwargs) if filter_type == 'laplacian': return fspecial_laplacian(*args, **kwargs) if filter_type == 'log': return fspecial_log(*args, **kwargs) if filter_type == 'motion': return fspecial_motion(*args, **kwargs) if filter_type == 'prewitt': return fspecial_prewitt(*args, **kwargs) if filter_type == 'sobel': return fspecial_sobel(*args, **kwargs) def fspecial_gauss(size, sigma): x, y = mgrid[-size // 2 + 1 : size // 2 + 1, -size // 2 + 1 : size // 2 + 1] g = exp(-((x ** 2 + y ** 2) / (2.0 * sigma ** 2))) return g / g.sum() def blurkernel_synthesis(h=37, w=None): # https://github.com/tkkcc/prior/blob/879a0b6c117c810776d8cc6b63720bf29f7d0cc4/util/gen_kernel.py w = h if w is None else w kdims = [h, w] x = randomTrajectory(250) k = None while k is None: k = kernelFromTrajectory(x) # center pad to kdims pad_width = ((kdims[0] - k.shape[0]) // 2, (kdims[1] - k.shape[1]) // 2) pad_width = [(pad_width[0],), (pad_width[1],)] if pad_width[0][0]<0 or pad_width[1][0]<0: k = k[0:h, 0:h] else: k = pad(k, pad_width, "constant") x1,x2 = k.shape if np.random.randint(0, 4) == 1: k = cv2.resize(k, (random.randint(x1, 5*x1), random.randint(x2, 5*x2)), interpolation=cv2.INTER_LINEAR) y1, y2 = k.shape k = k[(y1-x1)//2: (y1-x1)//2+x1, (y2-x2)//2: (y2-x2)//2+x2] if sum(k)<0.1: k = fspecial_gaussian(h, 0.1+6*np.random.rand(1)) k = k / sum(k) # import matplotlib.pyplot as plt # plt.imshow(k, interpolation="nearest", cmap="gray") # plt.show() return k def kernelFromTrajectory(x): h = 5 - log(rand()) / 0.15 h = round(min([h, 27])).astype(int) h = h + 1 - h % 2 w = h k = zeros((h, w)) xmin = min(x[0]) xmax = max(x[0]) ymin = min(x[1]) ymax = max(x[1]) xthr = arange(xmin, xmax, (xmax - xmin) / w) ythr = arange(ymin, ymax, (ymax - ymin) / h) for i in range(1, xthr.size): for j in range(1, ythr.size): idx = ( (x[0, :] >= xthr[i - 1]) & (x[0, :] < xthr[i]) & (x[1, :] >= ythr[j - 1]) & (x[1, :] < ythr[j]) ) k[i - 1, j - 1] = sum(idx) if sum(k) == 0: return k = k / sum(k) k = convolve2d(k, fspecial_gauss(3, 1), "same") k = k / sum(k) return k def randomTrajectory(T): x = zeros((3, T)) v = randn(3, T) r = zeros((3, T)) trv = 1 / 1 trr = 2 * pi / T for t in range(1, T): F_rot = randn(3) / (t + 1) + r[:, t - 1] F_trans = randn(3) / (t + 1) r[:, t] = r[:, t - 1] + trr * F_rot v[:, t] = v[:, t - 1] + trv * F_trans st = v[:, t] st = rot3D(st, r[:, t]) x[:, t] = x[:, t - 1] + st return x def rot3D(x, r): Rx = array([[1, 0, 0], [0, cos(r[0]), -sin(r[0])], [0, sin(r[0]), cos(r[0])]]) Ry = array([[cos(r[1]), 0, sin(r[1])], [0, 1, 0], [-sin(r[1]), 0, cos(r[1])]]) Rz = array([[cos(r[2]), -sin(r[2]), 0], [sin(r[2]), cos(r[2]), 0], [0, 0, 1]]) R = Rz @ Ry @ Rx x = R @ x return x if __name__ == '__main__': a = opt_fft_size([111]) print(a) print(fspecial('gaussian', 5, 1)) print(p2o(torch.zeros(1,1,4,4).float(),(14,14)).shape) k = blurkernel_synthesis(11) import matplotlib.pyplot as plt plt.imshow(k, interpolation="nearest", cmap="gray") plt.show()