Commit a296b8336c3250b59c0d8847ff948276ef158b8c
1 parent
9127f47e
added the muve alignment Python script
Showing
2 changed files
with
161 additions
and
324 deletions
Show diff stats
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]) |
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 | ... | ... |