Authored by David Mayerich
1 parent 9127f47e

### added the muve alignment Python script

Showing 2 changed files with 161 additions and 324 deletions
python/coupledwave.py deleted
 1 -# -*- coding: utf-8 -*- 2 -""" 3 -Created on Thu Sep 6 22:14:36 2018 4 - 5 -@author: david 6 -""" 7 - 8 -import numpy as np 9 -import matplotlib.pyplot as plt 10 - 11 -#evaluate a plane wave for all points in R = [Rx, Ry, Rz] 12 -# E0 [Ex, Ey, Ez] is the electric field vector 13 -# k is the k vector 14 -# n is the refractive index of the material 15 -# R is a list of coordinates in x, y, and z 16 -def planewave2field(E0, k, n, R): 17 - knr = n * (k[0] * R[0] + k[1] * R[1] + k[2] + R[2]) 18 - exp_2pknr = np.exp(1j * 2 * np.pi * knr) 19 - Ex = E0[0] * exp_2pknr 20 - Ey = E0[1] * exp_2pknr 21 - Ez = E0[2] * exp_2pknr 22 - 23 - return [Ex, Ey, Ez] 24 - 25 -def orthogonalize(E, k): 26 - s = np.cross(E, k) 27 - return np.cross(k, s) 28 - 29 -class layersample: 30 - 31 - #generate a layered sample based on a list of layer refractive indices 32 - # layer_ri is a list of complex refractive indices for each layer 33 - # z is the list of boundary positions along z 34 - def __init__(self, layer_ri, z): 35 - self.n = np.array(layer_ri).astype(np.complex128) 36 - self.z = np.array(z) 37 - 38 - #calculate the index of the field component associated with a layer 39 - # l is the layer index [0, L) 40 - # c is the component (x=0, y=1, z=2) 41 - # d is the direction (0 = transmission, 1 = reflection) 42 - def idx(self, l, c, d): 43 - i = l * 6 + d * 3 + c - 3 44 - print(i) 45 - return i 46 - 47 - #create a matrix for a single plane wave specified by k and E 48 - def solve(self, k, E): 49 - 50 - #calculate the wavenumber 51 - kn = np.linalg.norm(k) 52 - 53 - #s is the plane wave direction scaled by the refractive index 54 - s = k / kn * self.n[0] 55 - 56 - #allocate space for the matrix 57 - L = len(self.n) 58 - M = np.zeros((6*(L-1), 6*(L-1)), dtype=np.complex128) 59 - 60 - #allocate space for the RHS vector 61 - b = np.zeros(6*(L-1), dtype=np.complex128) 62 - 63 - #initialize a counter for the equation number 64 - ei = 0 65 - 66 - #calculate the sz component for each layer 67 - self.sz = np.zeros(L, dtype=np.complex128) 68 - for l in range(L): 69 - self.sz[l] = np.sqrt(self.n[l]**2 - s[0]**2 - s[1]**2) 70 - 71 - #set constraints based on Gauss' law 72 - for l in range(0, L): 73 - #sz = np.sqrt(self.n[l]**2 - s[0]**2 - s[1]**2) 74 - 75 - #set the upward components for each layer 76 - # note that layer L-1 does not have a downward component 77 - # David I, Equation 7 78 - if l != L-1: 79 - M[ei, self.idx(l, 0, 1)] = s[0] 80 - M[ei, self.idx(l, 1, 1)] = s[1] 81 - M[ei, self.idx(l, 2, 1)] = -self.sz[l] 82 - ei = ei+1 83 - 84 - #set the downward components for each layer 85 - # note that layer 0 does not have a downward component 86 - # Davis I, Equation 6 87 - if l != 0: 88 - M[ei, self.idx(l, 0, 0)] = s[0] 89 - M[ei, self.idx(l, 1, 0)] = s[1] 90 - M[ei, self.idx(l, 2, 0)] = self.sz[l] 91 - ei = ei+1 92 - 93 - #enforce a continuous field across boundaries 94 - for l in range(1, L): 95 - sz0 = self.sz[l-1] 96 - sz1 = self.sz[l] 97 - A = np.exp(1j * kn * sz0 * (self.z[l] - self.z[l-1])) 98 - if l < L - 1: 99 - B = np.exp(-1j * kn * sz1 * (self.z[l] - self.z[l+1])) 100 - 101 - 102 - #if this is the second layer, use the simplified equations that account for the incident field 103 - if l == 1: 104 - M[ei, self.idx(0, 0, 1)] = 1 105 - M[ei, self.idx(1, 0, 0)] = -1 106 - if L > 2: 107 - M[ei, self.idx(1, 0, 1)] = -B 108 - 109 - b[ei] = -A * E[0] 110 - ei = ei + 1 111 - 112 - M[ei, self.idx(0, 1, 1)] = 1 113 - M[ei, self.idx(1, 1, 0)] = -1 114 - if L > 2: 115 - M[ei, self.idx(1, 1, 1)] = -B 116 - b[ei] = -A * E[1] 117 - ei = ei + 1 118 - 119 - M[ei, self.idx(0, 2, 1)] = s[1] 120 - M[ei, self.idx(0, 1, 1)] = sz0 121 - M[ei, self.idx(1, 2, 0)] = -s[1] 122 - M[ei, self.idx(1, 1, 0)] = sz1 123 - if L > 2: 124 - M[ei, self.idx(1, 2, 1)] = -B*s[1] 125 - M[ei, self.idx(1, 1, 1)] = -B*sz1 126 - b[ei] = A * sz0 * E[1] - A * s[1]*E[2] 127 - ei = ei + 1 128 - 129 - M[ei, self.idx(0, 0, 1)] = -sz0 130 - M[ei, self.idx(0, 2, 1)] = -s[0] 131 - M[ei, self.idx(1, 0, 0)] = -sz1 132 - M[ei, self.idx(1, 2, 0)] = s[0] 133 - if L > 2: 134 - M[ei, self.idx(1, 0, 1)] = B*sz1 135 - M[ei, self.idx(1, 2, 1)] = B*s[0] 136 - b[ei] = A * s[0] * E[2] - A * sz0 * E[0] 137 - ei = ei + 1 138 - 139 - #if this is the last layer, use the simplified equations that exclude reflections from the last layer 140 - elif l == L-1: 141 - M[ei, self.idx(l-1, 0, 0)] = A 142 - M[ei, self.idx(l-1, 0, 1)] = 1 143 - M[ei, self.idx(l, 0, 0)] = -1 144 - ei = ei + 1 145 - 146 - M[ei, self.idx(l-1, 1, 0)] = A 147 - M[ei, self.idx(l-1, 1, 1)] = 1 148 - M[ei, self.idx(l, 1, 0)] = -1 149 - ei = ei + 1 150 - 151 - M[ei, self.idx(l-1, 2, 0)] = A*s[1] 152 - M[ei, self.idx(l-1, 1, 0)] = -A*sz0 153 - M[ei, self.idx(l-1, 2, 1)] = s[1] 154 - M[ei, self.idx(l-1, 1, 1)] = sz0 155 - M[ei, self.idx(l, 2, 0)] = -s[1] 156 - M[ei, self.idx(l, 1, 0)] = sz1 157 - ei = ei + 1 158 - 159 - M[ei, self.idx(l-1, 0, 0)] = A*sz0 160 - M[ei, self.idx(l-1, 2, 0)] = -A*s[0] 161 - M[ei, self.idx(l-1, 0, 1)] = -sz0 162 - M[ei, self.idx(l-1, 2, 1)] = -s[0] 163 - M[ei, self.idx(l, 0, 0)] = -sz1 164 - M[ei, self.idx(l, 2, 0)] = s[0] 165 - ei = ei + 1 166 - #otherwise use the full set of boundary conditions 167 - else: 168 - M[ei, self.idx(l-1, 0, 0)] = A 169 - M[ei, self.idx(l-1, 0, 1)] = 1 170 - M[ei, self.idx(l, 0, 0)] = -1 171 - M[ei, self.idx(l, 0, 1)] = -B 172 - ei = ei + 1 173 - 174 - M[ei, self.idx(l-1, 1, 0)] = A 175 - M[ei, self.idx(l-1, 1, 1)] = 1 176 - M[ei, self.idx(l, 1, 0)] = -1 177 - M[ei, self.idx(l, 1, 1)] = -B 178 - ei = ei + 1 179 - 180 - M[ei, self.idx(l-1, 2, 0)] = A*s[1] 181 - M[ei, self.idx(l-1, 1, 0)] = -A*sz0 182 - M[ei, self.idx(l-1, 2, 1)] = s[1] 183 - M[ei, self.idx(l-1, 1, 1)] = sz0 184 - M[ei, self.idx(l, 2, 0)] = -s[1] 185 - M[ei, self.idx(l, 1, 0)] = sz1 186 - M[ei, self.idx(l, 2, 1)] = -B*s[1] 187 - M[ei, self.idx(l, 1, 1)] = -B*sz1 188 - ei = ei + 1 189 - 190 - M[ei, self.idx(l-1, 0, 0)] = A*sz0 191 - M[ei, self.idx(l-1, 2, 0)] = -A*s[0] 192 - M[ei, self.idx(l-1, 0, 1)] = -sz0 193 - M[ei, self.idx(l-1, 2, 1)] = -s[0] 194 - M[ei, self.idx(l, 0, 0)] = -sz1 195 - M[ei, self.idx(l, 2, 0)] = s[0] 196 - M[ei, self.idx(l, 0, 1)] = B*sz1 197 - M[ei, self.idx(l, 2, 1)] = B*s[0] 198 - ei = ei + 1 199 - 200 - #store the matrix and RHS vector (for debugging) 201 - self.M = M 202 - self.b = b 203 - 204 - #evaluate the linear system 205 - P = np.linalg.solve(M, b) 206 - 207 - #save the results (also for debugging) 208 - self.P = P 209 - 210 - #store the coefficients for each layer 211 - self.Ptrans = [] 212 - self.Prefl = [] 213 - for l in range(L): 214 - if l == 0: 215 - self.Ptrans.append((E[0], E[1], E[2])) 216 - else: 217 - px = P[self.idx(l, 0, 0)] 218 - py = P[self.idx(l, 1, 0)] 219 - pz = P[self.idx(l, 2, 0)] 220 - self.Ptrans.append((px, py, pz)) 221 - 222 - if l == L-1: 223 - self.Prefl.append((0, 0, 0)) 224 - else: 225 - px = P[self.idx(l, 0, 1)] 226 - py = P[self.idx(l, 1, 1)] 227 - pz = P[self.idx(l, 2, 1)] 228 - self.Prefl.append((px, py, pz)) 229 - 230 - #this converts the coefficients into a NumPy array for convenience later on 231 - self.Ptrans = np.array(self.Ptrans) 232 - self.Prefl = np.array(self.Prefl) 233 - 234 - #store values required for evaluation 235 - #store k 236 - self.k = kn 237 - 238 - #store sx and sy 239 - self.s = np.array([s[0], s[1]]) 240 - 241 - self.solved = True 242 - 243 - #evaluate a solved homogeneous substrate 244 - def evaluate(self, X, Y, Z): 245 - 246 - if not self.solved: 247 - print("ERROR: the layered substrate hasn't been solved") 248 - return 249 - 250 - #this code is a bit cumbersome and could probably be optimized 251 - # Basically, it vectorizes everything by creating an image 252 - # such that the value at each pixel is the corresponding layer 253 - # that the pixel resides in. That index is then used to calculate 254 - # the field within the layer 255 - 256 - #allocate space for layer indices 257 - LI = np.zeros(Z.shape, dtype=np.int) 258 - 259 - #find the layer index for each sample point 260 - L = len(self.z) 261 - LI[Z < self.z[0]] = 0 262 - for l in range(L-1): 263 - idx = np.logical_and(Z > self.z[l], Z <= self.z[l+1]) 264 - LI[idx] = l 265 - LI[Z > self.z[-1]] = L - 1 266 - 267 - #calculate the appropriate phase shift for the wave transmitted through the layer 268 - Ph_t = np.exp(1j * self.k * self.sz[LI] * (Z - self.z[LI])) 269 - 270 - #calculate the appropriate phase shift for the wave reflected off of the layer boundary 271 - LIp = LI + 1 272 - LIp[LIp >= L] = 0 273 - Ph_r = np.exp(-1j * self.k * self.sz[LI] * (Z - self.z[LIp])) 274 - Ph_r[LI >= L-1] = 0 275 - 276 - #calculate the phase shift based on the X and Y positions 277 - Ph_xy = np.exp(1j * self.k * (self.s[0] * X + self.s[1] * Y)) 278 - 279 - #apply the phase shifts 280 - Et = self.Ptrans[LI] * Ph_t[:, :, None] 281 - Er = self.Prefl[LI] * Ph_r[:, :, None] 282 - 283 - #add everything together coherently 284 - E = (Et + Er) * Ph_xy[:, :, None] 285 - 286 - #return the electric field 287 - return E 288 - 289 - 290 -#This sample code produces a field similar to Figure 2 in Davis et al., 2010 291 -#set the material properties 292 -depths = [-50, -15, 15, 40] 293 -n = [1.0, 1.4+1j*0.05, 1.4, 1.0] 294 - 295 -#create a layered sample 296 -layers = layersample(n, depths) 297 - 298 -#set the input light parameters 299 -d = np.array([0.2, 0, 1]) 300 -d = d / np.linalg.norm(d) 301 -l = 5.0 302 -k = 2 * np.pi / l * d 303 -E0 = [0, 1, 0] 304 -E0 = orthogonalize(E0, d) 305 - 306 -#solve for the substrate field 307 -layers.solve(k, E0) 308 - 309 -#set the simulation domain 310 -N = 512 311 -M = 1024 312 -D = [-50, 80] 313 - 314 -x = np.linspace(D[0], D[1], N) 315 -z = np.linspace(D[0], D[1], M) 316 -[X, Z] = np.meshgrid(x, z) 317 -Y = np.zeros(X.shape) 318 -E = layers.evaluate(X, Y, Z) 319 - 320 -Er = np.real(E) 321 -I = Er[..., 0] ** 2 + Er[..., 1] **2 + Er[..., 2] ** 2 322 -plt.figure() 323 -plt.set_cmap("afmhot") 324 -plt.imshow(Er[..., 1])
