idvpackage 3.0.10__py3-none-any.whl → 3.0.12__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.
idvpackage/common.py CHANGED
@@ -1,18 +1,11 @@
1
- import re
2
- from datetime import datetime
3
- from itertools import permutations
1
+
4
2
  import cv2
5
3
  import numpy as np
6
4
  from PIL import Image
7
- import io
8
- from google.cloud import vision_v1
9
- import tempfile
10
- import os
11
- from io import BytesIO
12
5
  import logging
13
6
  import base64
14
7
  from concurrent.futures import ThreadPoolExecutor, as_completed
15
- from scipy.spatial.distance import cosine
8
+
16
9
 
17
10
  # Global variables to store lazily loaded modules
18
11
  _deepface = None
@@ -34,553 +27,8 @@ def get_face_recognition():
34
27
  _face_recognition = face_recognition
35
28
  return _face_recognition
36
29
 
37
- def func_common_dates( extract_no_space):
38
- dob = ''
39
- expiry_date = ''
40
- try:
41
- matches = re.findall(r'\d{2}/\d{2}/\d{4}', extract_no_space)
42
- y1 = matches[0][-4:]
43
- y2 = matches[1][-4:]
44
- if int(y1) < int(y2):
45
- dob = matches[0]
46
- expiry_date = matches[1]
47
- else:
48
- dob = matches[1]
49
- expiry_date = matches[0]
50
- except:
51
- dob = ''
52
- expiry_date = ''
53
-
54
- return dob, expiry_date
55
-
56
- def convert_dob(input_date):
57
- day = input_date[4:6]
58
- month = input_date[2:4]
59
- year = input_date[0:2]
60
-
61
- current_year = datetime.now().year
62
- current_century = current_year // 100
63
- current_year_last_two_digits = current_year % 100
64
-
65
- century = current_century
66
- # If the given year is greater than the last two digits of the current year, assume last century
67
- if int(year) > current_year_last_two_digits:
68
- century = current_century - 1
69
-
70
- final_date = f"{day}/{month}/{century}{year}"
71
-
72
- return final_date
73
-
74
- def func_dob( extract):
75
- extract_no_space = extract.replace(' ','')
76
- dob, expiry_date = func_common_dates(extract_no_space)
77
- if dob == '':
78
- match_dob = re.findall(r'\d{7}(?:M|F)\d', extract_no_space)
79
- for i in match_dob:
80
- # print(i)
81
- raw_dob = i[0:6]
82
- # print(raw_dob)
83
- year = str(datetime.today().year)[2:4]
84
- temp = '19'
85
- if int(raw_dob[0:2]) > int(year):
86
- temp = '19'
87
- else:
88
- temp = '20'
89
- dob = raw_dob[4:6]+'/'+raw_dob[2:4]+'/'+temp+raw_dob[0:2]
90
- try:
91
- dt_obj = datetime.strptime(dob, '%d/%m/%Y')
92
- break
93
- except:
94
- # print(f'invalid date {dob}')
95
- dob = ''
96
- else:
97
- pattern = r"\b(\d{14}).*?\b"
98
-
99
- new_dob_match = re.search(pattern, extract_no_space)
100
-
101
- if new_dob_match:
102
- new_dob = new_dob_match.group(1)
103
- new_dob = new_dob[:7]
104
- dob = convert_dob(new_dob)
105
-
106
- return dob
107
-
108
- def func_expiry_date( extract):
109
- extract_no_space = extract.replace(' ','')
110
- dob, expiry_date = func_common_dates(extract_no_space)
111
- if expiry_date == '':
112
- match_doe = re.findall(r'\d{7}[A-Z]{2,3}', extract_no_space)
113
- for i in match_doe:
114
-
115
- raw_doe = i[0:6]
116
- # print(raw_doe)
117
- expiry_date = raw_doe[4:6]+'/'+raw_doe[2:4]+'/20'+raw_doe[0:2]
118
- try:
119
- dt_obj = datetime.strptime(expiry_date, '%d/%m/%Y')
120
- break
121
- except:
122
-
123
- expiry_date = ''
124
-
125
- return expiry_date
126
-
127
- def convert_expiry_date(input_date):
128
- day = input_date[4:6]
129
- month = input_date[2:4]
130
- year = input_date[0:2]
131
-
132
- current_year = datetime.now().year
133
- current_century = current_year // 100
134
- current_year_last_two_digits = current_year % 100
135
- century = current_century
136
-
137
- if int(year) <= current_year_last_two_digits:
138
- century = current_century
139
- else:
140
- century = current_century
141
- final_date = f"{day}/{month}/{century}{year}"
142
-
143
- return final_date
144
-
145
- def extract_first_9_digits(string_input):
146
- match = re.search(r'\b\d{9}\b', string_input)
147
- if match:
148
- sequence = match.group(0)
149
- return sequence
150
- else:
151
- return ""
152
-
153
- def func_card_number( extract):
154
- extract_no_space = extract.replace(' ','')
155
- try:
156
- card_number = re.search(r'\d{9}', extract_no_space).group()
157
- except:
158
- card_number= extract_first_9_digits(extract_no_space)
159
-
160
- return card_number
161
-
162
-
163
- def count_digits_after_pattern(s):
164
- """
165
- Counts the number of digits that come after a specified pattern in a string.
166
-
167
- Parameters:
168
- s (str): The input string.
169
- pattern (str): The pattern to search for.
170
-
171
- Returns:
172
- int: The count of digits that come after the pattern.
173
- """
174
- # Construct the regex pattern to find the specified pattern followed by digits
175
- pattern = "<<<<"
176
- regex_pattern = re.compile(f"{re.escape(pattern)}(\d+)")
177
-
178
- # Search for the pattern in the string
179
- match = regex_pattern.search(s)
180
-
181
- # If a match is found, count the digits
182
- if match:
183
- digits_after_pattern = match.group(1)
184
- return len(digits_after_pattern)
185
- else:
186
- return 0 # Pattern not found
187
-
188
- def remove_special_characters1(string):
189
- # This pattern matches any character that is not a letter, digit, or space
190
- #pattern = r'[^a-zA-Z0-9<\s]'
191
- pattern = r'[^a-zA-Z0-9<>]'
192
- return re.sub(pattern, '', string)
193
-
194
- def remove_special_characters_mrz2(string):
195
- # This pattern matches any character that is not a letter, digit, or space
196
- pattern = r'[^a-zA-Z0-9\s]'
197
- return re.sub(pattern, '', string)
198
-
199
- def validate_string(s):
200
- """
201
- Validates if the string follows the specific structure.
202
-
203
- Structure: 7 digits, followed by 'M' or 'F', then 7 digits again,
204
- then 3 uppercase letters, and ending with 1 digit.
205
-
206
- Parameters:
207
- s (str): The string to be validated.
208
-
209
- Returns:
210
- bool: True if the string follows the structure, False otherwise.
211
- """
212
- pattern = r'^\d{7}[MF]\d{7}[A-Z]{3}\d$'
213
- return bool(re.match(pattern, s))
214
-
215
-
216
- def remove_special_characters2(string):
217
- # This pattern matches any character that is not a letter, digit, or space
218
- pattern = r'[^a-zA-Z0-9\s]'
219
- return re.sub(pattern, ' ', string)
220
-
221
- def func_name(extract):
222
- bio_data = extract[-40:]
223
- breakup = bio_data.split('\n')
224
- if len(breakup) == 2:
225
- name_extract = breakup.pop(0)
226
- else:
227
- country_extract = breakup.pop(0).replace(" ","")
228
- name_extract = breakup.pop(0)
229
-
230
- # Check the alphanumeric nature of name_extract
231
- if not name_extract.isupper():
232
- name_extract = breakup.pop(0)
233
-
234
- try:
235
- name = name_extract.replace("<", " ").replace(">", " ").replace(".", " ").replace(":", " ").replace('«','').strip()
236
- name = ' '.join(name.split())
237
- name = name.replace("0", "O") # special case fix
238
- except:
239
- name = ""
240
-
241
- return name
242
-
243
- def func_nationality( extract):
244
- extract_no_space = extract.replace(' ','')
245
- try:
246
- pattern = r'\d{5}[A-Z]{3}|\d{5}[A-Z]{2}'
247
-
248
- m = re.findall(pattern, extract_no_space)
249
- country = m[len(m)-1].replace("<", "")[5:]
250
- except:
251
- country = ""
252
-
253
- if country == '':
254
- try:
255
- pattern = r'\d{2}[a-z][A-Z]{2}'
256
-
257
- m = re.findall(pattern, extract_no_space)
258
- country = m[len(m)-1].replace("<", "")[2:].upper()
259
- except:
260
- country = ""
261
-
262
- return country
263
-
264
- def clean_string(input_string):
265
- cleaned_string = re.sub(r'[^\w\s]', ' ', input_string)
266
- return cleaned_string.strip()
267
-
268
- def count_digits(element):
269
- digits = [char for char in element if char.isdigit()]
270
- return len(digits)
271
-
272
- def find_and_slice_number(input_number, digits):
273
- # Generate all possible permutations of the digits
274
- perms = [''.join(p) for p in permutations(digits)]
275
-
276
- # Initialize variables to keep track of the found pattern and its index
277
- found_pattern = None
278
- found_index = -1
279
-
280
- # Search for any permutation of the digits in the input_number
281
- for perm in perms:
282
- found_index = input_number.find(perm)
283
- if found_index != -1:
284
- found_pattern = perm
285
- break
286
-
287
- # If a pattern is found, slice the number accordingly
288
- if found_pattern:
289
- if found_index > len(input_number) - found_index - len(found_pattern):
290
- # Slice to the left
291
- sliced_number = input_number[:found_index + len(found_pattern)]
292
- else:
293
- # Slice to the right
294
- sliced_number = input_number[found_index:]
295
-
296
- return sliced_number
297
- else:
298
- return ''
299
-
300
- def func_id_number(extract,dob):
301
-
302
- try:
303
- p = "784" + "\d{12}"
304
- id_re = re.search(p, clean_string(extract).replace(' ',''))
305
- id_number = id_re.group()
306
- except:
307
-
308
- try:
309
- pattern = r'\d{15,}'
310
- digits = '784'
311
- matches = re.findall(pattern, clean_string(extract).replace(' ',''))
312
- input_number = matches[0]
313
- dob=dob[-4:]
314
- id_number='784'+dob+find_and_slice_number(input_number, digits)[:8]
315
-
316
- except:
317
- id_number = ''
318
-
319
- return id_number
320
-
321
-
322
- # #year = dob[-4:]
323
- # p = "784" + "\d{12}"
324
- # id_re = re.search(p, clean_string(data).replace(' ',''))
325
- # id_number = id_re.group()
326
30
 
