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,1209 @@
1
+ from .face_landmarks import (facial_landmarks, pull_jawline_to_inside_of_hairline,
2
+ unpack_dict_to_vector_xy, pack_vector_xy_as_dict)
3
+
4
+ from math import floor, ceil
5
+
6
+ # import matplotlib.pyplot as plt
7
+ import numpy as np
8
+ import math
9
+ import os.path
10
+
11
+ # image io and grayscale conversion
12
+ from PIL import Image as PilImage
13
+
14
+ # image translation and rotation
15
+ from skimage.transform import SimilarityTransform, warp
16
+
17
+ # image scaling
18
+ import dlib
19
+
20
+ # Procrustes analysis
21
+ # import procrustesTool as pt
22
+ # from .procrustes_tools import procrustes_tools as pt
23
+ from .procrustes_tools import (get_translation,
24
+ generalized_procrustes_analysis,
25
+ translate, get_rotation_scale,
26
+ procrustes_analysis)
27
+
28
+ # from .apertureTools import (fit_ellipse_semi_minor, make_ellipse_map,
29
+ # make_circle_map, make_map_below_y,
30
+ # make_moss_egg, make_four_channel_image)
31
+
32
+ from .aperture_tools import (fit_ellipse_semi_minor, make_ellipse_map,
33
+ make_moss_egg, make_four_channel_image)
34
+
35
+ from .warp_tools import pawarp
36
+
37
+ from .contrast_tools import contrast_stretch
38
+
39
+ from .plot_tools import (plot_faces_with_landmarks,
40
+ plot_faces_with_landmarks_one_by_one,
41
+ fit_ellipse_to_2D_cloud,
42
+ plot_ellipse)
43
+
44
+ # import pdb # TEMPORARY FOR DEBUGGING
45
+ # Final adjustments and saving image
46
+ from skimage import exposure
47
+ from skimage.io import imsave
48
+
49
+ # files
50
+ import pickle
51
+ import os
52
+ import json
53
+ import io
54
+ import csv
55
+
56
+ # testing
57
+ import sys
58
+
59
+ # additional functions
60
+ from .make_files import get_source_files, clone_directory_tree
61
+
62
+ # adjust_size = 'default'
63
+ # size_value = None
64
+ # start_fresh = True
65
+ # iris_by_coherence = False
66
+
67
+ # -----------------------------------------------------------------------------
68
+ # Support function
69
+ def get_rotation_matrix_2d(center, angle):
70
+ (x0, y0) = center[0], center[1]
71
+ M = np.array([[np.cos(angle), -np.sin(angle),
72
+ x0*(1 - np.cos(angle)) + y0*np.sin(angle)],
73
+ [np.sin(angle), np.cos(angle),
74
+ y0*(1 - np.cos(angle)) - x0*np.sin(angle)],
75
+ [0, 0, 1]])
76
+ return M
77
+
78
+ # -----------------------------------------------------------------------------
79
+ # adjust_size, size_value -> only in 1st section, which is not necessary here.
80
+ # color_of_result -> 303
81
+ #
82
+ # actually required:
83
+ # MotherDir, start_fresh, iris_by_coherence
84
+
85
+
86
+ def get_landmarks(MotherDir, file_prefix, file_postfix, include_jaw=False,
87
+ start_fresh=True):
88
+
89
+ # files = make_files(MotherDir, file_prefix, file_postfix)
90
+ # source_files = files[0]
91
+ # output_files = files[1]
92
+ source_files = get_source_files(MotherDir, FilePrefix=file_prefix,
93
+ FilePostfix=file_postfix)
94
+
95
+ # adjust_size='default', size_value=None, color_of_result='grayscale'
96
+
97
+ # MotherDir = "/Users/carl/Studies/Collaborations/Eugenie/RawFacesCleaned/"
98
+ # MotherDir = "/Users/carl/Studies/your_project_here/faces/"
99
+ # viewpoint = 'all' # 'front', 'left', 'right'
100
+
101
+ # Copy directory structure for output
102
+ MotherBits = MotherDir.split(os.path.sep)
103
+ go_back = -len(MotherBits[-2]) - 1
104
+ GrannyDir = MotherDir[0:go_back]
105
+ file_results_temporary = MotherDir + "results_temporary.dat"
106
+ file_results = MotherDir + "results.dat"
107
+
108
+ if start_fresh:
109
+ try:
110
+ with open(file_results_temporary, "rb") as f:
111
+ print(file_results_temporary + "\t exists.\nRemoving ...")
112
+ os.remove(file_results_temporary)
113
+ except IOError as error:
114
+ print(error)
115
+
116
+ # To do: User interface for getting this, or simple arguments into script
117
+ keys_to_remove_for_alignment = ['JAWLINE_POINTS']
118
+
119
+ # try:
120
+ # f = open(file_results_aligned, "rb")
121
+ # data2 = []
122
+ # for _ in range(pickle.load(f)):
123
+ # data2.append(pickle.load(f))
124
+ # LabelToCoordinateIndex = data2[0]
125
+ # output_files = data2[1]
126
+ # FinalLandmarks = data2[2]
127
+ # f.close()
128
+ # except:
129
+ ###########################################################################
130
+ # input and output files
131
+ # with open(GrannyDir + 'input_files.data', 'rb') as filehandle:
132
+ # source_files = pickle.load(filehandle)
133
+ # with open(GrannyDir + 'output_files.data', 'rb') as filehandle:
134
+ # output_files = pickle.load(filehandle)
135
+
136
+ ###########################################################################
137
+ # Main
138
+
139
+ # Obtain facial landmarks and center-point for all faces
140
+ try:
141
+ with open(file_results_temporary, "rb") as f:
142
+ data_in = []
143
+ for _ in range(pickle.load(f)):
144
+ data_in.append(pickle.load(f))
145
+ AllSubjectsLandmarksDict = data_in[0]
146
+ AllSubjectsLandmarksDictAllFeature = data_in[1]
147
+ MeanXY = data_in[2]
148
+ LandmarkFailureFile = data_in[3]
149
+ LandmarkFailureIndex = data_in[4]
150
+ StartingIndex = len(MeanXY) + len(LandmarkFailureFile)
151
+ IrisPoints = data_in[5]
152
+ except IOError as error:
153
+ print(error)
154
+ AllSubjectsLandmarksDict = []
155
+ AllSubjectsLandmarksDictAllFeature = []
156
+ MeanXY = []
157
+ LandmarkFailureFile = []
158
+ LandmarkFailureIndex = []
159
+ StartingIndex = 0
160
+ IrisPoints = []
161
+
162
+ # for infile, outfile, facei in zip(source_files[StartingIndex:], output_files[StartingIndex:], range(StartingIndex, len(source_files))):
163
+ for infile, facei in zip(source_files[StartingIndex:], range(StartingIndex, len(source_files))):
164
+ # image = cv2.imread(infile)
165
+ # gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
166
+ gray = np.array(PilImage.open(infile).convert("L"))
167
+ Landmarks = facial_landmarks(gray)
168
+
169
+ if Landmarks is None:
170
+ print("Landmarks is None: " + str(facei))
171
+ LandmarkFailureFile.append(infile)
172
+ LandmarkFailureIndex.append(facei)
173
+ # AllSubjectsLandmarksDictAllFeature.append(np.array(CoordinateVector))
174
+ #
175
+ # AllSubjectsLandmarksDict.append(np.array(CoordinateVector))
176
+ #
177
+ # MeanXY.append(np.array([None, None]))
178
+ else:
179
+ le = Landmarks['LEFT_EYE_POINTS']
180
+ re = Landmarks['RIGHT_EYE_POINTS']
181
+ mle = le.mean(axis=0)
182
+ mre = re.mean(axis=0)
183
+ temp_dict = {0: (int(mre[1]), int(mre[0])),
184
+ 1: (int(mle[1]), int(mle[0]))}
185
+ IrisPoints.append(temp_dict)
186
+
187
+ Landmarks = pull_jawline_to_inside_of_hairline(Landmarks, gray)
188
+ CoordinateVector, LabelToCoordinateIndexAll = unpack_dict_to_vector_xy(Landmarks)
189
+ AllSubjectsLandmarksDictAllFeature.append(np.array(CoordinateVector))
190
+
191
+ for this_key in keys_to_remove_for_alignment:
192
+ Landmarks.pop(this_key, None)
193
+
194
+ CoordinateVector, LabelToCoordinateIndex = unpack_dict_to_vector_xy(Landmarks)
195
+ AllSubjectsLandmarksDict.append(np.array(CoordinateVector))
196
+
197
+ mean_x, mean_y = get_translation(CoordinateVector)
198
+ MeanXY.append(np.array([mean_x, mean_y]))
199
+
200
+ f = open(file_results_temporary, "wb")
201
+ data = [AllSubjectsLandmarksDict, AllSubjectsLandmarksDictAllFeature, MeanXY, LandmarkFailureFile, LandmarkFailureIndex, IrisPoints]
202
+ pickle.dump(len(data), f)
203
+ for value in data:
204
+ pickle.dump(value, f)
205
+ f.close()
206
+
207
+ print(f'{facei:5d}', end="\r")
208
+
209
+ # Remove files with failed landmark-detection.
210
+ # for bi, bf in zip(LandmarkFailureIndex, LandmarkFailureFile):
211
+ # print("Removing " + source_files[bi])
212
+ # print(" which is " + bf)
213
+ # del source_files[bi]
214
+ # del output_files[bi]
215
+ SF = np.asarray(source_files)
216
+ # OF = np.asarray(output_files)
217
+ # keep_locs = (set([l for l in range(len(output_files))]) -
218
+ # set(LandmarkFailureIndex))
219
+ keep_locs = (set([l for l in range(len(source_files))]) -
220
+ set(LandmarkFailureIndex))
221
+ source_files = list(SF[list(keep_locs)])
222
+ # output_files = list(OF[list(keep_locs)])
223
+
224
+ # Rewrite those files to disk (source directory)
225
+ # with open(MotherDir + 'input_files.data', 'wb') as filehandle:
226
+ # pickle.dump(source_files, filehandle)
227
+ # with open(MotherDir + 'output_files.data', 'wb') as filehandle:
228
+ # pickle.dump(output_files, filehandle)
229
+
230
+ # Create new file that lists any failures
231
+ # with open(MotherDir + 'excluded_files.data', 'wb') as filehandle:
232
+ # pickle.dump(LandmarkFailureFile, filehandle)
233
+
234
+ # -----------------------------------------------------------------------------
235
+ # Produce a single JSON file that contains all landmark data
236
+ lmd = len(MotherDir)
237
+ # Combine into single dictionary
238
+ features = ['LEFT_EYEBROW', 'LEFT_EYE',
239
+ 'RIGHT_EYEBROW', 'RIGHT_EYE',
240
+ 'NOSE', 'MOUTH_OUTLINE',
241
+ 'MOUTH_INNER', 'JAWLINE']
242
+ landmarks = dict()
243
+ number_photos = len(source_files)
244
+ for outfile, i in zip(source_files, range(0, number_photos)):
245
+ # LandmarksThisGuy = FinalLandmarks[i]
246
+ LandmarksThisGuy = AllSubjectsLandmarksDictAllFeature[i]
247
+ ThisSubjectLandmarksDict = pack_vector_xy_as_dict(LandmarksThisGuy.tolist(),
248
+ LabelToCoordinateIndexAll)
249
+ fullpath = source_files[i]
250
+ # partpath = fullpath[fullpath.index('aligned') + len('aligned'):]
251
+ partpath = fullpath[lmd:]
252
+ this_photo = dict()
253
+ for fi in range(len(features)):
254
+ this_array = ThisSubjectLandmarksDict[features[fi] + '_POINTS']
255
+ # this_array = AllSubjectsLandmarksDictAllFeature[features[fi] + '_POINTS']
256
+ this_photo[features[fi].lower()] = \
257
+ list(this_array.flatten().astype(float))
258
+ # list(this_array.flatten().astype(np.float))
259
+ # TO DO
260
+ # append iris points [x, y] to this_photo
261
+ # take from IrisPoints
262
+ #
263
+ # IrisPoints is list [p1, p2, ... pn] for n people, where
264
+ # p_i is dictionary
265
+ # p_i{0} = (y, x)_for right eye
266
+ # p_i{1} = (y, x) for left eye
267
+ this_photo['left_iris'] = [float(IrisPoints[i][1][1]),
268
+ float(IrisPoints[i][1][0])]
269
+ this_photo['right_iris'] = [float(IrisPoints[i][0][1]),
270
+ float(IrisPoints[i][0][0])]
271
+ landmarks[partpath] = this_photo
272
+
273
+ # export to JSON
274
+ json_file = MotherDir + 'landmarks.txt'
275
+ with open(json_file, 'w') as outfile:
276
+ json.dump(landmarks, outfile)
277
+
278
+ # End of chunk for Getting Landmarks
279
+ #
280
+ # Output:
281
+ # source_files, output_files, AllSubjectsLandmarksDict,
282
+ # AllSubjectsLandmarksDictAllFeature, MeanXY, LandmarkFailureFile,
283
+ # LandmarkFailureIndex, IrisPoints
284
+ # files = []
285
+ # files.append(source_files)
286
+ # files.append(output_files)
287
+ if include_jaw:
288
+ listOfLandmarks = AllSubjectsLandmarksDictAllFeature
289
+ else:
290
+ listOfLandmarks = AllSubjectsLandmarksDict
291
+ print("\n\n------------------------------------------------------------\n")
292
+ print("\nThere should now be a JSON-formatted" +
293
+ "file called landmarks.txt in:\n")
294
+ print("\t" + MotherDir + "\n")
295
+ print("Please examine the contents.\n\n")
296
+ print("\n--------------------------------------------------------------\n")
297
+ return source_files, listOfLandmarks, MeanXY, IrisPoints
298
+ # -------------------------------------------------------------------------
299
+
300
+
301
+ def get_landmark_features(source_dir, output_dir="", exclude_features=['jawline',
302
+ 'left_iris', 'right_iris', 'mouth_inner']):
303
+ # Returns a DICT with lots of useful stuff, like eye_distances.
304
+ # AllSubjectsLandmarksDict is landmarks for all faces.
305
+ # exclude_features refers to this; does not affect calculation of other stuff.
306
+
307
+ # Determine if landmarks.txt exists in source_dir.
308
+ # Load or return error if does not exist.
309
+ full_landmark_filename = source_dir + 'landmarks.txt'
310
+ exists = os.path.isfile(full_landmark_filename)
311
+ if exists:
312
+ with io.open(full_landmark_filename, 'r') as f:
313
+ imported_landmarks = json.loads(f.readlines()[0].strip())
314
+ else:
315
+ print(['JSON file landmarks.txt does not exist in ' + source_dir])
316
+ return
317
+
318
+ # Initialize results
319
+ eye_distances = []
320
+ eye_radians = []
321
+ eye_midpoints = []
322
+ mouth2eye_distances = []
323
+ mouth2eye_radians = []
324
+ nose_tips = []
325
+
326
+ # Combine source_dir and output_dir with keys of imported_landmarks to get
327
+ # source_files and output_files.
328
+ #
329
+ # Also obtaining corresponding LOL as appropriate
330
+ # input to alignment functions.
331
+ source_files = []
332
+ output_files = []
333
+ LOL = []
334
+ MeanXY = []
335
+ IrisPoints = []
336
+ for rest_of_file_name in imported_landmarks.keys():
337
+ source_files.append(source_dir + rest_of_file_name)
338
+ output_files.append(output_dir + rest_of_file_name)
339
+
340
+ # Convert imported_landmarks -> LOL (like listOfLandmarks)
341
+ dict_of_coordinates = imported_landmarks[rest_of_file_name]
342
+ this_person = np.array([], dtype=np.int64).reshape(0,)
343
+ this_iris = dict()
344
+
345
+ # Get specific features.
346
+ dickeys = dict_of_coordinates.keys()
347
+ if ('left_iris' in dickeys) and ('right_iris' in dickeys):
348
+ left_iris = np.array(dict_of_coordinates['left_iris'])
349
+ right_iris = np.array(dict_of_coordinates['right_iris'])
350
+ else:
351
+ ex = np.array(dict_of_coordinates['left_eye'][0::2]).mean()
352
+ ey = np.array(dict_of_coordinates['left_eye'][1::2]).mean()
353
+ left_iris = np.array([ex, ey])
354
+ ex = np.array(dict_of_coordinates['right_eye'][0::2]).mean()
355
+ ey = np.array(dict_of_coordinates['right_eye'][1::2]).mean()
356
+ right_iris = np.array([ex, ey])
357
+
358
+ mouth_inner = dict_of_coordinates['mouth_inner']
359
+ mouth_outline = dict_of_coordinates['mouth_outline']
360
+ nx = dict_of_coordinates['nose'][0::2]
361
+ ny = dict_of_coordinates['nose'][1::2]
362
+ NOSE_TIP = np.array([nx[6], ny[6]])
363
+ nose_tips.append(NOSE_TIP)
364
+
365
+ eye_vector = left_iris - right_iris
366
+ EYE_DIST = np.sqrt(((eye_vector)**2).sum())
367
+ EYE_RAD = np.arctan2(eye_vector[1], eye_vector[0])
368
+ trans_eye = np.array([(EYE_DIST / 2) * np.cos(EYE_RAD),
369
+ (EYE_DIST / 2) * np.sin(EYE_RAD)])
370
+ EYE_MID = right_iris + trans_eye
371
+ eye_distances.append(EYE_DIST)
372
+ eye_radians.append(EYE_RAD)
373
+ eye_midpoints.append(EYE_MID)
374
+
375
+ mix = np.array(mouth_inner[0::2]).mean()
376
+ miy = np.array(mouth_inner[1::2]).mean()
377
+ mox = np.array(mouth_outline[0::2]).mean()
378
+ moy = np.array(mouth_outline[1::2]).mean()
379
+ mcx, mcy = (mox + mix) / 2, (moy + miy) / 2
380
+ mouth = np.array([mcx, mcy])
381
+
382
+ mouth_vector = mouth - EYE_MID
383
+ MOUTH_DIST = np.sqrt(((mouth_vector)**2).sum())
384
+ MOUTH_RAD = np.arctan2(mouth_vector[1], mouth_vector[0])
385
+ mouth2eye_distances.append(MOUTH_DIST)
386
+ mouth2eye_radians.append(MOUTH_RAD)
387
+
388
+ # Concatenation of landmarks (with some exclusions)
389
+ for this_feature in dict_of_coordinates.keys():
390
+ if not (this_feature in exclude_features):
391
+ these_coords = dict_of_coordinates[this_feature]
392
+ this_person = np.hstack((this_person, np.array(these_coords)))
393
+ if (this_feature == 'left_iris'):
394
+ these_coords = dict_of_coordinates[this_feature]
395
+ this_iris[1] = np.array([these_coords[1], these_coords[0]])
396
+ if (this_feature == 'right_iris'):
397
+ these_coords = dict_of_coordinates[this_feature]
398
+ this_iris[0] = np.array([these_coords[1], these_coords[0]])
399
+ LOL.append(this_person)
400
+ IrisPoints.append(this_iris)
401
+ xj = this_person[0::2]
402
+ yj = this_person[1::2]
403
+ MeanXY.append(np.array([int(xj.mean()), int(yj.mean())]))
404
+
405
+ AllSubjectsLandmarksDict = LOL
406
+
407
+ # Vectors of indices for each facial feature
408
+ for remove_this_feature in exclude_features:
409
+ del dict_of_coordinates[remove_this_feature]
410
+ CoordinateVector, LabelToCoordinateIndex = unpack_dict_to_vector_xy(dict_of_coordinates)
411
+
412
+ # Package final results.
413
+ landmark_features = dict()
414
+ landmark_features['eye_distances'] = eye_distances
415
+ landmark_features['eye_radians'] = eye_radians
416
+ landmark_features['eye_midpoints'] = eye_midpoints
417
+ landmark_features['mouth2eye_distances'] = mouth2eye_distances
418
+ landmark_features['mouth2eye_radians'] = mouth2eye_radians
419
+ landmark_features['nose_tips'] = nose_tips
420
+
421
+ landmark_features['AllSubjectsLandmarksDict'] = AllSubjectsLandmarksDict
422
+ landmark_features['IrisPoints'] = IrisPoints
423
+ landmark_features['MeanXY'] = MeanXY
424
+ landmark_features['LabelToCoordinateIndex'] = LabelToCoordinateIndex
425
+
426
+ files = []
427
+ files.append(source_files)
428
+ files.append(output_files)
429
+ return landmark_features, files
430
+
431
+
432
+ def landmarks_report(source_dir, file_prefix="", file_postfix="jpg"):
433
+ numbers = {}
434
+ infiles = get_source_files(source_dir, file_prefix, file_postfix)
435
+ rel_paths = [fp[len(source_dir):] for fp in infiles]
436
+ num_total_images = len(rel_paths)
437
+
438
+ full_path_inaccurate = source_dir + "bad-landmarks.csv"
439
+ if os.path.isfile(full_path_inaccurate):
440
+ with open(full_path_inaccurate) as csv_file:
441
+ csv_reader = csv.reader(csv_file, delimiter=',')
442
+ for row in csv_reader:
443
+ num_detected_but_removed = len(row);
444
+ else:
445
+ num_detected_but_removed = 0;
446
+
447
+ landmark_features, files = get_landmark_features(source_dir, source_dir)
448
+ num_in_landmarks_file = len(files[0])
449
+ num_failed_detections = (num_total_images - num_in_landmarks_file -
450
+ num_detected_but_removed)
451
+ numbers['num_total_images'] = num_total_images
452
+ numbers['num_detected_but_removed'] = num_detected_but_removed
453
+ numbers['num_failed_detections'] = num_failed_detections
454
+
455
+ print("Total input images " + str(num_total_images))
456
+ print("Number of images with a failed face detection " + str(num_failed_detections))
457
+ print("Number of images with a landmark inaccuracy " + str(num_detected_but_removed))
458
+
459
+ return numbers
460
+
461
+
462
+ def exclude_files_with_bad_landmarks(source_dir):
463
+ full_bad_filename = source_dir + "bad-landmarks.csv"
464
+ exists = os.path.isfile(full_bad_filename)
465
+ if exists:
466
+ with open(full_bad_filename) as f:
467
+ reader = csv.reader(f)
468
+ remove_these_files = list(reader)[0]
469
+ else:
470
+ print(['CSV file bad-landmarks.csv does not exist in ' + source_dir])
471
+ return None
472
+
473
+ full_landmark_filename = source_dir + "landmarks.txt"
474
+ exists = os.path.isfile(full_landmark_filename)
475
+ if exists:
476
+ with io.open(full_landmark_filename, 'r') as f:
477
+ imported_landmarks = json.loads(f.readlines()[0].strip())
478
+ else:
479
+ print(['JSON file landmarks.txt does not exist in ' + source_dir])
480
+ return None
481
+
482
+ # First save original with a new name just in case
483
+ full_landmark_filename = source_dir + "landmarks-original.txt"
484
+ with open(full_landmark_filename, 'w') as outfile:
485
+ json.dump(imported_landmarks, outfile)
486
+
487
+ # Now delete unwanted entries and save as landmarks.txt
488
+ for this_file in remove_these_files:
489
+ del imported_landmarks[this_file.strip()]
490
+ full_landmark_filename = source_dir + "landmarks.txt"
491
+ with open(full_landmark_filename, 'w') as outfile:
492
+ json.dump(imported_landmarks, outfile)
493
+ return None
494
+
495
+
496
+ def tilt_radians_to_ensure_eyes_level(the_shape, LabelToCoordinateIndex):
497
+ x_locs = LabelToCoordinateIndex["left_eye"][::2]
498
+ y_locs = LabelToCoordinateIndex["left_eye"][1::2]
499
+ left_center = np.array([the_shape[x_locs].mean(), the_shape[y_locs].mean()])
500
+ x_locs = LabelToCoordinateIndex["right_eye"][::2]
501
+ y_locs = LabelToCoordinateIndex["right_eye"][1::2]
502
+ right_center = np.array([the_shape[x_locs].mean(), the_shape[y_locs].mean()])
503
+ eye_vector = left_center - right_center
504
+ clock_wise_norm = (-eye_vector[1], eye_vector[0])
505
+ eye_tilt_radians = np.arctan2(clock_wise_norm[1], clock_wise_norm[0])
506
+ rotate_to_level_radians = np.pi / 2 - eye_tilt_radians
507
+ return rotate_to_level_radians
508
+
509
+
510
+ def align_procrustes(source_dir, file_prefix='', file_postfix='jpg',
511
+ exclude_features=['jawline', 'left_iris', 'right_iris',
512
+ 'mouth_inner'], include_features=None,
513
+ adjust_size='default', size_value=None,
514
+ color_of_result='grayscale'):
515
+ # NOTE: include_features overrides exclude_features
516
+
517
+ # files, output_dir = make_files(source_dir, file_prefix, file_postfix,
518
+ # new_dir="aligned")
519
+ # source_files = files[0]
520
+ # output_files = files[1]
521
+
522
+ output_dir = clone_directory_tree(source_dir, new_dir="aligned",
523
+ FilePrefix=file_prefix,
524
+ FilePostfix=file_postfix)
525
+
526
+ if include_features != None:
527
+ features = ['left_eyebrow', 'left_eye', 'right_eyebrow', 'right_eye',
528
+ 'nose', 'mouth_outline', 'mouth_inner', 'jawline',
529
+ 'left_iris', 'right_iris']
530
+ exclude_features = list(set(features) - set(include_features))
531
+
532
+ landmark_features, files = get_landmark_features(source_dir, output_dir,
533
+ exclude_features)
534
+ source_files = files[0]
535
+ output_files = files[1]
536
+ # if len((set(source_files) - set(source_files_))) !=0:
537
+ # print("source_files returned by make_files() and get_landmark_features() are different.")
538
+ # print(source_files[0] + "\n" + source_files_[0])
539
+ # return
540
+ # source_files = files[0]
541
+ # output_files = files[1]
542
+ AllSubjectsLandmarksDict = landmark_features['AllSubjectsLandmarksDict']
543
+ IrisPoints = landmark_features['IrisPoints']
544
+ MeanXY = landmark_features['MeanXY']
545
+
546
+ LabelToCoordinateIndex = landmark_features['LabelToCoordinateIndex']
547
+
548
+ # # Determine if landmarks.txt exists in source_dir.
549
+ # # Load or return error if does not exist.
550
+ # full_landmark_filename = source_dir + 'landmarks.txt'
551
+ # exists = os.path.isfile(full_landmark_filename)
552
+ # if exists:
553
+ # with io.open(full_landmark_filename, 'r') as f:
554
+ # imported_landmarks = json.loads(f.readlines()[0].strip())
555
+ # else:
556
+ # print(['JSON file landmarks.txt does not exist in ' + source_dir])
557
+ # return
558
+ #
559
+ # # Combine source_dir and output_dir with keys of imported_landmarks to get
560
+ # # source_files and output_files.
561
+ # #
562
+ # # Also obtaining corresponding LOL as appropriate input
563
+ # # to alignment functions.
564
+ # # exclude_features = ['jawline', 'left_iris', 'right_iris']
565
+ # source_files = []
566
+ # output_files = []
567
+ # LOL = []
568
+ # MeanXY = []
569
+ # IrisPoints = []
570
+ # for rest_of_file_name in imported_landmarks.keys():
571
+ # source_files.append(source_dir + rest_of_file_name)
572
+ # output_files.append(output_dir + rest_of_file_name)
573
+ #
574
+ # # Convert imported_landmarks -> LOL (like listOfLandmarks)
575
+ # dict_of_coordinates = imported_landmarks[rest_of_file_name]
576
+ # this_person = np.array([], dtype=np.int64).reshape(0,)
577
+ # this_iris = dict()
578
+ # for this_feature in dict_of_coordinates.keys():
579
+ # if not (this_feature in exclude_features):
580
+ # these_coords = dict_of_coordinates[this_feature]
581
+ # this_person = np.hstack((this_person, np.array(these_coords)))
582
+ # if (this_feature == 'left_iris'):
583
+ # these_coords = dict_of_coordinates[this_feature]
584
+ # this_iris[1] = (these_coords[1], these_coords[0])
585
+ # if (this_feature == 'right_iris'):
586
+ # these_coords = dict_of_coordinates[this_feature]
587
+ # this_iris[0] = (these_coords[1], these_coords[0])
588
+ # LOL.append(this_person)
589
+ # IrisPoints.append(this_iris)
590
+ # xj = this_person[0::2]
591
+ # yj = this_person[1::2]
592
+ # MeanXY.append(np.array([int(xj.mean()), int(yj.mean())]))
593
+ #
594
+ # AllSubjectsLandmarksDict = LOL
595
+
596
+ # IrisPoints is list [p1, p2, ... pn] for n people, where
597
+ # p_i is dictionary
598
+ # p_i{0} = (y, x)_for right eye
599
+ # p_i{1} = (y, x) for left eye
600
+
601
+ # -------------------------------------------------------------------------
602
+ # EYE_DISTANCE, IMAGE_WIDTH, IMAGE_HEIGHT not used until 269-323
603
+ # EYE_DISTANCE -> Procrustes scaling of template 269
604
+ # IMAGE_WIDTH, IMAGE_HEIGHT -> padding/cropping 322-23
605
+ #
606
+ # rest of variables within are local to this section.
607
+ assert adjust_size in ['set_eye_distance', 'set_image_width',
608
+ 'set_image_height', 'default']
609
+ if adjust_size == 'default':
610
+ assert size_value is None
611
+
612
+ # values for setting fixed proportions
613
+ _EYE_DISTANCE = 94
614
+ _IMAGE_WIDTH = 300 - 20
615
+ _IMAGE_HEIGHT = 338 + 80 - 30
616
+
617
+ if adjust_size == 'default':
618
+ EYE_DISTANCE = _EYE_DISTANCE
619
+ IMAGE_WIDTH = _IMAGE_WIDTH
620
+ IMAGE_HEIGHT = _IMAGE_HEIGHT
621
+ elif adjust_size == 'set_eye_distance':
622
+ EYE_DISTANCE = size_value
623
+ IMAGE_WIDTH = round((_IMAGE_WIDTH / _EYE_DISTANCE) * EYE_DISTANCE)
624
+ IMAGE_HEIGHT = round((_IMAGE_HEIGHT / _EYE_DISTANCE) * EYE_DISTANCE)
625
+ elif adjust_size == 'set_image_width':
626
+ IMAGE_WIDTH = size_value
627
+ EYE_DISTANCE = round((_EYE_DISTANCE / _IMAGE_WIDTH) * IMAGE_WIDTH)
628
+ IMAGE_HEIGHT = round((_IMAGE_HEIGHT / _IMAGE_WIDTH) * IMAGE_WIDTH)
629
+ elif adjust_size == 'set_image_height':
630
+ IMAGE_HEIGHT = size_value
631
+ EYE_DISTANCE = round((_EYE_DISTANCE / _IMAGE_HEIGHT) * IMAGE_HEIGHT)
632
+ IMAGE_WIDTH = round((_IMAGE_WIDTH / _IMAGE_HEIGHT) * IMAGE_HEIGHT)
633
+ # -------------------------------------------------------------------------
634
+
635
+ # -------------------------------------------------------------------------
636
+ # Get eyedist and jawdist.
637
+ # Need IrisPoints and AllSubjectsLandmarksDictAllFeature.
638
+
639
+ # NOTES ON ORIGINAL FORMAT OF IrisPoints
640
+ #
641
+ # IrisPoints is list [p1, p2, ... pn] for n people, where
642
+ # p_i is dictionary
643
+ # p_i{0} = (y, x)_for right eye
644
+ # p_i{1} = (y, x) for left eye
645
+
646
+ number_so_far = len(IrisPoints)
647
+ # jawdist = np.empty((number_so_far, 1))
648
+ eyedist = np.empty((number_so_far, 1))
649
+ for this_face in range(0, number_so_far):
650
+ # lx = AllSubjectsLandmarksDictAllFeature[this_face][0::2]
651
+ # ly = AllSubjectsLandmarksDictAllFeature[this_face][1::2]
652
+ ey = [IrisPoints[this_face][0][0], IrisPoints[this_face][1][0]]
653
+ ex = [IrisPoints[this_face][0][1], IrisPoints[this_face][1][1]]
654
+ # JX = lx[0:17:16]
655
+ # JY = ly[0:17:16]
656
+ # JD = abs(np.diff(JX + JY * 1j))
657
+ ED = abs(np.diff(np.array(ex) + np.array(ey) * 1j))
658
+ # jawdist[this_face, 0] = JD[0]
659
+ eyedist[this_face, 0] = ED[0]
660
+ # -------------------------------------------------------------------------
661
+
662
+ # -------------------------------------------------------------------------
663
+ # Generalised Procrustes analysis
664
+ #
665
+ # Need:
666
+ # AllSubjectsLandmarksDict
667
+ # eyedist, EYE_DISTANCE
668
+ # source_files, output_files
669
+ # color_of_result
670
+ # IMAGE_WIDTH, IMAGE_HEIGHT
671
+ #
672
+ # To do:
673
+ # Rotate mean_shape_proper_scale by
674
+ # (a) line between mouth-center and midpoint of eyes.
675
+ # (b) line between eyes
676
+ # (c) leave as is
677
+ #
678
+ # Scale
679
+ # (a) line between mouth-center and midpoint of eyes.
680
+ # (b) line between eyes [CURRENT]
681
+ #
682
+ # Actually, the use of specialized GP function is not necessary hereself.
683
+ # I simply need the mean shape, which is simple to calculate and whose
684
+ # values for each landmark are independent.
685
+ #
686
+ # I acutally code the rest myself without specialized GP.
687
+ # Check to see if that's actually correct.
688
+ #
689
+ # True but:
690
+ # new_shapes is useful for comparing with landmarks of aligned images.
691
+ # mean_shape, new_shapes = pt.generalized_procrustes_analysis(AllSubjectsLandmarksDict)
692
+ mean_shape, new_shapes, shit = generalized_procrustes_analysis(AllSubjectsLandmarksDict)
693
+ # pdb.set_trace() # TEMPORARY FOR DEBUGGING
694
+ # # mean_shape_with_jawline, variable_not_used = pt.generalized_procrustes_analysis(AllSubjectsLandmarksDictAllFeature)
695
+ # mean_shape_with_jawline, shit, shat = pt.generalized_procrustes_analysis(AllSubjectsLandmarksDictAllFeature)
696
+ #
697
+ # # Set mean shape to have a head width desired for all of the faces
698
+ # LandmarksOfCenteredMeanShape = pack_vector_xy_as_dict(mean_shape_with_jawline.tolist(), LabelToCoordinateIndexAll)
699
+ # HeadWidthOfMeanShape = LandmarksOfCenteredMeanShape['JAWLINE_POINTS'][:,0].max() - LandmarksOfCenteredMeanShape['JAWLINE_POINTS'][:,0].min()
700
+
701
+
702
+ # LandmarksOfCenteredMeanShape = pack_vector_xy_as_dict(mean_shape.tolist(), LabelToCoordinateIndex)
703
+ # LE = LandmarksOfCenteredMeanShape['LEFT_EYE_POINTS'].mean(axis=0)
704
+ # RE = LandmarksOfCenteredMeanShape['RIGHT_EYE_POINTS'].mean(axis=0)
705
+ # EyeDistOfMeanShape = np.sqrt(((RE-LE)**2).sum())
706
+ # print([EyeDistOfMeanShape, MeanEyeDist])
707
+
708
+
709
+ # EYE_DISTANCE = HeadWidthConstant * EyeDistToHeadWidthRatio
710
+
711
+ # Incorrect denominator for normalization:
712
+ # MeanEyeDist = eyedist.mean()
713
+ # mean_shape_proper_scale = mean_shape * (EYE_DISTANCE / MeanEyeDist)
714
+ # or course, it should be the actual distance within mean_shape!!!
715
+
716
+ have_both_eyes = ("right_eye" in list(LabelToCoordinateIndex) and
717
+ "left_eye" in list(LabelToCoordinateIndex))
718
+
719
+ if have_both_eyes:
720
+ left_eye = mean_shape[LabelToCoordinateIndex["left_eye"]]
721
+ right_eye = mean_shape[LabelToCoordinateIndex["right_eye"]]
722
+ LX = left_eye[::2].mean()
723
+ LY = left_eye[1::2].mean()
724
+ RX = right_eye[::2].mean()
725
+ RY = right_eye[1::2].mean()
726
+ mean_shape_eye_dist = np.sqrt((LX-RX)**2 + (LY-RY)**2)
727
+ mean_shape_proper_scale = mean_shape * (EYE_DISTANCE / mean_shape_eye_dist)
728
+ LabelToCoordinateIndex = landmark_features['LabelToCoordinateIndex']
729
+ angle_constant_rad = tilt_radians_to_ensure_eyes_level(mean_shape_proper_scale, LabelToCoordinateIndex)
730
+ angle_constant = angle_constant_rad * 180 / math.pi
731
+ else:
732
+ mean_shape_proper_scale = mean_shape
733
+ angle_constant = 0
734
+
735
+ # Now obtain precise scale and angle change for AllSubjectsLandmarksDict[i] --> mean_shape_proper_scale
736
+ # FinalLandmarks = []
737
+ # FinalLandmarksLimited = []
738
+ # LastLandmarkFailureFile = []
739
+ # LastLandmarkFailureIndex = []
740
+ current_working_dir = os.getcwd()
741
+ for infile, outfile, CenterOnThisFace in zip(source_files, output_files, range(0,len(source_files))):
742
+ # for infile, outfile, CenterOnThisFace in zip(source_files[0:50], output_files[0:50], range(0, 50)):
743
+
744
+ ################################
745
+ # from procrustes_analysis.py
746
+ temp_sh = np.copy(AllSubjectsLandmarksDict[CenterOnThisFace])
747
+ translate(temp_sh)
748
+
749
+ # get scale and rotation
750
+ scale, theta = get_rotation_scale(mean_shape_proper_scale, temp_sh)
751
+ scale = 1 / scale
752
+ angle_degree = (theta * 180 / math.pi) + angle_constant
753
+ ################################
754
+
755
+ # 0. load image to be aligned
756
+ # image = cv2.imread(infile)
757
+ image = np.array(PilImage.open(infile))
758
+
759
+ if color_of_result == 'grayscale':
760
+ # input_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
761
+ input_image = np.array(PilImage.open(infile).convert("L"))
762
+ else:
763
+ input_image = image
764
+
765
+ # 1. translate center of landmarks to center of image
766
+ rows, cols = input_image.shape[0], input_image.shape[1]
767
+ Cy, Cx = rows/2, cols/2
768
+ DeltaX = Cx - MeanXY[CenterOnThisFace][0]
769
+ DeltaY = Cy - MeanXY[CenterOnThisFace][1]
770
+ # M = np.float32([[1, 0, DeltaX], [0, 1, DeltaY]])
771
+ # translated_image = cv2.warpAffine(input_image, M, (cols, rows))
772
+ tform = SimilarityTransform(translation=(-DeltaX, -DeltaY))
773
+ translated_image = warp(input_image, tform, preserve_range=True)
774
+
775
+ # 2. rotate to match orientation of mean shape
776
+ # cv_rotation_matrix = cv2.get_rotation_matrix_2d((Cx, Cy), -angle_degree, 1)
777
+ # rotated_image = cv2.warpAffine(translated_image,
778
+ # cv_rotation_matrix, (cols, rows))
779
+ angle_radians = angle_degree * np.pi / 180
780
+ M = get_rotation_matrix_2d((Cx, Cy), -angle_radians)
781
+ tform = SimilarityTransform(matrix=M)
782
+ rotated_image = warp(translated_image, tform, preserve_range=True).astype(np.uint8)
783
+
784
+ # 3. scale to match size of mean shape & recalculate image center
785
+ # scaled_image = cv2.resize(rotated_image, (0, 0), fx=scale, fy=scale)
786
+ oldrows, oldcols = rotated_image.shape[0], rotated_image.shape[1]
787
+ scaled_image = dlib.resize_image(rotated_image, int(oldrows * scale),
788
+ int(oldcols * scale))
789
+
790
+ DeltaY = EYE_DISTANCE * 5 / 12 # Can add additional options here
791
+ tform = SimilarityTransform(translation=(0, -DeltaY))
792
+ cropped_image = warp(scaled_image, tform, preserve_range=True).astype(np.uint8)
793
+
794
+ # Additional shift if mouth landmarks excluded from alignment.
795
+ ema = "mouth_inner" in exclude_features
796
+ emb = "mouth_outline" in exclude_features
797
+ if (ema & emb):
798
+ DeltaY = EYE_DISTANCE * 29 / 94
799
+ tform = SimilarityTransform(translation=(0, DeltaY))
800
+ cropped_image = warp(cropped_image, tform, preserve_range=True).astype(np.uint8)
801
+
802
+ rows, cols = cropped_image.shape[0], cropped_image.shape[1]
803
+ Cy, Cx = rows/2, cols/2
804
+
805
+ # 4. crop to be 256 x 256 -- centered on (Cy, Cx)
806
+ Cy = int(Cy)
807
+ Cx = int(Cx)
808
+ # cropped_gray = scaled_gray[Cy-128:Cy+128, Cx-128:Cx+128]
809
+ # cropped_gray = scaled_gray[Cy-144-15:Cy+144-15, Cx-128:Cx+128]
810
+ add_vertical = IMAGE_HEIGHT - cropped_image.shape[0]
811
+ add_horizontal = IMAGE_WIDTH - cropped_image.shape[1]
812
+ # if (add_vertical > 0) or (add_horizontal > 0):
813
+ # pad_tuple = ((floor(add_vertical/2), ceil(add_vertical/2)), (floor(add_horizontal/2), ceil(add_horizontal/2)))
814
+ # cropped_gray = np.pad(scaled_gray, pad_tuple, 'constant', constant_values=((0,0),(0,0)))
815
+ # else:
816
+ # cropped_gray = scaled_gray
817
+
818
+ # cropped_image = scaled_image
819
+
820
+ if cropped_image.ndim == 2:
821
+ if (add_vertical > 0):
822
+ pad = ((floor(add_vertical/2), ceil(add_vertical/2)), (0, 0))
823
+ cropped_image = np.pad(cropped_image, pad,
824
+ 'constant', constant_values=((0, 0), (0, 0)))
825
+ elif (add_vertical < 0):
826
+ pre_clip = floor(abs(add_vertical)/2)
827
+ pos_clip = ceil(abs(add_vertical)/2)
828
+ cropped_image = cropped_image[pre_clip: -pos_clip, :]
829
+ if (add_horizontal > 0):
830
+ pad = ((0, 0), (floor(add_horizontal/2), ceil(add_horizontal/2)))
831
+ cropped_image = np.pad(cropped_image, pad,
832
+ 'constant', constant_values=((0, 0), (0, 0)))
833
+ elif (add_horizontal < 0):
834
+ pre_clip = floor(abs(add_horizontal)/2)
835
+ pos_clip = ceil(abs(add_horizontal)/2)
836
+ cropped_image = cropped_image[:, pre_clip: -pos_clip]
837
+ else: # new lines to account for third channel of an RGB image
838
+ if (add_vertical > 0):
839
+ pad = ((floor(add_vertical/2), ceil(add_vertical/2)), (0, 0), (0, 0))
840
+ cropped_image = np.pad(cropped_image, pad,
841
+ 'constant', constant_values=((0, 0), (0, 0), (0, 0)))
842
+ elif (add_vertical < 0):
843
+ pre_clip = floor(abs(add_vertical)/2)
844
+ pos_clip = ceil(abs(add_vertical)/2)
845
+ cropped_image = cropped_image[pre_clip: -pos_clip, :, :]
846
+ if (add_horizontal > 0):
847
+ pad = ((0, 0), (floor(add_horizontal/2), ceil(add_horizontal/2)), (0, 0))
848
+ cropped_image = np.pad(cropped_image, pad,
849
+ 'constant', constant_values=((0, 0), (0, 0), (0, 0)))
850
+ elif (add_horizontal < 0):
851
+ pre_clip = floor(abs(add_horizontal)/2)
852
+ pos_clip = ceil(abs(add_horizontal)/2)
853
+ cropped_image = cropped_image[:, pre_clip: -pos_clip, :]
854
+
855
+ rows, cols = cropped_image.shape[0], cropped_image.shape[1]
856
+ Cy, Cx = rows/2, cols/2
857
+
858
+ # Rescale intensity to [0 255]
859
+ final_image = exposure.rescale_intensity(cropped_image)
860
+
861
+ DirBits = outfile.split(os.path.sep)
862
+ go_back = -len(DirBits[-1])
863
+ save_to_dir = outfile[0:go_back]
864
+ save_to_this = DirBits[-1]
865
+
866
+ if color_of_result == 'rgb':
867
+ # aligned_gray = cv2.cvtColor(final_image, cv2.COLOR_BGR2GRAY)
868
+ # final_image = cv2.cvtColor(final_image, cv2.COLOR_BGR2RGB)
869
+ aligned_gray = np.array(PilImage.fromarray(final_image).convert("L"))
870
+ final_image = np.array(PilImage.fromarray(final_image))
871
+ else:
872
+ aligned_gray = final_image
873
+
874
+ os.chdir(save_to_dir)
875
+ imsave(save_to_this, final_image)
876
+ os.chdir(current_working_dir)
877
+ print("\n\n------------------------------------------------------------\n")
878
+ print("\nThis directory:\n")
879
+ print("\t" + output_dir + "\n")
880
+ print("should now be populated with aligned faces.\n")
881
+ print("Please examine the contents.\n\n")
882
+ print("\n--------------------------------------------------------------\n")
883
+ # Write a specs.csv file to record constant image dimensions
884
+ fieldnames = ["adjust_size", "image_height", "image_width", "eye_distance"]
885
+ with open(output_dir + "specs.csv", mode="w") as csv_file:
886
+ writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
887
+ writer.writeheader()
888
+ writer.writerow({"adjust_size": adjust_size, "image_height":IMAGE_HEIGHT, "image_width":IMAGE_WIDTH,"eye_distance":EYE_DISTANCE})
889
+ # return mean_shape, new_shapes, source_files
890
+ return output_dir
891
+
892
+
893
+ def place_aperture(source_dir, file_prefix='', file_postfix='jpg',
894
+ aperture_type="MossEgg", no_save=False, contrast_norm="max",
895
+ color_of_result='grayscale'):
896
+ if color_of_result == 'rgb':
897
+ assert (contrast_norm=="max") or (contrast_norm==None)
898
+
899
+ # files, output_dir = make_files(source_dir, file_prefix, file_postfix,
900
+ # new_dir="windowed")
901
+ # source_files = files[0]
902
+ # output_files = files[1]
903
+
904
+ output_dir = clone_directory_tree(source_dir, new_dir="windowed",
905
+ FilePrefix=file_prefix,
906
+ FilePostfix=file_postfix)
907
+
908
+ landmark_features, files = get_landmark_features(source_dir, output_dir)
909
+ source_files = files[0]
910
+ output_files = files[1]
911
+
912
+ # if len((set(source_files) - set(source_files_))) !=0:
913
+ # print("source_files returned by make_files() and get_landmark_features() are different.")
914
+ # print(source_files[0] + "\n" + source_files_[0])
915
+ # return
916
+
917
+ shapes = np.array(landmark_features['AllSubjectsLandmarksDict'])
918
+ # source_files = files[0]
919
+ FilePostfix = source_files[0].split("/")[-1].split(".")[-1]
920
+
921
+ # Points for all faces, mean face, and center of all landmarks.
922
+ mean_shape = shapes.mean(axis=0)
923
+ MX, MY = mean_shape[0::2], mean_shape[1::2]
924
+ CX, CY = MX.mean(), MY.mean()
925
+ # size = cv2.imread(source_files[0]).shape[0:2]
926
+ size = np.array(PilImage.open(source_files[0]).convert("L")).shape[0:2]
927
+
928
+ aperture_good = True
929
+ if aperture_type == "Ellipse":
930
+ X = shapes[:, 0::2].reshape(-1,)
931
+ Y = shapes[:, 1::2].reshape(-1,)
932
+
933
+ # Longest vertical length of ellipse that fits within image.
934
+ if (size[0] / 2) < CY:
935
+ ellipse_height = (size[0] - CY) * 2
936
+ elif (size[0] / 2) > CY:
937
+ ellipse_height = CY * 2
938
+ else:
939
+ ellipse_height = size[0]
940
+ semi_major = ellipse_height / 2
941
+
942
+ semi_minor = fit_ellipse_semi_minor(semi_major=semi_major,
943
+ landmarks=(X, Y),
944
+ center=(CX, CY))
945
+
946
+ the_aperture = make_ellipse_map(semi_minor, semi_major,
947
+ (CX, CY), size, soften=True)
948
+ elif aperture_type == "MossEgg":
949
+ the_aperture = make_moss_egg(landmark_features, (CX, CY),
950
+ size, fraction_width=47/100, soften=True)[0]
951
+ else:
952
+ print("Error: aperture_type should be Ellipse or MossEgg.")
953
+ aperture_good = False
954
+ # the_aperture = []
955
+ if no_save:
956
+ return the_aperture
957
+ if aperture_good:
958
+ # # Need this to create all subfolders in ./in_aperture
959
+ # files = make_files(source_dir, FilePrefix="",
960
+ # FilePostfix=FilePostfix, new_dir="windowed")
961
+ # output_files = files[1]
962
+ if color_of_result=='grayscale':
963
+ for infile, outfile in zip(source_files, output_files):
964
+ # image = cv2.imread(infile)
965
+ # gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
966
+ gray = np.array(PilImage.open(infile).convert("L"))
967
+ gray = contrast_stretch(gray, inner_locs=(the_aperture * 255) > 16,
968
+ type=contrast_norm)
969
+ BGRA = make_four_channel_image(img=gray, aperture=the_aperture)
970
+
971
+ out_png = outfile.split(".")[0] + ".png"
972
+ print(outfile)
973
+ print(out_png)
974
+ # cv2.imwrite(out_png, BGRA)
975
+ PilImage.fromarray(BGRA).save(out_png)
976
+ elif color_of_result=='rgb':
977
+ for infile, outfile in zip(source_files, output_files):
978
+ # rgb = cv2.imread(infile)
979
+ rgb = np.array(PilImage.open(infile))
980
+ rgb = contrast_stretch(rgb, inner_locs=[], type=contrast_norm)
981
+ BGRA = make_four_channel_image(img=rgb, aperture=the_aperture)
982
+
983
+ out_png = outfile.split(".")[0] + ".png"
984
+ print(outfile)
985
+ print(out_png)
986
+ # cv2.imwrite(out_png, BGRA)
987
+ PilImage.fromarray(BGRA).save(out_png)
988
+ print("\n\n--------------------------------------------------------\n")
989
+ print("\nThere should now be a bunch of faces set in apertures in:\n")
990
+ print("\t" + output_dir + "\n")
991
+ print("Please examine the contents.\n\n")
992
+ print("\n----------------------------------------------------------\n")
993
+ return the_aperture, output_dir
994
+
995
+
996
+ def get_mean_image(file_path, max_inner_face_contrast=False):
997
+ with io.open(file_path + '/landmarks.txt', 'r') as f:
998
+ imported_landmarks = json.loads(f.readlines()[0].strip())
999
+ guys = list(imported_landmarks.keys())
1000
+ gray_0 = np.array(PilImage.open(file_path + guys[0]).convert("L"))
1001
+ shape_0 = gray_0.shape
1002
+
1003
+ if max_inner_face_contrast:
1004
+ # Normalize contrast within aperture, common mean of 127.5
1005
+ the_aperture = place_aperture(file_path, file_path, no_save=True)
1006
+ inner_map = (the_aperture * 255) > 16
1007
+ gray_0 = contrast_stretch(gray_0, inner_locs=inner_map, type="mean_127") - 127.5
1008
+ else:
1009
+ # UINT8 to double. Center and normalize
1010
+ gray_0 = gray_0.astype(float)
1011
+ gray_0 -= gray_0.mean()
1012
+ gray_0 = gray_0 / gray_0.std()
1013
+
1014
+ mean_image = gray_0
1015
+ for guy in guys[1:]:
1016
+ gray = np.array(PilImage.open(file_path + guy).convert("L"))
1017
+ if gray.shape==shape_0:
1018
+
1019
+ if max_inner_face_contrast:
1020
+ # Normalize contrast within aperture, common mean of 127.5
1021
+ gray = contrast_stretch(gray, inner_locs=inner_map, type="mean_127") - 127.5
1022
+ else:
1023
+ # UINT8 to double. Center and normalize
1024
+ gray = gray.astype(float)
1025
+ gray -= gray.mean()
1026
+ gray = gray / gray.std()
1027
+
1028
+ mean_image += gray
1029
+ else:
1030
+ print("_get_mean_image() requires that all images are same dimensions!!")
1031
+ mean_image = None
1032
+ return mean_image
1033
+ # print("Go back to [0-255]")
1034
+ mean_image = mean_image / len(guys)
1035
+ return mean_image
1036
+
1037
+
1038
+ def warp_to_mean_landmarks(source_dir, file_prefix='', file_postfix='jpg'):
1039
+
1040
+ output_dir = clone_directory_tree(source_dir, new_dir="warp-to-mean",
1041
+ FilePrefix=file_prefix,
1042
+ FilePostfix=file_postfix)
1043
+
1044
+ landmark_features, files = get_landmark_features(source_dir, output_dir,
1045
+ exclude_features=['jawline',
1046
+ 'left_iris', 'right_iris'])
1047
+ source_files = files[0]
1048
+ L = np.array(landmark_features['AllSubjectsLandmarksDict'])
1049
+ # source_files = files[0]
1050
+
1051
+ # Image size and number of images
1052
+ # size = cv2.imread(source_files[0]).shape[0:2]
1053
+ size = np.array(PilImage.open(source_files[0]).convert("L")).shape[0:2]
1054
+ num_faces = len(source_files)
1055
+
1056
+ # Points for all faces, mean face, and center of all landmarks.
1057
+ ML = L.mean(axis=0)
1058
+ bx = ML[::2]
1059
+ by = ML[1::2]
1060
+ base = (bx, by)
1061
+
1062
+ # Important -> imarray and target (landmarks)
1063
+ original_images = np.zeros((num_faces, size[0], size[1]))
1064
+ for i, of in enumerate(source_files):
1065
+ # image = cv2.imread(of)
1066
+ # original_images[i, :, :] = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
1067
+ original_images[i, :, :] = np.array(PilImage.open(of).convert("L"))
1068
+ tx = L[i, ::2]
1069
+ ty = L[i, 1::2]
1070
+ target = (tx, ty)
1071
+
1072
+ base = np.transpose(np.array(base))
1073
+
1074
+ warped_to_mean = np.zeros((num_faces, size[0], size[1]))
1075
+ for i, of in enumerate(source_files):
1076
+ im = original_images[i, :, :]
1077
+ im -= im.min()
1078
+ im = im / im.max()
1079
+
1080
+ tx = L[i, ::2]
1081
+ ty = L[i, 1::2]
1082
+ target = (tx, ty)
1083
+ target = np.transpose(np.array(target))
1084
+
1085
+ warpim, tri, inpix, fwdwarpix = pawarp(im, base, target,
1086
+ interp='bilin')
1087
+ warped_to_mean[i, :, :] = warpim
1088
+
1089
+ # Write to file
1090
+ if not os.path.isdir(output_dir):
1091
+ os.mkdir(output_dir)
1092
+ for i, im in enumerate(warped_to_mean):
1093
+ sim = (im * 255).astype("uint8")
1094
+ out_png = output_dir + os.path.sep + "P" + str(i) + ".png"
1095
+ # cv2.imwrite(out_png, sim)
1096
+ PilImage.fromarray(sim).save(out_png)
1097
+
1098
+ # Estimate landmarks.
1099
+ get_landmarks(output_dir, "P", "png")
1100
+
1101
+ # Get aperture for inner-face
1102
+ the_aperture = place_aperture(output_dir, output_dir, no_save=True)
1103
+ inner_map = (the_aperture * 255) > 16
1104
+
1105
+ # Normalize contrast within aperture, common mean of 127.5
1106
+ WTMN = np.zeros((num_faces, size[0], size[1]))
1107
+ for i, im in enumerate(warped_to_mean):
1108
+ nim = contrast_stretch(im, inner_locs=inner_map, type="mean_127")
1109
+ out_png = output_dir + os.path.sep + "N" + str(i) + ".png"
1110
+ # cv2.imwrite(out_png, nim)
1111
+ PilImage.fromarray(nim).save(out_png)
1112
+ WTMN[i, :, :] = nim
1113
+
1114
+ # Mean of warped and normalized faces, and save to file
1115
+ AverageOfMorphs = contrast_stretch(WTMN.mean(axis=0), type="max")
1116
+ out_png = output_dir + os.path.sep + "N-mean.png"
1117
+ # cv2.imwrite(out_png, AverageOfMorphs)
1118
+ PilImage.fromarray(AverageOfMorphs).save(out_png)
1119
+
1120
+ return original_images, WTMN
1121
+
1122
+
1123
+ def morph_between_two_faces(source_dir, do_these, num_morphs, file_prefix='',
1124
+ file_postfix='jpg', new_dir = "morphed",
1125
+ weight_texture=True):
1126
+ assert len(do_these) == 2
1127
+
1128
+ output_dir = clone_directory_tree(source_dir, new_dir=new_dir,
1129
+ FilePrefix=file_prefix,
1130
+ FilePostfix=file_postfix)
1131
+
1132
+ landmark_features, files = get_landmark_features(source_dir, output_dir)
1133
+ lms = np.array(landmark_features['AllSubjectsLandmarksDict'])
1134
+ SF = files[0]
1135
+
1136
+ # select 2 faces in list do_these
1137
+ lms = lms[do_these, :]
1138
+ source_files = [SF[i] for i in do_these]
1139
+
1140
+ # Image size and number of images
1141
+ # size = cv2.imread(source_files[0]).shape[0:2]
1142
+ size = np.array(PilImage.open(source_files[0]).convert("L")).shape[0:2]
1143
+ num_faces = 2
1144
+
1145
+ # # Points for all faces, mean face, and center of all landmarks.
1146
+ # ML = lms.mean(axis=0)
1147
+ # bx = ML[::2]
1148
+ # by = ML[1::2]
1149
+ # base = (bx, by)
1150
+
1151
+ original_images = np.zeros((num_faces, size[0], size[1]))
1152
+ F, L = [], []
1153
+ for i, of in enumerate(source_files):
1154
+ # image = cv2.imread(of)
1155
+ # im = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
1156
+ im = np.array(PilImage.open(of).convert("L"))
1157
+ im -= im.min()
1158
+ F.append(im / im.max())
1159
+
1160
+ tx = lms[i, ::2]
1161
+ ty = lms[i, 1::2]
1162
+ target = (tx, ty)
1163
+ L.append(np.transpose(np.array(target))) # point by [x,y]
1164
+
1165
+ p = [i/(num_morphs-1) for i in range(num_morphs)]
1166
+ face_array = np.zeros((num_morphs, F[0].shape[0], F[0].shape[1]))
1167
+ for i in range(num_morphs):
1168
+ N = p[i]*L[0] + (1-p[i])*L[1]
1169
+ F0, tri, inpix, fwdwarpix = pawarp(F[0], N, L[0], interp='bilin')
1170
+ F1, tri, inpix, fwdwarpix = pawarp(F[1], N, L[1], interp='bilin')
1171
+ if weight_texture:
1172
+ M = p[i]*F0 + (1-p[i])*F1
1173
+ else:
1174
+ M = (F0 + F1) / 2
1175
+ face_array[i, :, :] = M
1176
+
1177
+ # From face A to B
1178
+ face_array =np.flip(face_array, axis=0)
1179
+
1180
+ # Write to morphed faces to file
1181
+ if not os.path.isdir(output_dir):
1182
+ os.mkdir(output_dir)
1183
+ for i, im in enumerate(face_array):
1184
+ sim = (im * 255).astype("uint8")
1185
+ out_png = output_dir + os.path.sep + "P" + str(i) + ".png"
1186
+ # cv2.imwrite(out_png, sim)
1187
+ PilImage.fromarray(sim).save(out_png)
1188
+
1189
+ # Estimate landmarks.
1190
+ get_landmarks(output_dir, "P", "png")
1191
+
1192
+ # Get aperture for inner-face
1193
+ the_aperture = place_aperture(output_dir, no_save=True)
1194
+ inner_map = (the_aperture * 255) > 16
1195
+
1196
+ # Normalize contrast within aperture, common mean of 127.5
1197
+ WTMN = np.zeros((num_morphs, size[0], size[1]))
1198
+ for i, im in enumerate(face_array):
1199
+ im[im>1] = 1
1200
+ im[im<0] = 0
1201
+ nim = contrast_stretch(im, inner_locs=inner_map, type="mean_127")
1202
+ out_png = output_dir + os.path.sep + "N" + str(i) + ".png"
1203
+ # cv2.imwrite(out_png, nim)
1204
+ PilImage.fromarray(nim).save(out_png)
1205
+ WTMN[i, :, :] = nim
1206
+
1207
+ return WTMN, p, output_dir
1208
+ # -----------------------------------------------------------------------------
1209
+ # -----------------------------------------------------------------------------