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