alignfaces 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,316 @@
1
+ import numpy as np
2
+ from alignfaces.warp_tools import bilin
3
+ from alignfaces.warp_tools import pawarp
4
+ from numpy.fft import fft2, ifft2
5
+
6
+ #################################################################
7
+ # HELPER FUNCTIONS
8
+ #################################################################
9
+ def _make_pixarr(limits):
10
+ xmn, ymn = limits[0], limits[0]
11
+ xmx, ymx = limits[1], limits[1]
12
+ x = np.arange(xmn, xmx + 1)
13
+ y = np.arange(ymn, ymx + 1)
14
+ xx, yy = np.meshgrid(x, y)
15
+ pixarr = (xx.flatten(), yy.flatten())
16
+ pixarr = np.transpose(np.array(pixarr))
17
+ return pixarr
18
+
19
+
20
+ def _random_locations(im_size, num_loc=4, min_dist=5):
21
+ """create array of num_loc points within min distance of min_dist pixels"""
22
+ PAD = int(im_size * .15)
23
+ locations = (np.random.rand(1, 2) * (im_size-1-PAD*2)+PAD).astype(int)
24
+ total = 1
25
+ while total < num_loc:
26
+ # Sample new point.
27
+ loc = (np.random.rand(1, 2) * (im_size-1-PAD*2)+PAD).astype(int)
28
+
29
+ SAMPLE_NEW = 0;
30
+ for SETLOC in locations:
31
+ if np.sqrt(((SETLOC-loc)**2).sum()) <= min_dist:
32
+ # Bad sample. Skip to beginning of loop for new sample.
33
+ SAMPLE_NEW = 1;
34
+ if SAMPLE_NEW:
35
+ continue
36
+
37
+ # Good sample. Append to results.
38
+ locations = np.r_[locations, loc]
39
+ total += 1
40
+ IND = _locations_to_indices(im_size, locations)
41
+ locations = _indices_to_locations(im_size, IND)
42
+ return locations, IND
43
+
44
+
45
+ def _perturb_locations(locations, max_value, change_range):
46
+ num_targets = locations.shape[0]
47
+ noise = np.random.rand(num_targets, 2)
48
+ other_locations = ((noise-.5) * change_range + locations).astype(int)
49
+ other_locations[other_locations < 0] = 0
50
+ other_locations[other_locations > max_value] = max_value
51
+ return other_locations
52
+
53
+
54
+ def _image_with_donut_targets(im_size=64, locations=((8, 8),(32, 32))):
55
+ """make zero image with targets at locations. also return target"""
56
+ # Target
57
+ B = np.zeros((3, 3))
58
+
59
+ B[1, 1] = 9 * 8
60
+ B[0, 1] = -9
61
+ B[2, 1] = -9
62
+ B[1, 0] = -9
63
+ B[1, 2] = -9
64
+
65
+ B[0, 0] = -9
66
+ B[0, 2] = -9
67
+ B[2, 0] = -9
68
+ B[2, 2] = -9
69
+
70
+ # Image with targets
71
+ A = np.zeros((im_size, im_size))
72
+ for l in locations:
73
+ tr, tc = l
74
+ A[1 + tr - 1, 1 + tc - 1] = B[1, 1]
75
+ A[0 + tr - 1, 1 + tc - 1] = B[0, 1]
76
+ A[2 + tr - 1, 1 + tc - 1] = B[2, 1]
77
+ A[1 + tr - 1, 0 + tc - 1] = B[1, 0]
78
+ A[1 + tr - 1, 2 + tc - 1] = B[1, 2]
79
+
80
+ A[0 + tr - 1, 0 + tc - 1] = B[0, 0]
81
+ A[0 + tr - 1, 2 + tc - 1] = B[0, 2]
82
+ A[2 + tr - 1, 0 + tc - 1] = B[2, 0]
83
+ A[2 + tr - 1, 2 + tc - 1] = B[2, 2]
84
+
85
+ img, target = A, B
86
+ return img, target
87
+
88
+
89
+ def _fft_convolve2d(x, y):
90
+ """ 2D convolution, using FFT"""
91
+ pad = np.array(x.shape) - np.array(y.shape)
92
+ if pad[0] % 2 == 0:
93
+ rb, ra = int(pad[0]/2)+1, int(pad[0]/2)-1
94
+ else:
95
+ rb, ra = int(np.ceil(pad[0]/2)), int(np.floor(pad[0]/2))
96
+ if pad[1] % 2 == 0:
97
+ cb, ca = int(pad[1]/2)+1, int(pad[1]/2)-1
98
+ else:
99
+ cb, ca = int(np.ceil(pad[1]/2)), int(np.floor(pad[1]/2))
100
+ pad_width = ((rb, ra), (cb, ca))
101
+ py = np.pad(y, pad_width, mode="constant")
102
+
103
+ fr = fft2(x)
104
+ fr2 = fft2(np.flipud(np.fliplr(py)))
105
+ m,n = fr.shape
106
+ cc = np.real(ifft2(fr*fr2))
107
+ cc = np.roll(cc, int(-m/2+1), axis=0)
108
+ cc = np.roll(cc, int(-n/2+1), axis=1)
109
+ return cc
110
+
111
+
112
+ def _locations_to_indices(img_width, locations):
113
+ num_targets = locations.shape[0]
114
+ IND = np.zeros(num_targets, )
115
+ for i, rc in enumerate(locations):
116
+ r, c = rc
117
+ this_ind = r * img_width + c
118
+ IND[i] = this_ind
119
+ IND.sort()
120
+ return IND.astype(int)
121
+
122
+
123
+ def _indices_to_locations(img_width, IND):
124
+ IND.sort()
125
+ num_targets = IND.size
126
+ SUB = np.zeros((num_targets, 2), dtype=int)
127
+ for i, ind in enumerate(IND):
128
+ r = int(np.floor(ind / img_width))
129
+ c = int(ind % img_width)
130
+ SUB[i, :] = [r, c]
131
+ return SUB
132
+
133
+
134
+ def _top_n_locations(C, num_targets):
135
+ cv = np.copy(C).flatten()
136
+ si = cv.argsort()
137
+ IND = si[-num_targets:]
138
+ IND.sort()
139
+ return IND
140
+
141
+
142
+ def _top_n_locations_robust(C, num_targets):
143
+ CC = np.copy(C)
144
+ SUB = np.zeros((num_targets, 2), dtype=int)
145
+ for i in range(num_targets):
146
+ MC = np.where(CC==CC.max())
147
+ r, c = MC[0][0], MC[1][0]
148
+ SUB[i, :] = [r, c]
149
+ # set 5 x 5 area centered on (r, c) to 0
150
+ CC[r-1, c-1] = 0
151
+ CC[r-1, c+0] = 0
152
+ CC[r-1, c+1] = 0
153
+ CC[r+0, c-1] = 0
154
+ CC[r+0, c+0] = 0
155
+ CC[r+0, c+1] = 0
156
+ CC[r+1, c-1] = 0
157
+ CC[r+1, c+0] = 0
158
+ CC[r+1, c+1] = 0
159
+
160
+ CC[r-2, c-2] = 0
161
+ CC[r-2, c-1] = 0
162
+ CC[r-2, c-0] = 0
163
+ CC[r-2, c+1] = 0
164
+ CC[r-2, c+2] = 0
165
+
166
+ CC[r-1, c-2] = 0
167
+ CC[r-1, c+2] = 0
168
+
169
+ CC[r-0, c-2] = 0
170
+ CC[r-0, c+2] = 0
171
+
172
+ CC[r+1, c-2] = 0
173
+ CC[r+1, c+2] = 0
174
+
175
+ CC[r+2, c-2] = 0
176
+ CC[r+2, c-1] = 0
177
+ CC[r+2, c-0] = 0
178
+ CC[r+2, c+1] = 0
179
+ CC[r+2, c+2] = 0
180
+ IND = _locations_to_indices(CC.shape[1], SUB)
181
+ SUB = _indices_to_locations(CC.shape[1], IND)
182
+ return SUB, IND
183
+
184
+
185
+ def _distances_of_best_matched_points(locations, top_locations):
186
+ # every location matched with every estimated location
187
+ num_targets = locations.shape[0]
188
+ O = locations
189
+ N = top_locations
190
+ DELTA = np.kron(O, np.ones((num_targets, 1))) - np.tile(N, (num_targets, 1))
191
+ DIST = np.sqrt((DELTA**2).sum(axis=1))
192
+ D = DIST.reshape((num_targets, num_targets))
193
+
194
+ # distances between points and estimated points giving best-match
195
+ MIN_D = np.zeros((num_targets,))
196
+ for di in range(num_targets):
197
+ MIN_D[di] = D.min();
198
+ locs = np.where(D==D.min())
199
+ R, C = locs[0][0], locs[1][0]
200
+ D[R,:] = np.inf
201
+ D[:,C] = np.inf
202
+ return MIN_D
203
+
204
+
205
+ #################################################################
206
+ # DEFINE FUNCTIONS USED FOR UNIT TESTING
207
+ #################################################################
208
+ def test_bilin_with_integer_coordinates():
209
+ """Simple test where warped image should be same as original."""
210
+ im = np.array([[1, 2], [3, 4]])
211
+ xy = (np.array([0, 1, 0, 1]), np.array([0, 0, 1, 1]))
212
+ nRGB = 1
213
+ RGBsub = np.array([0, 0, 0, 0])
214
+ out = bilin(im, xy, nRGB, RGBsub)
215
+ expected_output = im.flatten()
216
+ assert np.allclose(out, expected_output)
217
+
218
+
219
+ def test_bilin_with_integer_coordinates_rgb():
220
+ """Simple test where warped RGB image should be same as original."""
221
+ im = np.array([[1, 2], [3, 4]])
222
+ xy = (np.array([0, 1, 0, 1]), np.array([0, 0, 1, 1]))
223
+ nRGB = 3
224
+ RGB = np.stack((im, ) * 3, axis=-1)
225
+ RGBsub = np.array([0, 0, 0, 0])
226
+ for i in range(1, nRGB):
227
+ append_this = np.ones((4,)) * i
228
+ RGBsub = np.hstack((RGBsub, append_this))
229
+ RGBsub = RGBsub.astype(int)
230
+ out = bilin(RGB, xy, nRGB, RGBsub)
231
+
232
+ pixarr = _make_pixarr([0, 1])
233
+ pixarr_tiled = np.tile(pixarr, (3, 1))
234
+ expected_output = RGB[pixarr_tiled[:, 1], pixarr_tiled[:, 0], RGBsub]
235
+ assert np.allclose(out, expected_output)
236
+
237
+
238
+ def test_bilin_with_real_valued_coordinates():
239
+ """Expected warped image is easy to calculate by hand."""
240
+ im = np.array([[28, 24], [16, 28]])
241
+ xy = ([0.75], [0.60])
242
+ nRGB = 1
243
+ RGBsub = [0]
244
+ out = bilin(im, xy, nRGB, RGBsub)
245
+
246
+ p = xy[0][0]
247
+ linear1 = (1 - p) * im[0, 0] + p * im[0, 1]
248
+ linear2 = (1 - p) * im[1, 0] + p * im[1, 1]
249
+ p = xy[1][0]
250
+ expected_output = (1 - p) * linear1 + p * linear2
251
+ assert np.allclose(out[0], expected_output)
252
+
253
+
254
+ def test_pawarp():
255
+ # Parameters - input image with targets centered on landmarks (locations)
256
+ im_size = 128 # length of square image, pixels
257
+ num_targets = 3 # number of targets
258
+ min_target_dist = 32 # minimum distance between landmarks, pixels
259
+
260
+ # Parameters - warped version of input image
261
+ change_range = 8 # range of random shift in landmark position, pixels
262
+ max_value=im_size-1 # maximum x and y position. minimum always 0.
263
+
264
+ # Parameters - test
265
+ iterations = 1000
266
+ ALL_D = np.zeros((iterations, num_targets))
267
+
268
+ # Perform multiple tests.
269
+ for this_it in range(iterations):
270
+ # Create image with cross-shaped targets
271
+ locations, target_indices = _random_locations(im_size, num_targets, min_target_dist)
272
+ img, target = _image_with_donut_targets(im_size, locations)
273
+
274
+ # # Ensure that all helper functions are working properly.
275
+ # # Should be able to find exact target positions using convolution.
276
+ # C = fft_convolve2d(img, target)
277
+ # top_indices = top_n_locations(C, num_targets)
278
+ # working_functions = (top_indices==target_indices).all()
279
+ # assert working_functions, "Helper functions do not work: Cannot interpret test."
280
+
281
+ # Warp centers of targets to slightly different locations
282
+ new_locations = _perturb_locations(locations, max_value, change_range)
283
+ wimg, tri, inpix, fwdwarpix = pawarp(img, base=new_locations, target=locations, interp='bilin')
284
+
285
+ # Recover the original image by warping back
286
+ wwimg, tri, inpix, fwdwarpix = pawarp(wimg, base=locations, target=new_locations, interp='bilin')
287
+
288
+ # Look for the targets in the recovered image
289
+ C = _fft_convolve2d(wwimg, target)
290
+ estimated_locations, top_indices = _top_n_locations_robust(C, num_targets)
291
+
292
+ # plt_1 = plt.figure(figsize=(10,10))
293
+ # plt.imshow(wwimg, cmap="gray")
294
+ # plt.plot(locations[:,1], locations[:,0], 'g.', markersize=24, alpha=0.5)
295
+ # plt.plot(estimated_locations[:,1], estimated_locations[:,0], 'r+', markersize=24, alpha=0.5)
296
+ # plt.show()
297
+
298
+ best_distances = _distances_of_best_matched_points(locations, estimated_locations)
299
+ # print("Distances of best-matched points: ", best_distances)
300
+ # print(this_it)
301
+
302
+ ALL_D[this_it, :] = best_distances
303
+
304
+ # Evaluate
305
+ ALL_D = ALL_D.flatten()
306
+ Q95_less_than_1_pixel = np.quantile(ALL_D, 0.95) < 1
307
+ max_offset_less_than_perturbation = ALL_D.max() < change_range
308
+ PASS = Q95_less_than_1_pixel and max_offset_less_than_perturbation
309
+ assert PASS
310
+ #################################################################
311
+ # UNIT TESTS
312
+ #################################################################
313
+ test_bilin_with_integer_coordinates()
314
+ test_bilin_with_integer_coordinates_rgb()
315
+ test_bilin_with_real_valued_coordinates()
316
+ test_pawarp()
@@ -0,0 +1,279 @@
1
+ import numpy as np
2
+ from scipy.spatial import Delaunay
3
+
4
+
5
+ # bilinear interpolation
6
+ #
7
+ # input:
8
+ # array image, numpy array.
9
+ # xy image coordinates, positive real valued, tuple of arrays.
10
+ # (length is number of pixels in array)
11
+ # nRGB number of color channels (third dimension of array).
12
+ # RGBsub channel index for each pixel in array, numpy array
13
+ # (length is number of pixels in array)
14
+ def bilin(array, xy, nRGB, RGBsub):
15
+
16
+ assert (array.ndim == 2) or (array.ndim == 3)
17
+ if (array.ndim == 3):
18
+ assert (array.shape[2] == nRGB)
19
+ assert (len(RGBsub) == len(xy[0]) * nRGB)
20
+
21
+ # Zero padding.
22
+ # Handles solution to bad cases where subscripts pushed beyond image.
23
+ if nRGB == 1:
24
+ array = np.hstack((array, np.zeros((array.shape[0], 1))))
25
+ array = np.vstack((array, np.zeros((1, array.shape[1]))))
26
+ else:
27
+ #
28
+ # above works for single-channel grayscale.
29
+ # now for RGB
30
+ # print("In bilin:")
31
+ # print(array.shape)
32
+ # print(np.zeros((nRGB, array.shape[0], 1)).shape)
33
+ # array = np.concatenate((array,
34
+ # np.zeros((nRGB, array.shape[0], 1))), axis=2)
35
+ # array = np.concatenate((array,
36
+ # np.zeros((nRGB, 1, array.shape[1]))), axis=1)
37
+ array = np.concatenate((array,
38
+ np.zeros((array.shape[0], 1, nRGB))), axis=1)
39
+ array = np.concatenate((array,
40
+ np.zeros((1, array.shape[1], nRGB))), axis=0)
41
+
42
+ xy = (np.tile(xy[0], (nRGB, 1)).flatten(),
43
+ np.tile(xy[1], (nRGB, 1)).flatten())
44
+
45
+ ur = (np.ceil(xy[0]).astype(int), np.floor(xy[1]).astype(int))
46
+ ul = (np.floor(xy[0]).astype(int), np.floor(xy[1]).astype(int))
47
+ br = (np.ceil(xy[0]).astype(int), np.ceil(xy[1]).astype(int))
48
+ bl = (np.floor(xy[0]).astype(int), np.ceil(xy[1]).astype(int))
49
+
50
+ bad = br[0] == ul[0]
51
+ ur[0][bad] += 1
52
+ br[0][bad] += 1
53
+
54
+ bad = bl[1] == ul[1]
55
+ br[1][bad] += 1
56
+ bl[1][bad] += 1
57
+
58
+ indbl = np.ravel_multi_index((bl[1], bl[0], RGBsub),
59
+ (array.shape[0], array.shape[1], nRGB))
60
+ indbr = np.ravel_multi_index((br[1], br[0], RGBsub),
61
+ (array.shape[0], array.shape[1], nRGB))
62
+ indul = np.ravel_multi_index((ul[1], ul[0], RGBsub),
63
+ (array.shape[0], array.shape[1], nRGB))
64
+ indur = np.ravel_multi_index((ur[1], ur[0], RGBsub),
65
+ (array.shape[0], array.shape[1], nRGB))
66
+
67
+ vecarray = array.flatten()
68
+
69
+ denom = br[0] - bl[0]
70
+ num_a = br[0] - xy[0]
71
+ num_b = xy[0] - bl[0]
72
+ x1out = ((num_a / denom) * vecarray[indbl] +
73
+ (num_b / denom) * vecarray[indbr])
74
+
75
+ x2out = ((num_a / denom) * vecarray[indul] +
76
+ (num_b / denom) * vecarray[indur])
77
+
78
+ denom = ul[1] - bl[1]
79
+ num_a = ul[1] - xy[1]
80
+ num_b = xy[1] - bl[1]
81
+ out = (num_a / denom) * x1out + (num_b / denom) * x2out
82
+ return out
83
+
84
+
85
+ # piecewise affine warp of target image ''im'' from target to base coords.
86
+ #
87
+ # input:
88
+ # im image, numpy array.
89
+ # base landmarks, rows are [x,y] points, numpy array.
90
+ # target landmarks, rows are [x,y] points, numpy array.
91
+ def pawarp(im, base, target, interp='bilin'):
92
+ # base & target should be numpy arrays, vertices X coordinate [x/y]
93
+ isuint8 = False
94
+ if (im.dtype == 'uint8'):
95
+ isuint8 = True
96
+ assert (im.ndim == 2) or (im.ndim == 3)
97
+ if (im.ndim == 3):
98
+ nRGB = im.shape[2]
99
+ else:
100
+ nRGB = 1
101
+ imdims = (im.shape[0], im.shape[1])
102
+ warpim = np.zeros((im.shape[0], im.shape[1], nRGB))
103
+
104
+ nverts = base.shape[0]
105
+ nverts2 = target.shape[0]
106
+ assert (nverts == nverts2)
107
+
108
+ boxpix = np.array([[1, 1], [1, imdims[0]],
109
+ [imdims[1], 1], [imdims[1], imdims[0]]])
110
+ boxpix -= 1
111
+
112
+ pix1 = base.astype(float)
113
+ pix2 = target.astype(float)
114
+
115
+ pix1 = np.vstack((pix1, boxpix))
116
+ pix2 = np.vstack((pix2, boxpix))
117
+
118
+ # Perform Delaunay triangulation on pixel coordinates of base vertices.
119
+ dt = Delaunay(pix1)
120
+ tri = dt.simplices
121
+ ntri = tri.shape[0]
122
+
123
+ # Get the first, second, and third vertex for each triangle (x--coords).
124
+ xio = pix1[tri[:, 0], 0]
125
+ xi = pix2[tri[:, 0], 0]
126
+ xjo = pix1[tri[:, 1], 0]
127
+ xj = pix2[tri[:, 1], 0]
128
+ xko = pix1[tri[:, 2], 0]
129
+ xk = pix2[tri[:, 2], 0]
130
+
131
+ # Get the first, second, and third vertex for each triangle (y--coords).
132
+ yio = pix1[tri[:, 0], 1]
133
+ yi = pix2[tri[:, 0], 1]
134
+ yjo = pix1[tri[:, 1], 1]
135
+ yj = pix2[tri[:, 1], 1]
136
+ yko = pix1[tri[:, 2], 1]
137
+ yk = pix2[tri[:, 2], 1]
138
+
139
+ # Array for warp parameters (one set of params per triangle).
140
+ # a_i for i in 1 to 6, in equation 28 of Matthews & Baker on page 145.
141
+ wparams = np.zeros((ntri, 6))
142
+
143
+ # Calculate warp parameters for each triangle.
144
+ denom = (xjo - xio) * (yko - yio) - (yjo - yio) * (xko - xio)
145
+
146
+ wparams[:, 0] = ((xio * ((xk - xi) * (yjo - yio) - (xj - xi) * (yko - yio)) +
147
+ yio * ((xj - xi) * (xko - xio) - (xk - xi) * (xjo - xio))) /
148
+ denom + xi)
149
+
150
+ wparams[:, 3] = ((xio * ((yk - yi) * (yjo - yio) - (yj - yi) * (yko - yio)) +
151
+ yio * ((yj - yi) * (xko - xio) - (yk - yi) * (xjo - xio))) /
152
+ denom + yi)
153
+
154
+ wparams[:, 1] = ((xj - xi) * (yko - yio) - (xk - xi) * (yjo - yio)) / denom
155
+
156
+ wparams[:, 4] = ((yj - yi) * (yko - yio) - (yk - yi) * (yjo - yio)) / denom
157
+
158
+ wparams[:, 2] = ((xk - xi) * (xjo - xio) - (xj - xi) * (xko - xio)) / denom
159
+
160
+ wparams[:, 5] = ((yk - yi) * (xjo - xio) - (yj - yi) * (xko - xio)) / denom
161
+
162
+ # Determine square bounds of pixels inside base mesh.
163
+ xmx = int(min(np.ceil(pix1[:, 0].max()), imdims[1]))
164
+ xmn = int(max(np.floor(pix1[:, 0].min()), 0))
165
+ ymx = int(min(np.ceil(pix1[:, 1].max()), imdims[0]))
166
+ ymn = int(max(np.floor(pix1[:, 1].min()), 0))
167
+
168
+ # Array for pixel coordinates inside base mesh.
169
+ npix = im[ymn:ymx + 1, xmn:xmx + 1].size
170
+ pixarr = np.zeros((npix, 2))
171
+
172
+ x = np.arange(xmn, xmx + 1)
173
+ y = np.arange(ymn, ymx + 1)
174
+ xx, yy = np.meshgrid(x, y)
175
+ pixarr = (xx.flatten(), yy.flatten())
176
+ # pixind = np.ravel_multi_index((pixarr[1], pixarr[0]), imdims)
177
+
178
+ pixarr = np.transpose(np.array(pixarr))
179
+ inpix = dt.find_simplex(pixarr)
180
+
181
+ # Get only those pixels that are inside the convex hull.
182
+ isin = np.argwhere(inpix >= 0)[:, 0]
183
+
184
+ # Warp parameters for each pixel inside convex hull.
185
+ wp = wparams[inpix[isin], :]
186
+
187
+ fwdx = wp[:, 0] + wp[:, 1] * pixarr[isin, 0] + wp[:, 2] * pixarr[isin, 1]
188
+ fwdy = wp[:, 3] + wp[:, 4] * pixarr[isin, 0] + wp[:, 5] * pixarr[isin, 1]
189
+ if interp == 'nearest':
190
+ fwdx = fwdx.round().astype(int)
191
+ fwdy = fwdy.round().astype(int)
192
+
193
+ fwdwarpix = np.transpose(np.vstack((fwdx, fwdy)))
194
+
195
+ fwdwarpix[fwdwarpix[:, 0] < 1, 0] = 1
196
+ fwdwarpix[fwdwarpix[:, 1] < 1, 1] = 1
197
+
198
+ fwdwarpix[np.isnan(fwdwarpix[:, 0]), 0] = 1
199
+ fwdwarpix[np.isnan(fwdwarpix[:, 1]), 1] = 1
200
+
201
+ fwdwarpix[fwdwarpix[:, 0] > imdims[1], 0] = imdims[1]
202
+ fwdwarpix[fwdwarpix[:, 1] > imdims[0], 1] = imdims[0]
203
+
204
+ RGBsub = np.empty((1, 1))
205
+ for RGB in range(nRGB):
206
+ this_channel = np.ones((fwdwarpix.shape[0], 1)) * RGB
207
+ RGBsub = np.vstack((RGBsub, this_channel))
208
+ RGBsub = np.delete(RGBsub, (0), axis=0)
209
+ RGBsub = RGBsub.astype(int).flatten()
210
+
211
+ pixx = np.tile(pixarr[isin, 0], (nRGB, 1)).flatten()
212
+ pixy = np.tile(pixarr[isin, 1], (nRGB, 1)).flatten()
213
+ # alldims = (imdims[0], imdims[1], nRGB)
214
+ # pixind = np.ravel_multi_index((pixy, pixx, RGBsub), alldims)
215
+ # print("pixarr should be tiled by channel regardless of method.")
216
+ # print("input fwdwarpix_tup grayscale for bilin.")
217
+ # print("fwdwarpix tiled for nearest neighbor.")
218
+ # print("pixx.shape, pixy.shape: \t")
219
+ # print(pixx.shape)
220
+ # print(pixy.shape)
221
+ # print("pixarr.shape: \t")
222
+ # print(pixarr.shape)
223
+
224
+ # Pixarr is now expanded by channel.
225
+ # Same as before in the case of grayscale image.
226
+ pixarr = np.vstack((pixx, pixy)).transpose()
227
+
228
+ if interp == 'nearest':
229
+ # fwdwarpind = sub2ind([imdims nRGB], repmat(fwdwarpix(:,2),[nRGB 1]),
230
+ # repmat(fwdwarpix(:,1),[nRGB 1]), RGBsub);
231
+ print("Nearest neighbor to do:")
232
+ print("In Matlab, we would derive fwdwarpind.")
233
+ print("In this case, expand pixarr & fwdwarpix across RGB channels.")
234
+ if nRGB > 1:
235
+ warpim = np.zeros((imdims[0], imdims[1], nRGB))
236
+ else:
237
+ warpim = np.zeros(imdims)
238
+ if interp == 'nearest':
239
+ print("fwdwarpix already reduced by isin!")
240
+ print("To do: deal with that!")
241
+ # expand fwdwarpix across channels
242
+ # pixx = np.tile(pixarr[isin, 0], (nRGB, 1)).flatten()
243
+ # pixy = np.tile(pixarr[isin, 1], (nRGB, 1)).flatten()
244
+ # pixarr = np.vstack((pixx, pixy)).transpose()
245
+ fwdx = np.tile(fwdwarpix[:, 0], (nRGB, 1)).flatten()
246
+ fwdy = np.tile(fwdwarpix[:, 1], (nRGB, 1)).flatten()
247
+ fwdwarpix = np.vstack((fwdx, fwdy)).transpose()
248
+
249
+ print("Indexing for 3rd dimension, if there is one!")
250
+ if nRGB > 1:
251
+ warpim[pixarr[:, 1], pixarr[:, 0], RGBsub] = im[fwdwarpix[:, 1],
252
+ fwdwarpix[:, 0],
253
+ RGBsub]
254
+ else:
255
+ warpim[pixarr[:, 1], pixarr[:, 0]] = im[fwdwarpix[:, 1],
256
+ fwdwarpix[:, 0]]
257
+ else:
258
+ # print("now, pixarr is already reduced by isin!")
259
+ # print("To do: deal with that!")
260
+ #
261
+ # print("RGBsub.shape")
262
+ # print(RGBsub.shape)
263
+
264
+ fwdwarpix_tup = (fwdwarpix[:, 0], fwdwarpix[:, 1])
265
+ out = bilin(im, fwdwarpix_tup, nRGB, RGBsub)
266
+
267
+ # print("Indexing for 3rd dimension, if there is one!")
268
+ if nRGB > 1:
269
+ warpim[pixarr[:, 1], pixarr[:, 0], RGBsub] = out
270
+ else:
271
+ warpim[pixarr[:, 1], pixarr[:, 0]] = out
272
+
273
+ if isuint8:
274
+ warpim = warpim.astype(np.uint8)
275
+ return warpim, tri, inpix, fwdwarpix
276
+ # -----------------------------------------------------------------------------
277
+
278
+
279
+ # print("All above - final warp code in /warp_tests/")