python/muve-align.py 0 โ 100644

 1 +# -*- coding: utf-8 -*- 2 +""" 3 +Created on Fri May 4 17:15:07 2018 4 + 5 +@author: david 6 +""" 7 + 8 +import imagestack 9 +import numpy 10 +import matplotlib.pyplot as plt 11 +import cv2 12 +import progressbar 13 +import scipy.ndimage 14 +import sys 15 + 16 + 17 +def press(event): 18 + global i 19 + global ax 20 + global fig 21 + if event.key == 'z': 22 + i = i - 1 23 + if i < 0: 24 + i = 0 25 + if event.key == 'x': 26 + i = i + 1 27 + if i == S.shape[0]: 28 + i = S.shape[0] 29 + if event.key == "up": 30 + for n in range(i, S.shape[0]): 31 + warps[n][1, 2] = warps[n][1, 2] + 1 32 + if event.key == "down": 33 + for n in range(i, S.shape[0]): 34 + warps[n][1, 2] = warps[n][1, 2] - 1 35 + if event.key == "left": 36 + for n in range(i, S.shape[0]): 37 + warps[n][0, 2] = warps[n][0, 2] + 1 38 + if event.key == "right": 39 + for n in range(i, S.shape[0]): 40 + warps[n][0, 2] = warps[n][0, 2] - 1 41 + aligned = cv2.warpAffine(S[i, :, :, :], warps[i], (S.shape[2], S.shape[1]), flags=cv2.INTER_LINEAR + cv2.WARP_INVERSE_MAP); 42 + ax.cla() 43 + ax.imshow(aligned.astype(numpy.uint8)) 44 + ax.set_title('Slice ' + str(i)) 45 + fig.canvas.draw() 46 + sys.stdout.flush 47 + 48 +#apply the alignment adjustments to I based on the list of warp matrices in t 49 +def apply_alignment(I, t): 50 + A = numpy.zeros(I.shape, dtype=numpy.uint8) 51 + for i in range(0, I.shape[0]): 52 + A[i, :, :, :] = cv2.warpAffine(I[i, :, :, :], t[i], (I.shape[2], I.shape[1]), flags=cv2.INTER_LINEAR + cv2.WARP_INVERSE_MAP); 53 + 54 + return A 55 + 56 +#get the transform that aligns image B to image A 57 +def align(A, B, max_power=5): 58 + # Define the motion model 59 + warp_mode = cv2.MOTION_TRANSLATION 60 + 61 + # Specify the number of iterations. 62 + number_of_iterations = 5000; 63 + 64 + # Specify the threshold of the increment 65 + # in the correlation coefficient between two iterations 66 + termination_eps = 1e-10; 67 + 68 + # Define termination criteria 69 + criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, number_of_iterations, termination_eps) 70 + 71 + #attempt to fit the image, increasing the blur as necessary 72 + i = 0 73 + while i <= max_power: 74 + #blur the image used for fitting 75 + im1_blur = scipy.ndimage.filters.gaussian_filter(A, (2 ** i, 2 ** i), mode='constant') 76 + im2_blur = scipy.ndimage.filters.gaussian_filter(B, (2 ** i, 2 ** i), mode='constant') 77 + 78 + # Run the ECC algorithm. The results are stored in warp_matrix. 79 + # Define 2x3 matrix and initialize the matrix to identity 80 + warp_matrix = numpy.eye(2, 3, dtype=numpy.float32) 81 + try: 82 + (cc, warp_matrix) = cv2.findTransformECC (im1_blur,im2_blur,warp_matrix, warp_mode, criteria) 83 + except: 84 + #print("Error aligning at p = " + str(i)) 85 + i = i + 1 86 + else: 87 + #print("Successful alignment at p = " + str(i)) 88 + break 89 + #enforce the fact that the x-axis is already aligned 90 + #warp_matrix[0, 2] = 0 91 + return warp_matrix 92 + 93 + #if i > 0: 94 + # (cc, warp_matrix) = cv2.findTransformECC (A,B,warp_matrix, warp_mode, criteria) 95 + #warp_matrix[0, 2] = 0 96 + # return warp_matrix 97 + 98 +if len(sys.argv) < 3: 99 + print("usage: muve-align input_mask output_directory") 100 + print("ex: muve-align *.bmp ./aligned") 101 + return 102 + 103 +fmask = sys.argv[1] 104 +out_dir = sys.argv[2] 105 + 106 +if not os.path.isdir(out_dir): 107 + os.mkdir(out_dir) 108 + 109 +#read the image stack for alignment 110 +print("Loading image stack...") 111 +S = imagestack.load(fmask, dtype=numpy.float32) 112 +#convert to grayscale 113 +G = imagestack.rgb2gray(S) 114 + 115 +# Define the motion model 116 +warp_mode = cv2.MOTION_TRANSLATION 117 + 118 +# Define the motion model 119 +warp_mode = cv2.MOTION_TRANSLATION 120 + 121 +# Specify the number of iterations. 122 +number_of_iterations = 5000 123 + 124 +# Specify the threshold of the increment 125 +# in the correlation coefficient between two iterations 126 +termination_eps = 1e-10 127 + 128 +# Define termination criteria 129 +criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, number_of_iterations, termination_eps) 130 + 131 +print("\n\nCalculating alignment transformations...") 132 +bar = progressbar.ProgressBar(max_value=G.shape[0]) 133 +#for each image in the stack, calculate a transformation matrix to align it to the previous image 134 +warps = [numpy.eye(2, 3, dtype=numpy.float32)] 135 +for i in range(1, G.shape[0]): 136 + warps.append(align(G[i-1, :, :], G[i, :, :])) 137 + bar.update(i+1) 138 + 139 + 140 +print("\n\nApplying alignment transformation...") 141 +# Define 2x3 matrix and initialize the matrix to identity 142 +for i in range(1, G.shape[0]): 143 + warps[i][0, 2] = warps[i][0, 2] + warps[i-1][0, 2] 144 + warps[i][1, 2] = warps[i][1, 2] + warps[i-1][1, 2] 145 + 146 + 147 + 148 +i = 0 149 +aligned = cv2.warpAffine(S[i, :, :, :], warps[i], (S.shape[2], S.shape[1]), flags=cv2.INTER_LINEAR + cv2.WARP_INVERSE_MAP) 150 + 151 +fig, ax = plt.subplots() 152 + 153 +fig.canvas.mpl_connect('key_press_event', press) 154 + 155 +ax.imshow(aligned.astype(numpy.uint8)) 156 +ax.set_title('Slice ' + str(i)) 157 +ax.set_xlabel("Press 'z' and 'x' to change slices and arrow keys to align") 158 +plt.show() 159 + 160 +I = apply_alignment(S, warps) 161 +imagestack.save(I, out_dir + "/aligned_", ".bmp") 0 162 \ No newline at end of file ... ...