├── perceptual ├── __init__.py ├── metric.py └── filterbank.py ├── images ├── 02-112.png └── 02-149.png ├── example_metric.py ├── README.rst └── example_Steerable.py /perceptual/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/02-112.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreydung/Steerable-filter/HEAD/images/02-112.png -------------------------------------------------------------------------------- /images/02-149.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreydung/Steerable-filter/HEAD/images/02-149.png -------------------------------------------------------------------------------- /example_metric.py: -------------------------------------------------------------------------------- 1 | from perceptual.metric import Metric 2 | import cv2 3 | 4 | im1 = cv2.imread('images/02-112.png', cv2.IMREAD_GRAYSCALE) 5 | im2 = cv2.imread('images/02-149.png', cv2.IMREAD_GRAYSCALE) 6 | 7 | m = Metric() 8 | 9 | print m.STSIM(im1, im2) 10 | print m.STSIM2(im1, im2) 11 | f = m.STSIM_M(im1) 12 | print f 13 | print f.shape 14 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Steerable pyramid and STSIM metrics 2 | ======================= 3 | 4 | Implement the complex steerable pyramid decomposition. And perceptual image metrics such as STSIM, STSTIM 2 and STSIM M 5 | 6 | Install: 7 | 8 | sudo pip install perceptual 9 | 10 | Usage: 11 | 12 | Use perceptual.filterbank.Steerable() and perceptual.metric.Metric classes -------------------------------------------------------------------------------- /example_Steerable.py: -------------------------------------------------------------------------------- 1 | from perceptual.filterbank import Steerable 2 | import cv2 3 | 4 | im = cv2.imread('images/02-112.png', cv2.IMREAD_GRAYSCALE) 5 | 6 | # Build a complex steerable pyramid 7 | # with height 5 (including lowpass and highpass) 8 | s = Steerable(5) 9 | coeff = s.buildSCFpyr(im) 10 | 11 | # coeff is an array and subbands can be accessed as follows: 12 | # coeff[0] : highpass 13 | # coeff[1][0], coeff[1][1], coeff[1][2], coeff[1][3] : bandpass of scale 1 14 | # coeff[2][0], coeff[2][1], coeff[2][2], coeff[2][3] : bandpass of scale 2 15 | # ... 16 | # coeff[4]: lowpass. It can also be accessed as coeff[-1] 17 | cv2.imwrite("subbband.png", coeff[1][0].real) 18 | 19 | # or visualization of whole decomposition 20 | # cv2.imwrite("coeff.png", visualize(coeff)) 21 | 22 | # reconstruction 23 | out = s.reconSCFpyr(coeff) 24 | cv2.imwrite("recon.png", out) 25 | -------------------------------------------------------------------------------- /perceptual/metric.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | import numpy as np 3 | from perceptual.filterbank import Steerable, SteerableNoSub 4 | import cv2 5 | from scipy import signal 6 | import itertools 7 | 8 | def MSE(img1, img2): 9 | return ((img2 - img1)**2).mean() 10 | 11 | def fspecial(win = 11, sigma = 1.5): 12 | """ 13 | 2D gaussian mask - should give the same result as MATLAB's 14 | fspecial('gaussian',[shape],[sigma]) 15 | """ 16 | shape = (win, win) 17 | m, n = [(ss-1.)/2. for ss in shape] 18 | y, x = np.ogrid[-m:m+1,-n:n+1] 19 | h = np.exp( -(x*x + y*y) / (2.*sigma*sigma) ) 20 | h[ h < np.finfo(h.dtype).eps*h.max() ] = 0 21 | sumh = h.sum() 22 | 23 | if sumh != 0: 24 | h /= sumh 25 | return h 26 | 27 | class Metric: 28 | 29 | def __init__(self): 30 | self.win = 7 31 | 32 | def SSIM(self, img1, img2, K = (0.01, 0.03), L = 255): 33 | 34 | img1 = img1.astype(float) 35 | img2 = img2.astype(float) 36 | 37 | C1 = (K[0]*L) ** 2 38 | C2 = (K[1]*L) ** 2 39 | 40 | window = fspecial() 41 | window /= window.sum() 42 | 43 | mu1 = self.conv( img1, window) 44 | mu2 = self.conv( img2, window) 45 | 46 | mu1_sq = mu1 * mu1 47 | mu2_sq = mu2 * mu2 48 | 49 | sigma1_sq = self.conv( img1*img1, window) - mu1_sq; 50 | sigma2_sq = self.conv( img2*img2, window) - mu2_sq; 51 | sigma12 = self.conv( img1*img2, window) - mu1*mu2; 52 | 53 | ssim_map = ((2 *mu1 *mu2 + C1)*(2*sigma12 + C2))/((mu1_sq + mu2_sq + C1)*(sigma1_sq + sigma2_sq + C2)) 54 | 55 | return ssim_map.mean() 56 | 57 | def conv(self, a, b): 58 | """ 59 | Larger matrix go first 60 | """ 61 | return signal.correlate2d(a, b, mode = 'valid') 62 | # return cv2.filter2D(a, -1, b, anchor = (0,0))\ 63 | # [:(a.shape[0]-b.shape[0]+1), :(a.shape[1]-b.shape[1]+1)] 64 | 65 | def STSIM(self, im1, im2): 66 | assert im1.shape == im2.shape 67 | s = Steerable() 68 | 69 | pyrA = s.getlist(s.buildSCFpyr(im1)) 70 | pyrB = s.getlist(s.buildSCFpyr(im2)) 71 | 72 | stsim = map(self.pooling, pyrA, pyrB) 73 | 74 | return np.mean(stsim) 75 | 76 | def STSIM2(self, im1, im2): 77 | assert im1.shape == im2.shape 78 | 79 | s = Steerable() 80 | s_nosub = SteerableNoSub() 81 | 82 | pyrA = s.getlist(s.buildSCFpyr(im1)) 83 | pyrB = s.getlist(s.buildSCFpyr(im2)) 84 | stsim2 = map(self.pooling, pyrA, pyrB) 85 | 86 | # Add cross terms 87 | bandsAn = s_nosub.buildSCFpyr(im1) 88 | bandsBn = s_nosub.buildSCFpyr(im2) 89 | 90 | Nor = len(bandsAn[1]) 91 | 92 | # Accross scale, same orientation 93 | for scale in range(2, len(bandsAn) - 1): 94 | for orient in range(Nor): 95 | im11 = np.abs(bandsAn[scale - 1][orient]) 96 | im12 = np.abs(bandsAn[scale][orient]) 97 | 98 | im21 = np.abs(bandsBn[scale - 1][orient]) 99 | im22 = np.abs(bandsBn[scale][orient]) 100 | 101 | stsim2.append(self.compute_cross_term(im11, im12, im21, im22).mean()) 102 | 103 | # Accross orientation, same scale 104 | for scale in range(1, len(bandsAn) - 1): 105 | for orient in range(Nor - 1): 106 | im11 = np.abs(bandsAn[scale][orient]) 107 | im21 = np.abs(bandsBn[scale][orient]) 108 | 109 | for orient2 in range(orient + 1, Nor): 110 | im13 = np.abs(bandsAn[scale][orient2]) 111 | im23 = np.abs(bandsBn[scale][orient2]) 112 | stsim2.append(self.compute_cross_term(im11, im13, im21, im23).mean()) 113 | 114 | return np.mean(stsim2) 115 | 116 | def STSIM_M(self, im): 117 | ss = Steerable(5) 118 | M, N = im.shape 119 | coeff = ss.buildSCFpyr(im) 120 | 121 | f = [] 122 | # single subband statistics 123 | for s in ss.getlist(coeff): 124 | s = s.real 125 | shiftx = np.roll(s,1, axis = 0) 126 | shifty = np.roll(s,1, axis = 1) 127 | 128 | f.append(np.mean(s)) 129 | f.append(np.var(s)) 130 | f.append((shiftx * s).mean()/s.var()) 131 | f.append((shifty * s).mean()/s.var()) 132 | 133 | # correlation statistics 134 | # across orientations 135 | for orients in coeff[1:-1]: 136 | for (s1, s2) in list(itertools.combinations(orients, 2)): 137 | f.append((s1.real*s2.real).mean()) 138 | 139 | for orient in range(len(coeff[1])): 140 | for height in range(len(coeff) - 3): 141 | s1 = coeff[height + 1][orient].real 142 | s2 = coeff[height + 2][orient].real 143 | 144 | s1 = cv2.resize(s1, (0,0), fx = 0.5, fy = 0.5) 145 | f.append((s1*s2).mean()/np.sqrt(s1.var())/np.sqrt(s2.var())) 146 | return np.array(f) 147 | 148 | def pooling(self, im1, im2): 149 | win = self.win 150 | tmp = np.power(self.compute_L_term(im1, im2) * self.compute_C_term(im1, im2) * \ 151 | self.compute_C01_term(im1, im2) * self.compute_C10_term(im1, im2), 0.25) 152 | 153 | return tmp.mean() 154 | 155 | def compute_L_term(self, im1, im2): 156 | win = self.win 157 | C = 0.001 158 | window = fspecial(win, win/6) 159 | mu1 = np.abs(self.conv(im1, window)) 160 | mu2 = np.abs(self.conv(im2, window)) 161 | 162 | Lmap = (2 * mu1 * mu2 + C)/(mu1*mu1 + mu2*mu2 + C) 163 | return Lmap 164 | 165 | def compute_C_term(self, im1, im2): 166 | win = self.win 167 | C = 0.001 168 | window = fspecial(win, win/6) 169 | mu1 = np.abs(self.conv(im1, window)) 170 | mu2 = np.abs(self.conv(im2, window)) 171 | 172 | sigma1_sq = self.conv(np.abs(im1*im1), window) - mu1 * mu1 173 | sigma1 = np.sqrt(sigma1_sq) 174 | sigma2_sq = self.conv(np.abs(im2*im2), window) - mu2 * mu2 175 | sigma2 = np.sqrt(sigma2_sq) 176 | 177 | Cmap = (2*sigma1*sigma2 + C)/(sigma1_sq + sigma2_sq + C) 178 | return Cmap 179 | 180 | def compute_C01_term(self, im1, im2): 181 | win = self.win 182 | C = 0.001; 183 | window2 = 1/(win*(win-1)) * np.ones((win,win-1)); 184 | 185 | im11 = im1[:, :-1] 186 | im12 = im1[:, 1:] 187 | im21 = im2[:, :-1] 188 | im22 = im2[:, 1:] 189 | 190 | mu11 = self.conv(im11, window2) 191 | mu12 = self.conv(im12, window2) 192 | mu21 = self.conv(im21, window2) 193 | mu22 = self.conv(im22, window2) 194 | 195 | sigma11_sq = self.conv(np.abs(im11*im11), window2) - np.abs(mu11*mu11) 196 | sigma12_sq = self.conv(np.abs(im12*im12), window2) - np.abs(mu12*mu12) 197 | sigma21_sq = self.conv(np.abs(im21*im21), window2) - np.abs(mu21*mu21) 198 | sigma22_sq = self.conv(np.abs(im22*im22), window2) - np.abs(mu22*mu22) 199 | 200 | sigma1_cross = self.conv(im11*np.conj(im12), window2) - mu11*np.conj(mu12) 201 | sigma2_cross = self.conv(im21*np.conj(im22), window2) - mu21*np.conj(mu22) 202 | 203 | rho1 = (sigma1_cross + C)/(np.sqrt(sigma11_sq)*np.sqrt(sigma12_sq) + C) 204 | rho2 = (sigma2_cross + C)/(np.sqrt(sigma21_sq)*np.sqrt(sigma22_sq) + C) 205 | C01map = 1 - 0.5*np.abs(rho1 - rho2) 206 | 207 | return C01map 208 | 209 | def compute_C10_term(self, im1, im2): 210 | win = self.win 211 | C = 0.001; 212 | window2 = 1/(win*(win-1)) * np.ones((win-1,win)); 213 | 214 | im11 = im1[:-1, :] 215 | im12 = im1[1:, :] 216 | im21 = im2[:-1, :] 217 | im22 = im2[1:, :] 218 | 219 | mu11 = self.conv(im11, window2) 220 | mu12 = self.conv(im12, window2) 221 | mu21 = self.conv(im21, window2) 222 | mu22 = self.conv(im22, window2) 223 | 224 | sigma11_sq = self.conv(np.abs(im11*im11), window2) - np.abs(mu11*mu11) 225 | sigma12_sq = self.conv(np.abs(im12*im12), window2) - np.abs(mu12*mu12) 226 | sigma21_sq = self.conv(np.abs(im21*im21), window2) - np.abs(mu21*mu21) 227 | sigma22_sq = self.conv(np.abs(im22*im22), window2) - np.abs(mu22*mu22) 228 | 229 | sigma1_cross = self.conv(im11*np.conj(im12), window2) - mu11*np.conj(mu12) 230 | sigma2_cross = self.conv(im21*np.conj(im22), window2) - mu21*np.conj(mu22) 231 | 232 | rho1 = (sigma1_cross + C)/(np.sqrt(sigma11_sq)*np.sqrt(sigma12_sq) + C) 233 | rho2 = (sigma2_cross + C)/(np.sqrt(sigma21_sq)*np.sqrt(sigma22_sq) + C) 234 | C10map = 1 - 0.5*np.abs(rho1 - rho2) 235 | 236 | return C10map 237 | 238 | def compute_cross_term(self, im11, im12, im21, im22): 239 | 240 | C = 0.001; 241 | window2 = 1/(self.win**2)*np.ones((self.win, self.win)); 242 | 243 | mu11 = self.conv(im11, window2); 244 | mu12 = self.conv(im12, window2); 245 | mu21 = self.conv(im21, window2); 246 | mu22 = self.conv(im22, window2); 247 | 248 | sigma11_sq = self.conv((im11*im11), window2) - (mu11*mu11); 249 | sigma12_sq = self.conv((im12*im12), window2) - (mu12*mu12); 250 | sigma21_sq = self.conv((im21*im21), window2) - (mu21*mu21); 251 | sigma22_sq = self.conv((im22*im22), window2) - (mu22*mu22); 252 | sigma1_cross = self.conv(im11*im12, window2) - mu11*(mu12); 253 | sigma2_cross = self.conv(im21*im22, window2) - mu21*(mu22); 254 | 255 | rho1 = (sigma1_cross + C)/(np.sqrt(sigma11_sq)*np.sqrt(sigma12_sq) + C); 256 | rho2 = (sigma2_cross + C)/(np.sqrt(sigma21_sq)*np.sqrt(sigma22_sq) + C); 257 | 258 | Crossmap = 1 - 0.5*abs(rho1 - rho2); 259 | return Crossmap 260 | -------------------------------------------------------------------------------- /perceptual/filterbank.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | import numpy as np 3 | import scipy.misc as sc 4 | import scipy.signal 5 | from scipy.special import factorial 6 | 7 | def visualize(coeff, normalize = True): 8 | M, N = coeff[1][0].shape 9 | Norients = len(coeff[1]) 10 | out = np.zeros((M * 2 - coeff[-1].shape[0], Norients * N)) 11 | 12 | currentx = 0 13 | currenty = 0 14 | for i in range(1, len(coeff[:-1])): 15 | for j in range(len(coeff[1])): 16 | tmp = coeff[i][j].real 17 | m,n = tmp.shape 18 | 19 | if normalize: 20 | tmp = 255 * tmp/tmp.max() 21 | 22 | tmp[m - 1, :] = 255 23 | tmp[:, n - 1] = 2555 24 | 25 | out[currentx : currentx + m, currenty : currenty + n] = tmp 26 | currenty += n 27 | currentx += coeff[i][0].shape[0] 28 | currenty = 0 29 | 30 | m,n = coeff[-1].shape 31 | out[currentx : currentx+m, currenty : currenty+n] = 255 * coeff[-1]/coeff[-1].max() 32 | 33 | out[0,:] = 255 34 | out[:, 0] = 255 35 | 36 | return out 37 | 38 | class Steerable: 39 | def __init__(self, height = 5): 40 | """ 41 | height is the total height, including highpass and lowpass 42 | """ 43 | self.nbands = 4 44 | self.height = height 45 | self.isSample = True 46 | 47 | def buildSCFpyr(self, im): 48 | assert len(im.shape) == 2, 'Input image must be grayscale' 49 | 50 | M, N = im.shape 51 | log_rad, angle = self.base(M, N) 52 | Xrcos, Yrcos = self.rcosFn(1, -0.5) 53 | Yrcos = np.sqrt(Yrcos) 54 | YIrcos = np.sqrt(1 - Yrcos*Yrcos) 55 | 56 | lo0mask = self.pointOp(log_rad, YIrcos, Xrcos) 57 | hi0mask = self.pointOp(log_rad, Yrcos, Xrcos) 58 | 59 | imdft = np.fft.fftshift(np.fft.fft2(im)) 60 | lo0dft = imdft * lo0mask 61 | 62 | coeff = self.buildSCFpyrlevs(lo0dft, log_rad, angle, Xrcos, Yrcos, self.height - 1) 63 | 64 | hi0dft = imdft * hi0mask 65 | hi0 = np.fft.ifft2(np.fft.ifftshift(hi0dft)) 66 | 67 | coeff.insert(0, hi0.real) 68 | 69 | return coeff 70 | 71 | def getlist(self, coeff): 72 | straight = [bands for scale in coeff[1:-1] for bands in scale] 73 | straight = [coeff[0]] + straight + [coeff[-1]] 74 | return straight 75 | 76 | def buildSCFpyrlevs(self, lodft, log_rad, angle, Xrcos, Yrcos, ht): 77 | if (ht <=1): 78 | lo0 = np.fft.ifft2(np.fft.ifftshift(lodft)) 79 | coeff = [lo0.real] 80 | 81 | else: 82 | Xrcos = Xrcos - 1 83 | 84 | # ==================== Orientation bandpass ======================= 85 | himask = self.pointOp(log_rad, Yrcos, Xrcos) 86 | 87 | lutsize = 1024 88 | Xcosn = np.pi * np.array(range(-(2*lutsize+1),(lutsize+2)))/lutsize 89 | order = self.nbands - 1 90 | const = np.power(2, 2*order) * np.square(factorial(order)) / (self.nbands * factorial(2*order)) 91 | 92 | alpha = (Xcosn + np.pi) % (2*np.pi) - np.pi 93 | Ycosn = 2*np.sqrt(const) * np.power(np.cos(Xcosn), order) * (np.abs(alpha) < np.pi/2) 94 | 95 | orients = [] 96 | 97 | for b in range(self.nbands): 98 | anglemask = self.pointOp(angle, Ycosn, Xcosn + np.pi*b/self.nbands) 99 | banddft = np.power(np.complex(0,-1), self.nbands - 1) * lodft * anglemask * himask 100 | band = np.fft.ifft2(np.fft.ifftshift(banddft)) 101 | orients.append(band) 102 | 103 | # ================== Subsample lowpass ============================ 104 | dims = np.array(lodft.shape) 105 | 106 | lostart = np.ceil((dims+0.5)/2) - np.ceil((np.ceil((dims-0.5)/2)+0.5)/2) 107 | loend = lostart + np.ceil((dims-0.5)/2) 108 | 109 | lostart = lostart.astype(int) 110 | loend = loend.astype(int) 111 | 112 | log_rad = log_rad[lostart[0]:loend[0], lostart[1]:loend[1]] 113 | angle = angle[lostart[0]:loend[0], lostart[1]:loend[1]] 114 | lodft = lodft[lostart[0]:loend[0], lostart[1]:loend[1]] 115 | YIrcos = np.abs(np.sqrt(1 - Yrcos*Yrcos)) 116 | lomask = self.pointOp(log_rad, YIrcos, Xrcos) 117 | 118 | lodft = lomask * lodft 119 | 120 | coeff = self.buildSCFpyrlevs(lodft, log_rad, angle, Xrcos, Yrcos, ht-1) 121 | coeff.insert(0, orients) 122 | 123 | return coeff 124 | 125 | def reconSCFpyrLevs(self, coeff, log_rad, Xrcos, Yrcos, angle): 126 | 127 | if (len(coeff) == 1): 128 | return np.fft.fftshift(np.fft.fft2(coeff[0])) 129 | 130 | else: 131 | 132 | Xrcos = Xrcos - 1 133 | 134 | # ========================== Orientation residue========================== 135 | himask = self.pointOp(log_rad, Yrcos, Xrcos) 136 | 137 | lutsize = 1024 138 | Xcosn = np.pi * np.array(range(-(2*lutsize+1),(lutsize+2)))/lutsize 139 | order = self.nbands - 1 140 | const = np.power(2, 2*order) * np.square(sc.factorial(order)) / (self.nbands * sc.factorial(2*order)) 141 | Ycosn = np.sqrt(const) * np.power(np.cos(Xcosn), order) 142 | 143 | orientdft = np.zeros(coeff[0][0].shape) 144 | 145 | for b in range(self.nbands): 146 | anglemask = self.pointOp(angle, Ycosn, Xcosn + np.pi* b/self.nbands) 147 | banddft = np.fft.fftshift(np.fft.fft2(coeff[0][b])) 148 | orientdft = orientdft + np.power(np.complex(0,1), order) * banddft * anglemask * himask 149 | 150 | # ============== Lowpass component are upsampled and convoluted ============ 151 | dims = np.array(coeff[0][0].shape) 152 | 153 | lostart = (np.ceil((dims+0.5)/2) - np.ceil((np.ceil((dims-0.5)/2)+0.5)/2)).astype(np.int32) 154 | loend = lostart + np.ceil((dims-0.5)/2).astype(np.int32) 155 | lostart = lostart.astype(int) 156 | loend = loend.astype(int) 157 | 158 | nlog_rad = log_rad[lostart[0]:loend[0], lostart[1]:loend[1]] 159 | nangle = angle[lostart[0]:loend[0], lostart[1]:loend[1]] 160 | YIrcos = np.sqrt(np.abs(1 - Yrcos * Yrcos)) 161 | lomask = self.pointOp(nlog_rad, YIrcos, Xrcos) 162 | 163 | nresdft = self.reconSCFpyrLevs(coeff[1:], nlog_rad, Xrcos, Yrcos, nangle) 164 | 165 | res = np.fft.fftshift(np.fft.fft2(nresdft)) 166 | 167 | resdft = np.zeros(dims, 'complex') 168 | resdft[lostart[0]:loend[0], lostart[1]:loend[1]] = nresdft * lomask 169 | 170 | return resdft + orientdft 171 | 172 | def reconSCFpyr(self, coeff): 173 | 174 | if (self.nbands != len(coeff[1])): 175 | raise Exception("Unmatched number of orientations") 176 | 177 | M, N = coeff[0].shape 178 | log_rad, angle = self.base(M, N) 179 | 180 | Xrcos, Yrcos = self.rcosFn(1, -0.5) 181 | Yrcos = np.sqrt(Yrcos) 182 | YIrcos = np.sqrt(np.abs(1 - Yrcos*Yrcos)) 183 | 184 | lo0mask = self.pointOp(log_rad, YIrcos, Xrcos) 185 | hi0mask = self.pointOp(log_rad, Yrcos, Xrcos) 186 | 187 | tempdft = self.reconSCFpyrLevs(coeff[1:], log_rad, Xrcos, Yrcos, angle) 188 | 189 | hidft = np.fft.fftshift(np.fft.fft2(coeff[0])) 190 | outdft = tempdft * lo0mask + hidft * hi0mask 191 | 192 | return np.fft.ifft2(np.fft.ifftshift(outdft)).real.astype(int) 193 | 194 | 195 | def base(self, m, n): 196 | 197 | x = np.linspace(-(m // 2)/(m / 2), (m // 2)/(m / 2) - (1 - m % 2)*2/m , num = m) 198 | y = np.linspace(-(n // 2)/(n / 2), (n // 2)/(n / 2) - (1 - n % 2)*2/n , num = n) 199 | 200 | xv, yv = np.meshgrid(y, x) 201 | 202 | angle = np.arctan2(yv, xv) 203 | 204 | rad = np.sqrt(xv**2 + yv**2) 205 | rad[m//2][n//2] = rad[m//2][n//2 - 1] 206 | log_rad = np.log2(rad) 207 | 208 | return log_rad, angle 209 | 210 | def rcosFn(self, width, position): 211 | N = 256 212 | X = np.pi * np.array(range(-N-1, 2))/2/N 213 | 214 | Y = np.cos(X)**2 215 | Y[0] = Y[1] 216 | Y[N+2] = Y[N+1] 217 | 218 | X = position + 2*width/np.pi*(X + np.pi/4) 219 | return X, Y 220 | 221 | def pointOp(self, im, Y, X): 222 | out = np.interp(im.flatten(), X, Y) 223 | return np.reshape(out, im.shape) 224 | 225 | class SteerableNoSub(Steerable): 226 | 227 | def buildSCFpyrlevs(self, lodft, log_rad, angle, Xrcos, Yrcos, ht): 228 | if (ht <=1): 229 | lo0 = np.fft.ifft2(np.fft.ifftshift(lodft)) 230 | coeff = [lo0.real] 231 | 232 | else: 233 | Xrcos = Xrcos - 1 234 | 235 | # ==================== Orientation bandpass ======================= 236 | himask = self.pointOp(log_rad, Yrcos, Xrcos) 237 | 238 | lutsize = 1024 239 | Xcosn = np.pi * np.array(range(-(2*lutsize+1),(lutsize+2)))/lutsize 240 | order = self.nbands - 1 241 | const = np.power(2, 2*order) * np.square(sc.factorial(order)) / (self.nbands * sc.factorial(2*order)) 242 | 243 | alpha = (Xcosn + np.pi) % (2*np.pi) - np.pi 244 | Ycosn = 2*np.sqrt(const) * np.power(np.cos(Xcosn), order) * (np.abs(alpha) < np.pi/2) 245 | 246 | orients = [] 247 | 248 | for b in range(self.nbands): 249 | anglemask = self.pointOp(angle, Ycosn, Xcosn + np.pi*b/self.nbands) 250 | banddft = np.power(np.complex(0,-1), self.nbands - 1) * lodft * anglemask * himask 251 | band = np.fft.ifft2(np.fft.ifftshift(banddft)) 252 | orients.append(band) 253 | 254 | # ================== Subsample lowpass ============================ 255 | lostart = (0, 0) 256 | loend = lodft.shape 257 | 258 | log_rad = log_rad[lostart[0]:loend[0], lostart[1]:loend[1]] 259 | angle = angle[lostart[0]:loend[0], lostart[1]:loend[1]] 260 | lodft = lodft[lostart[0]:loend[0], lostart[1]:loend[1]] 261 | YIrcos = np.abs(np.sqrt(1 - Yrcos*Yrcos)) 262 | lomask = self.pointOp(log_rad, YIrcos, Xrcos) 263 | 264 | lodft = lomask * lodft 265 | 266 | coeff = self.buildSCFpyrlevs(lodft, log_rad, angle, Xrcos, Yrcos, ht-1) 267 | coeff.insert(0, orients) 268 | 269 | return coeff 270 | --------------------------------------------------------------------------------