327
31
 
328
-
329
- def convert_to_date(date_str):
330
- year = '19' + date_str[:2] if int(date_str[:2]) >= 50 else '20' + date_str[:2]
331
- month = date_str[2:4]
332
- day = date_str[4:6]
333
- return f"{day}/{month}/{year}"
334
-
335
- def check_valid_date(date_str, format="%d/%m/%Y"):
336
- try:
337
- datetime.strptime(date_str, format)
338
- return True
339
- except ValueError:
340
- return False
341
-
342
-
343
- def find_expiry_date(original_text,mrz2):
344
-
345
- dates = re.findall(r'\b\d{2}/\d{2}/\d{4}\b', original_text)
346
- expiry_date = ''
347
-
348
- if len(dates) == 2:
349
-
350
- date1 = datetime.strptime(dates[0], '%d/%m/%Y')
351
- date2 = datetime.strptime(dates[1], '%d/%m/%Y')
352
-
353
- if date2 < date1:
354
- expiry_date = dates[0]
355
- elif date2 > date1:
356
- expiry_date = dates[1]
357
-
358
- elif mrz2:
359
- match_expiry_date = re.search(r'[A-Za-z](\d+)', mrz2)
360
- if match_expiry_date:
361
- expiry_date = match_expiry_date.group(1)[:6]
362
- expiry_date = convert_to_date(expiry_date)
363
-
364
-
365
- if not check_valid_date(expiry_date):
366
- expiry_date=''
367
- return expiry_date
368
-
369
- def find_dob(original_text,mrz2):
370
-
371
- dates = re.findall(r'\b\d{2}/\d{2}/\d{4}\b', original_text)
372
- dob = ''
373
-
374
- if len(dates) == 2:
375
- date1 = datetime.strptime(dates[0], '%d/%m/%Y')
376
- date2 = datetime.strptime(dates[1], '%d/%m/%Y')
377
-
378
- if date2 < date1:
379
- dob = dates[1]
380
- elif date2 > date1:
381
- dob = dates[0]
382
-
383
- elif mrz2:
384
- match_dob = re.search(r'(\d+)[A-Za-z]', mrz2)
385
- if match_dob:
386
- dob = match_dob.group(1)[:6]
387
- dob=convert_to_date(dob)
388
-
389
- if not check_valid_date(dob):
390
- dob=''
391
- return dob
392
-
393
-
394
- def convert_date_format(date_str):
395
- # Parse the date from DD/MM/YYYY format
396
- date_obj = datetime.strptime(date_str, '%d/%m/%Y')
397
- # Convert it to YYYY-MM-DD format
398
- formatted_date = date_obj.strftime('%Y-%m-%d')
399
- return formatted_date
400
-
401
-
402
- def convert_gender(gender_char):
403
- if gender_char.lower() == 'm':
404
- return 'Male'
405
- elif gender_char.lower() == 'f':
406
- return 'Female'
407
- else:
408
- return ''
409
-
410
-
411
- def compute_ela_cv(orig_img, quality):
412
- SCALE = 15
413
- orig_img = cv2.cvtColor(orig_img, cv2.COLOR_BGR2RGB)
414
-
415
- with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as temp_file:
416
- temp_filename = temp_file.name
417
-
418
- cv2.imwrite(temp_filename, orig_img, [cv2.IMWRITE_JPEG_QUALITY, quality])
419
- # read compressed image
420
- compressed_img = cv2.imread(temp_filename)
421
-
422
- # get absolute difference between img1 and img2 and multiply by scale
423
- diff = SCALE * cv2.absdiff(orig_img, compressed_img)
424
-
425
- # delete the temporary file
426
- if os.path.exists(temp_filename):
427
- os.remove(temp_filename)
428
-
429
- return diff
430
-
431
-
432
- def calculate_error_difference(orig_img, country=None):
433
- if isinstance(orig_img, Image.Image):
434
- orig_img = np.array(orig_img)
435
-
436
- if np.any(orig_img):
437
- ela_val = compute_ela_cv(orig_img, quality=94)
438
- diff_avg = ela_val.mean()
439
-
440
- print(f"DIFFERENCE: {diff_avg}")
441
- if country == 'UAE':
442
- if diff_avg <= 25:
443
- label = 'Genuine'
444
- else:
445
- label = 'Tampered'
446
- else:
447
- if diff_avg <= 10.5:
448
- label = 'Genuine'
449
- else:
450
- label = 'Tampered'
451
-
452
- return label
453
- else:
454
- print(f"ISSUE")
455
- return 'Genuine'
456
-
457
-
458
- def eastern_arabic_to_english(eastern_numeral):
459
- try:
460
- arabic_to_english_map = {
461
- '٠': '0', '۰': '0',
462
- '١': '1', '۱': '1',
463
- '٢': '2', '۲': '2',
464
- '٣': '3', '۳': '3',
465
- '٤': '4', '۴': '4',
466
- '٥': '5', '۵': '5',
467
- '٦': '6', '۶': '6',
468
- '٧': '7', '۷': '7',
469
- '٨': '8', '۸': '8',
470
- '٩': '9', '۹': '9',
471
- '/': '/'
472
- }
473
-
474
- english_numeral = ''.join([arabic_to_english_map[char] if char in arabic_to_english_map else char for char in eastern_numeral])
475
-
476
- return english_numeral
477
-
478
- except:
479
- return eastern_numeral
480
-
481
- def english_to_eastern_arabic(english_numeral):
482
- try:
483
- english_to_arabic_map = {
484
- '0': '٠',
485
- '1': '١',
486
- '2': '٢',
487
- '3': '٣',
488
- '4': '٤',
489
- '5': '٥',
490
- '6': '٦',
491
- '7': '٧',
492
- '8': '٨',
493
- '9': '٩'
494
- }
495
-
496
- eastern_arabic_numeral = ''.join([english_to_arabic_map[char] if char in english_to_arabic_map else char for char in english_numeral])
497
-
498
- return eastern_arabic_numeral
499
-
500
- except Exception as e:
501
- return str(e)
502
-
503
- def crop_third_part(img):
504
- width, height = img.size
505
- part_height = height // 3
506
- third_part = img.crop((0, 2 * part_height, width, height))
507
- # third_part.save("/Users/fahadpatel/Pictures/thirdpart.jpg")
508
- return third_part
509
-
510
- def extract_text_from_image_data(client, image):
511
- """Detects text in the file."""
512
-
513
- # with io.BytesIO() as output:
514
- # image.save(output, format="PNG")
515
- # content = output.getvalue()
516
-
517
- compressed_image = BytesIO()
518
- image.save(compressed_image, format="JPEG", quality=100, optimize=True)
519
- content = compressed_image.getvalue()
520
-
521
- image = vision_v1.types.Image(content=content)
522
-
523
- response = client.text_detection(image=image)
524
- texts = response.text_annotations
525
-
526
- return texts[0].description
527
-
528
- def detect_id_card_uae(client, image_data, id_text, part=None):
529
- if id_text:
530
- vertices = id_text[0].bounding_poly.vertices
531
- left = vertices[0].x
532
- top = vertices[0].y
533
- right = vertices[2].x
534
- bottom = vertices[2].y
535
-
536
- padding = 30
537
- left -= padding
538
- top -= padding
539
- right += padding
540
- bottom += padding
541
-
542
- # img = image_data
543
-
544
- with Image.open(io.BytesIO(image_data)) as img:
545
- id_card = img.crop((max(0, left), max(0, top), right, bottom))
546
- width, height = id_card.size
547
- if width < height:
548
- id_card = id_card.rotate(90, expand=True)
549
-
550
- tampered_result = calculate_error_difference(id_card, country = 'UAE')
551
-
552
- part_text = id_text[0].description
553
- if part == 'third':
554
- part_img = crop_third_part(id_card)
555
- part_text = extract_text_from_image_data(client, part_img)
556
-
557
- return tampered_result, part_text
558
-
559
- def rotate_image(img):
560
- from skimage.transform import radon
561
-
562
- img_array = np.array(img)
563
-
564
- if len(img_array.shape) == 2:
565
- gray = img_array
566
- else:
567
- gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
568
-
569
- h, w = gray.shape
570
- if w > 640:
571
- gray = cv2.resize(gray, (640, int((h / w) * 640)))
572
- gray = gray - np.mean(gray)
573
- sinogram = radon(gray)
574
- r = np.array([np.sqrt(np.mean(np.abs(line) ** 2)) for line in sinogram.transpose()])
575
- rotation = np.argmax(r)
576
- angle = round(abs(90 - rotation) + 0.5)
577
-
578
- if abs(angle) > 5:
579
- rotated_img = img.rotate(angle, expand=True)
580
- return rotated_img
581
-
582
- return img
583
-
584
32
  def deepface_to_dlib_rgb(face):
585
33
  """
586
34
  Convert DeepFace face output to uint8 RGB for face_recognition
@@ -632,7 +80,8 @@ def load_and_process_image_deepface_topup(image_input):
632
80
  return face_objs, img_to_process, confidence
633
81
 
634
82
  # Clear memory if no face found
635
- del img_to_process
83
+ if 'img_to_process' in locals():
84
+ del img_to_process
636
85
  return None, None, 0
637
86
  except Exception as e:
638
87
  logging.info(f"Error processing angle {angle}: {e}")
@@ -775,10 +224,6 @@ def load_and_process_image_deepface_topup(image_input):
775
224
  del processed_image
776
225
 
777
226
 
778
-
779
-
780
-
781
-
782
227
  def load_and_process_image_deepface(image_input, country=None):
783
228
  DeepFace = get_deepface()
784
229
  face_recognition = get_face_recognition()
@@ -884,7 +329,6 @@ def load_and_process_image_deepface(image_input, country=None):
884
329
  print("Empty image input")
885
330
  return [], []
886
331
 
887
-
888
332
  # -------------------- ANGLE LOOP (NO THREADS) --------------------
889
333
 
890
334
  best_face_objs = None
@@ -907,10 +351,6 @@ def load_and_process_image_deepface(image_input, country=None):
907
351
  break # Exit loop on first valid detection
908
352
 
909
353
  # Keep best fallback (just in case)
910
-
911
-
912
- if country == "QAT":
913
- return 0, 0
914
354
 
915
355
  if best_face_objs is None or best_confidence < CONFIDENCE_THRESHOLD:
916
356
  print(f"No valid face found (threshold={CONFIDENCE_THRESHOLD})")
@@ -967,403 +407,3 @@ def extract_face_and_compute_similarity(front_face_locations, front_face_encodin
967
407
 
968
408
  return min(1, similarity_score)
969
409
 
970
- def load_and_process_image_deepface_all_orientations(image_input):
971
- """Similar to load_and_process_image_deepface but processes all orientations to find best confidence"""
972
- DeepFace = get_deepface() # Only load when needed
973
- face_recognition = get_face_recognition() # Only load when needed
974
- def process_angle(img, angle):
975
- try:
976
- # Create a view instead of copy when possible
977
- if angle != 0:
978
- # Minimize memory usage during rotation
979
- with np.errstate(all='ignore'):
980
- img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
981
- img_pil = Image.fromarray(img_rgb)
982
- # Use existing buffer when possible
983
- rotated = np.ascontiguousarray(img_pil.rotate(angle, expand=True))
984
- img_to_process = cv2.cvtColor(rotated, cv2.COLOR_RGB2BGR)
985
- # Clear references to intermediate arrays
986
- del img_rgb, img_pil, rotated
987
- else:
988
- img_to_process = img
989
-
990
- # Extract faces with memory optimization
991
- face_objs = DeepFace.extract_faces(
992
- img_to_process,
993
- detector_backend='fastmtcnn',
994
- enforce_detection=False,
995
- align=True
996
- )
997
-
998
- if face_objs and len(face_objs) > 0:
999
- confidence = face_objs[0].get('confidence', 0)
1000
- # print(f"Face detected at {angle} degrees with confidence {confidence}")
1001
-
1002
- return face_objs, img_to_process, confidence
1003
-
1004
- # Clear memory if no face found
1005
- del img_to_process
1006
- return None, None, 0
1007
- except Exception as e:
1008
- print(f"Error processing angle {angle}: {e}")
1009
- return None, None, 0
1010
- finally:
1011
- # Ensure memory is cleared
1012
- if 'img_to_process' in locals():
1013
- del img_to_process
1014
-
1015
- try:
1016
- # Process input image efficiently
1017
- if isinstance(image_input, np.ndarray):
1018
- # Use view when possible
1019
- image = np.ascontiguousarray(image_input)
1020
- if image.dtype != np.uint8:
1021
- image = image.astype(np.uint8, copy=False)
1022
- elif isinstance(image_input, str):
1023
- # Decode base64 directly to numpy array
1024
- image_data = base64.b64decode(image_input)
1025
- image = cv2.imdecode(np.frombuffer(image_data, np.uint8), cv2.IMREAD_COLOR)
1026
- del image_data # Clear decoded data
1027
- else:
1028
- print(f"Unexpected input type: {type(image_input)}")
1029
- return [], []
1030
-
1031
- if image is None or image.size == 0:
1032
- print("Empty image")
1033
- return [], []
1034
-
1035
- # Process all angles in parallel
1036
- angles = [0, 90, 180, 270]
1037
- best_confidence = 0
1038
- best_face_objs = None
1039
- best_image = None
1040
-
1041
- # Use context manager to ensure proper cleanup
1042
- with ThreadPoolExecutor(max_workers=4) as executor:
1043
- # Submit tasks
1044
- futures = {
1045
- executor.submit(process_angle, image, angle): angle
1046
- for angle in angles
1047
- }
1048
-
1049
- try:
1050
- for future in as_completed(futures):
1051
- face_objs, processed_image, confidence = future.result()
1052
- if face_objs is not None and confidence > best_confidence:
1053
- best_confidence = confidence
1054
- best_face_objs = face_objs
1055
- best_image = processed_image
1056
- finally:
1057
- # Ensure all futures are cancelled
1058
- for future in futures:
1059
- if not future.done():
1060
- future.cancel()
1061
-
1062
- if best_face_objs is None:
1063
- print("No faces detected with fastmtcnn at any angle")
1064
- return [], []
1065
-
1066
- # print(f"Using best detected face with confidence {best_confidence}")
1067
- try:
1068
- biggest_face = max(best_face_objs, key=lambda face: face['facial_area']['w'] * face['facial_area']['h'])
1069
- facial_area = biggest_face['facial_area']
1070
- x, y, w, h = facial_area['x'], facial_area['y'], facial_area['w'], facial_area['h']
1071
-
1072
- # Minimize memory during final processing
1073
- image_rgb = cv2.cvtColor(best_image, cv2.COLOR_BGR2RGB)
1074
- face_locations = [(y, x + w, y + h, x)]
1075
- face_encodings = face_recognition.face_encodings(image_rgb, face_locations)
1076
-
1077
- if face_encodings:
1078
- return face_locations, face_encodings
1079
-
1080
- print("Failed to extract face encodings")
1081
- return [], []
1082
- finally:
1083
- # Clear final processing memory
1084
- del image_rgb, best_image, best_face_objs
1085
-
1086
- except Exception as e:
1087
- print(f"Error in face detection: {e}")
1088
- return [], []
1089
- finally:
1090
- # Ensure main image is cleared
1091
- if 'image' in locals():
1092
- del image
1093
-
1094
-
1095
- def load_and_process_image_deepface_all_orientations(image_input):
1096
- """Similar to load_and_process_image_deepface but processes all orientations to find best confidence"""
1097
- DeepFace = get_deepface() # Only load when needed
1098
- face_recognition = get_face_recognition() # Only load when needed
1099
- def process_angle(img, angle):
1100
- try:
1101
- # Create a view instead of copy when possible
1102
- if angle != 0:
1103
- # Minimize memory usage during rotation
1104
- with np.errstate(all='ignore'):
1105
- img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
1106
- img_pil = Image.fromarray(img_rgb)
1107
- # Use existing buffer when possible
1108
- rotated = np.ascontiguousarray(img_pil.rotate(angle, expand=True))
1109
- img_to_process = cv2.cvtColor(rotated, cv2.COLOR_RGB2BGR)
1110
- # Clear references to intermediate arrays
1111
- del img_rgb, img_pil, rotated
1112
- else:
1113
- img_to_process = img
1114
-
1115
- # Extract faces with memory optimization
1116
- face_objs = DeepFace.extract_faces(
1117
- img_to_process,
1118
- detector_backend='fastmtcnn',
1119
- enforce_detection=False,
1120
- align=True
1121
- )
1122
-
1123
- if face_objs and len(face_objs) > 0:
1124
- confidence = face_objs[0].get('confidence', 0)
1125
- # print(f"Face detected at {angle} degrees with confidence {confidence}")
1126
-
1127
- return face_objs, img_to_process, confidence
1128
-
1129
- # Clear memory if no face found
1130
- del img_to_process
1131
- return None, None, 0
1132
- except Exception as e:
1133
- print(f"Error processing angle {angle}: {e}")
1134
- return None, None, 0
1135
- finally:
1136
- # Ensure memory is cleared
1137
- if 'img_to_process' in locals():
1138
- del img_to_process
1139
-
1140
- try:
1141
- # Process input image efficiently
1142
- if isinstance(image_input, np.ndarray):
1143
- # Use view when possible
1144
- image = np.ascontiguousarray(image_input)
1145
- if image.dtype != np.uint8:
1146
- image = image.astype(np.uint8, copy=False)
1147
- elif isinstance(image_input, str):
1148
- # Decode base64 directly to numpy array
1149
- image_data = base64.b64decode(image_input)
1150
- image = cv2.imdecode(np.frombuffer(image_data, np.uint8), cv2.IMREAD_COLOR)
1151
- del image_data # Clear decoded data
1152
- else:
1153
- print(f"Unexpected input type: {type(image_input)}")
1154
- return [], []
1155
-
1156
- if image is None or image.size == 0:
1157
- print("Empty image")
1158
- return [], []
1159
-
1160
- # Process all angles in parallel
1161
- angles = [0, 90, 180, 270]
1162
- best_confidence = 0
1163
- best_face_objs = None
1164
- best_image = None
1165
-
1166
- # Use context manager to ensure proper cleanup
1167
- with ThreadPoolExecutor(max_workers=4) as executor:
1168
- # Submit tasks
1169
- futures = {
1170
- executor.submit(process_angle, image, angle): angle
1171
- for angle in angles
1172
- }
1173
-
1174
- try:
1175
- for future in as_completed(futures):
1176
- face_objs, processed_image, confidence = future.result()
1177
- if face_objs is not None and confidence > best_confidence:
1178
- best_confidence = confidence
1179
- best_face_objs = face_objs
1180
- best_image = processed_image
1181
- finally:
1182
- # Ensure all futures are cancelled
1183
- for future in futures:
1184
- if not future.done():
1185
- future.cancel()
1186
-
1187
- if best_face_objs is None:
1188
- print("No faces detected with fastmtcnn at any angle")
1189
- return [], []
1190
-
1191
- # print(f"Using best detected face with confidence {best_confidence}")
1192
- try:
1193
- biggest_face = max(best_face_objs, key=lambda face: face['facial_area']['w'] * face['facial_area']['h'])
1194
- facial_area = biggest_face['facial_area']
1195
- x, y, w, h = facial_area['x'], facial_area['y'], facial_area['w'], facial_area['h']
1196
-
1197
- # Minimize memory during final processing
1198
- image_rgb = cv2.cvtColor(best_image, cv2.COLOR_BGR2RGB)
1199
- face_locations = [(y, x + w, y + h, x)]
1200
- face_encodings = face_recognition.face_encodings(image_rgb, face_locations)
1201
-
1202
- if face_encodings:
1203
- return face_locations, face_encodings
1204
-
1205
- print("Failed to extract face encodings")
1206
- return [], []
1207
- finally:
1208
- # Clear final processing memory
1209
- del image_rgb, best_image, best_face_objs
1210
-
1211
- except Exception as e:
1212
- print(f"Error in face detection: {e}")
1213
- return [], []
1214
- finally:
1215
- # Ensure main image is cleared
1216
- if 'image' in locals():
1217
- del image
1218
-
1219
- def get_facial_encodings_deepface_irq(image, country: str, model_name="ArcFace", detector_backend="retinaface"):
1220
- logging.info(f"Type: {type(image)} | Shape: {image.shape} | Dtype: {image.dtype}")
1221
-
1222
- from deepface import DeepFace
1223
- import numpy as np
1224
-
1225
- if image.ndim == 3 and image.shape[2] == 4:
1226
- # Convert RGBA to BGR (DeepFace expects BGR or RGB)
1227
- # OpenCV uses BGR by default
1228
- image = cv2.cvtColor(image, cv2.COLOR_BGRA2BGR)
1229
- elif image.ndim == 2:
1230
- # grayscale to BGR
1231
- image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
1232
-
1233
- if image.dtype != np.uint8:
1234
- image = image.astype(np.uint8)
1235
-
1236
-
1237
- # DeepFace doesnt accepts 4 channel images (RGBA), so we convert to RGB or BGR
1238
- try:
1239
- logging.info(f"[DeepFace] Starting face extraction for {country} with model {model_name} and detector {detector_backend}")
1240
-
1241
- face_objs = DeepFace.represent(
1242
- img_path=image,
1243
- model_name=model_name,
1244
- detector_backend=detector_backend,
1245
- enforce_detection=False,
1246
- align=True
1247
- )
1248
-
1249
- logging.info(f"[DeepFace] Face extraction successful: {len(face_objs)} faces detected.")
1250
- # logging.info(f"[DeepFace] Face details: {face_objs}")
1251
- except Exception as e:
1252
- logging.error(f"[DeepFace] Face extraction failed: {e}")
1253
- return [],[]
1254
-
1255
- if not face_objs:
1256
- logging.warning("No faces detected.")
1257
- return [], []
1258
-
1259
- # Normalize to list
1260
- if isinstance(face_objs, dict):
1261
- face_objs = [face_objs]
1262
-
1263
- # Select face with largest area
1264
- def get_area(face):
1265
- fa = face.get("facial_area", {})
1266
- if "w" in fa and "h" in fa:
1267
- return fa["w"] * fa["h"]
1268
- elif {"x", "x2", "y", "y2"}.issubset(fa):
1269
- return (fa["x2"] - fa["x"]) * (fa["y2"] - fa["y"])
1270
- return 0
1271
-
1272
- largest_face = max(face_objs, key=get_area)
1273
- facial_area = largest_face.get("facial_area", {})
1274
- confidence = largest_face.get("face_confidence", 0.0)
1275
- embedding = np.array(largest_face["embedding"], dtype=np.float32)
1276
-
1277
- # Compute w, h from bounding box
1278
- w, h = 0, 0
1279
- if "w" in facial_area and "h" in facial_area:
1280
- w, h = facial_area["w"], facial_area["h"]
1281
- elif {"x", "x2", "y", "y2"}.issubset(facial_area): #other libraries, models returns x2,y2 instead of w,h
1282
- w = facial_area["x2"] - facial_area["x"]
1283
- h = facial_area["y2"] - facial_area["y"]
1284
-
1285
- # Country-specific rejection
1286
- if country.upper() == "SDN":
1287
- if w < 40 or h < 50:
1288
- logging.warning(f"Rejected: SDN face too small ({w}x{h}), minimum 40x50")
1289
- return [], []
1290
-
1291
- if confidence < 0.95:
1292
- logging.warning(f"Rejected: low face detection confidence ({confidence:.3f})")
1293
- return [], []
1294
-
1295
- facial_area_filtered = {}
1296
- x = facial_area.get("x", 0)
1297
- y = facial_area.get("y", 0)
1298
- w = facial_area.get("w", 0)
1299
- h = facial_area.get("h", 0)
1300
-
1301
- facial_area_filtered = [(x,y,w,h)]
1302
- return facial_area_filtered, [embedding]
1303
-
1304
-
1305
- def cosine_similarity(vec1, vec2):
1306
- # vec1 = np.array(vec1)
1307
- # vec2 = np.array(vec2)
1308
- #
1309
- # if vec1.shape != vec2.shape:
1310
- # raise ValueError(f"Shape mismatch: {vec1.shape} vs {vec2.shape}")
1311
- #
1312
- # similarity = dot(vec1, vec2) / (norm(vec1) * norm(vec2))
1313
- # return round(float(similarity), 4)
1314
- similarity = 1 - cosine(vec1, vec2)
1315
- similarity = round(similarity, 5)
1316
- similarity = min(1, similarity)
1317
- return similarity
1318
-
1319
- def extract_face_and_compute_cosine_similarity(selfie, front_face_locations, front_face_encodings):
1320
- try:
1321
- if selfie is None:
1322
- print("Error: Selfie image is None")
1323
- return 0
1324
-
1325
- # Ensure the input array is contiguous and in the correct format
1326
- if not selfie.flags['C_CONTIGUOUS']:
1327
- selfie = np.ascontiguousarray(selfie)
1328
-
1329
- # Convert array to uint8 if needed
1330
- if selfie.dtype != np.uint8:
1331
- if selfie.max() > 255:
1332
- selfie = (selfie / 256).astype(np.uint8)
1333
- else:
1334
- selfie = selfie.astype(np.uint8)
1335
-
1336
- # Try DeepFace first as it's generally more reliable
1337
- # start_time = time.time()
1338
- face_locations1, face_encodings1 = get_facial_encodings_deepface_irq(selfie, country='IRQ')
1339
- # end_time = time.time()
1340
-
1341
- if not face_locations1 or not face_encodings1:
1342
- print("No face detected in Selfie Video by DeepFace")
1343
- return 0
1344
-
1345
- # print(f"Face detection took {end_time - start_time:.3f} seconds")
1346
- face_locations2, face_encodings2 = front_face_locations, front_face_encodings
1347
-
1348
- if not face_encodings2.any():
1349
- print('No face detected in front ID')
1350
- return 0
1351
-
1352
- largest_face_index1 = face_locations1.index(
1353
- max(face_locations1, key=lambda loc: (loc[2] - loc[0]) * (loc[3] - loc[1])))
1354
- largest_face_index2 = face_locations2.index(
1355
- max(face_locations2, key=lambda loc: (loc[2] - loc[0]) * (loc[3] - loc[1])))
1356
-
1357
- face_encoding1 = face_encodings1[largest_face_index1]
1358
- face_encoding2 = face_encodings2[largest_face_index2]
1359
-
1360
- similarity_score = cosine_similarity(face_encoding1, face_encoding2)
1361
- # print(f"Calculated similarity score: {similarity_score}")
1362
-
1363
- return similarity_score
1364
-
1365
- except Exception as e:
1366
- print(f"Error in extract_face_and_compute_similarity: {e}")
1367
- import traceback
1368
- traceback.print_exc()
1369
- return 0