Spaces:
Running
Running
| # # # # import numpy as np | |
| # # # # import cv2 | |
| # # # # from sklearn.metrics.pairwise import cosine_similarity | |
| # # # # from scipy.stats import weibull_min | |
| # # # # from tensorflow.keras.models import load_model | |
| # # # # from tensorflow.keras.applications.resnet import preprocess_input | |
| # # # # # from config import MODEL_PATH, IMG_SIZE, COSINE_WEIGHT, COSINE_THRESHOLD, HYBRID_THRESHOLD | |
| # # # # from config import Config | |
| # # # # from gallery import load_gallery | |
| # # # # import os | |
| # # # # from huggingface_hub import hf_hub_download | |
| # # # # MODEL_REPO = "Omamaa12/iris-models" | |
| # # # # model_path = hf_hub_download( | |
| # # # # repo_id=MODEL_REPO, | |
| # # # # filename="resnet50_imagenet.h5", | |
| # # # # token=os.getenv("HF_TOKEN") | |
| # # # # ) | |
| # # # # base_model = load_model(model_path) | |
| # # # # # Load model | |
| # # # # # base_model = load_model( Config.MODEL_PATH) | |
| # # # # # Load gallery | |
| # # # # gallery_data = load_gallery() | |
| # # # # gallery = gallery_data["gallery"] | |
| # # # # weibull_models = gallery_data["weibull_models"] | |
| # # # # mean_all = gallery_data["mean_all"] | |
| # # # # std_all = gallery_data["std_all"] | |
| # # # # # def embed_image(path): | |
| # # # # # img = cv2.imread(path) | |
| # # # # # h, w = img.shape[:2] | |
| # # # # # crop = img[h//4:3*h//4, w//4:3*w//4] | |
| # # # # # img = cv2.resize(crop, Config.IMG_SIZE) | |
| # # # # # if len(img.shape) == 2 or img.shape[2] == 1: | |
| # # # # # img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) | |
| # # # # # img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) | |
| # # # # # img_prep = preprocess_input(np.expand_dims(img.astype(np.float32), axis=0)) | |
| # # # # # emb = base_model.predict(img_prep, verbose=0).flatten() | |
| # # # # # emb = (emb - mean_all) / std_all | |
| # # # # # emb = emb / (np.linalg.norm(emb)+1e-10) | |
| # # # # # return emb | |
| # # # # # def predict_hybrid(vec): | |
| # # # # # sims = {c: cosine_similarity(emb, vec.reshape(1,-1)).max() for c, emb in gallery.items()} | |
| # # # # # pred_class = max(sims, key=sims.get) | |
| # # # # # cosine_score = sims[pred_class] | |
| # # # # # if cosine_score < Config.COSINE_THRESHOLD: | |
| # # # # # return "unknown", cosine_score | |
| # # # # # mean_vec = gallery[pred_class].mean(axis=0) | |
| # # # # # dist = np.linalg.norm(vec - mean_vec) | |
| # # # # # shape, loc, scale = weibull_models[pred_class] | |
| # # # # # evt_prob = 1 - weibull_min.cdf(dist, shape, loc=loc, scale=scale) | |
| # # # # # hybrid = Config.COSINE_WEIGHT * cosine_score + (1 - Config.COSINE_WEIGHT) * evt_prob | |
| # # # # # if hybrid < Config.HYBRID_THRESHOLD: | |
| # # # # # return "unknown", hybrid | |
| # # # # # return pred_class, hybrid | |
| # # # # def embed_image(path): | |
| # # # # img = cv2.imread(path) | |
| # # # # if img is None: | |
| # # # # raise ValueError(f"Cannot read image: {path}") | |
| # # # # h, w = img.shape[:2] | |
| # # # # crop = img[h//4:3*h//4, w//4:3*w//4] | |
| # # # # img = cv2.resize(crop, Config.IMG_SIZE) | |
| # # # # if len(img.shape) == 2 or img.shape[2] == 1: | |
| # # # # img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) | |
| # # # # img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) | |
| # # # # img_prep = preprocess_input(np.expand_dims(img.astype(np.float32), axis=0)) | |
| # # # # emb = base_model.predict(img_prep, verbose=0).flatten() | |
| # # # # emb = (emb - mean_all) / std_all | |
| # # # # emb = emb / (np.linalg.norm(emb) + 1e-10) | |
| # # # # return emb | |
| # # # # def predict_hybrid(vec): | |
| # # # # sims = { | |
| # # # # c: cosine_similarity(emb, vec.reshape(1, -1)).max() | |
| # # # # for c, emb in gallery.items() | |
| # # # # } | |
| # # # # pred_class = max(sims, key=sims.get) | |
| # # # # cosine_score = sims[pred_class] | |
| # # # # if cosine_score < Config.COSINE_THRESHOLD: | |
| # # # # return "unknown", cosine_score | |
| # # # # mean_vec = gallery[pred_class].mean(axis=0) | |
| # # # # dist = np.linalg.norm(vec - mean_vec) | |
| # # # # shape, loc, scale = weibull_models[pred_class] | |
| # # # # evt_prob = 1 - weibull_min.cdf(dist, shape, loc=loc, scale=scale) | |
| # # # # hybrid = Config.COSINE_WEIGHT * cosine_score + (1 - Config.COSINE_WEIGHT) * evt_prob | |
| # # # # if hybrid < Config.HYBRID_THRESHOLD: | |
| # # # # return "unknown", hybrid | |
| # # # # return pred_class, hybrid | |
| # # # # import os | |
| # # # # import numpy as np | |
| # # # # import cv2 | |
| # # # # from sklearn.metrics.pairwise import cosine_similarity | |
| # # # # from scipy.stats import weibull_min | |
| # # # # from tensorflow.keras.models import load_model | |
| # # # # from tensorflow.keras.applications.resnet import preprocess_input | |
| # # # # from huggingface_hub import hf_hub_download | |
| # # # # from config import Config | |
| # # # # from gallery import load_gallery | |
| # # # import os | |
| # # # import numpy as np | |
| # # # import cv2 | |
| # # # from sklearn.metrics.pairwise import cosine_similarity | |
| # # # from scipy.stats import weibull_min | |
| # # # from tensorflow.keras.applications import ResNet50 # β add this | |
| # # # from tensorflow.keras.applications.resnet import preprocess_input | |
| # # # from huggingface_hub import hf_hub_download | |
| # # # from config import Config | |
| # # # from gallery import load_gallery | |
| # # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # # # HuggingFace repo (set HF_TOKEN env var if repo is private) | |
| # # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # # MODEL_REPO = "Omamaa12/iris-models" | |
| # # # HF_TOKEN = os.getenv("HF_TOKEN") | |
| # # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # # # Load ResNet50 from HuggingFace | |
| # # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # # # print("β³ Downloading ResNet50 from HuggingFaceβ¦") | |
| # # # # _resnet_path = hf_hub_download( | |
| # # # # repo_id=MODEL_REPO, | |
| # # # # filename="resnet50_imagenet.keras", | |
| # # # # # token=HF_TOKEN, | |
| # # # # ) | |
| # # # # # base_model = load_model(_resnet_path) | |
| # # # # # base_model = load_model(_resnet_path, compile=False, safe_mode=False) | |
| # # # # base_model = load_model( | |
| # # # # _resnet_path, | |
| # # # # compile=False, | |
| # # # # custom_objects={} | |
| # # # # ) | |
| # # # # print("β ResNet50 ready.") | |
| # # # print("β³ Building ResNet50 with ImageNet weightsβ¦") | |
| # # # base_model = ResNet50(weights='imagenet', include_top=False, pooling='avg') | |
| # # # print("β ResNet50 ready.") | |
| # # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # # # Load gallery from HuggingFace | |
| # # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # # print("β³ Downloading iris gallery from HuggingFaceβ¦") | |
| # # # _gallery_path = hf_hub_download( | |
| # # # repo_id=MODEL_REPO, | |
| # # # filename="iris_gallery_fixed.pkl", | |
| # # # token=HF_TOKEN, | |
| # # # ) | |
| # # # # Temporarily point Config.GALLERY_PATH to the downloaded file so | |
| # # # # gallery.py's load_gallery() can find it without modification. | |
| # # # Config.GALLERY_PATH = _gallery_path | |
| # # # gallery_data = load_gallery() | |
| # # # gallery = gallery_data["gallery"] # {class: np.array of normed embeddings} | |
| # # # weibull_models = gallery_data["weibull_models"] # {class: (shape, loc, scale)} | |
| # # # mean_all = gallery_data["mean_all"] | |
| # # # std_all = gallery_data["std_all"] | |
| # # # print(f"β Gallery loaded β {len(gallery)} identities.") | |
| # # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # # # Feature extraction | |
| # # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # # def embed_image(path): | |
| # # # img = cv2.imread(path) | |
| # # # if img is None: | |
| # # # raise ValueError(f"Cannot read image: {path}") | |
| # # # h, w = img.shape[:2] | |
| # # # crop = img[h//4:3*h//4, w//4:3*w//4] | |
| # # # img = cv2.resize(crop, Config.IMG_SIZE) | |
| # # # # if len(img.shape) == 2 or img.shape[2] == 1: | |
| # # # # img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) | |
| # # # if len(img.shape) == 2 or (len(img.shape) == 3 and img.shape[2] == 1): | |
| # # # img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) # make it 3ch BGR first | |
| # # # img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) | |
| # # # img_arr = preprocess_input(np.expand_dims(img.astype(np.float32), axis=0)) | |
| # # # emb = base_model.predict(img_arr, verbose=0).flatten() | |
| # # # emb = (emb - mean_all) / std_all | |
| # # # emb = emb / (np.linalg.norm(emb) + 1e-10) | |
| # # # return emb | |
| # # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # # # Hybrid prediction (cosine + Weibull EVT) | |
| # # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # # def predict_hybrid(vec): | |
| # # # sims = { | |
| # # # c: cosine_similarity(emb, vec.reshape(1, -1)).max() | |
| # # # for c, emb in gallery.items() | |
| # # # } | |
| # # # pred_class = max(sims, key=sims.get) | |
| # # # cosine_score = sims[pred_class] | |
| # # # if cosine_score < Config.COSINE_THRESHOLD: | |
| # # # return "unknown", cosine_score | |
| # # # mean_vec = gallery[pred_class].mean(axis=0) | |
| # # # dist = np.linalg.norm(vec - mean_vec) | |
| # # # shape, loc, scale = weibull_models[pred_class] | |
| # # # evt_prob = 1 - weibull_min.cdf(dist, shape, loc=loc, scale=scale) | |
| # # # hybrid = Config.COSINE_WEIGHT * cosine_score + (1 - Config.COSINE_WEIGHT) * evt_prob | |
| # # # if hybrid < Config.HYBRID_THRESHOLD: | |
| # # # return "unknown", hybrid | |
| # # # return pred_class, hybrid | |
| # # import os | |
| # # import cv2 | |
| # # import time | |
| # # import csv | |
| # # import pickle | |
| # # import numpy as np | |
| # # from collections import defaultdict | |
| # # from datetime import datetime | |
| # # from scipy.stats import weibull_min | |
| # # from sklearn.metrics.pairwise import cosine_similarity | |
| # # from tensorflow.keras.applications import ResNet50 | |
| # # from tensorflow.keras.applications.resnet import preprocess_input | |
| # # from huggingface_hub import hf_hub_download, HfApi | |
| # # # ============================== | |
| # # # MODEL LOAD | |
| # # # ============================== | |
| # # model = ResNet50(weights='imagenet', include_top=False, pooling='avg') | |
| # # os.makedirs('static/debug', exist_ok=True) | |
| # # # ============================== | |
| # # # PREPROCESSING | |
| # # # ============================== | |
| # # IMG_SIZE = (224, 224) | |
| # # def normalize_lighting(img): | |
| # # """ | |
| # # Standard illumination normalization to maintain gallery compatibility. | |
| # # """ | |
| # # if img is None: return None | |
| # # gray_mean = np.mean(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)) | |
| # # gamma = np.log(128) / (np.log(gray_mean + 1e-5)) | |
| # # gamma = np.clip(gamma, 0.4, 2.5) | |
| # # lut = np.array([((i / 255.0) ** (1.0 / gamma)) * 255 for i in range(256)], dtype=np.uint8) | |
| # # img = cv2.LUT(img, lut) | |
| # # lab = cv2.cvtColor(img, cv2.COLOR_BGR2Lab) | |
| # # l, a, b = cv2.split(lab) | |
| # # clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) | |
| # # l = clahe.apply(l) | |
| # # lab = cv2.merge((l, a, b)) | |
| # # img = cv2.cvtColor(lab, cv2.COLOR_Lab2BGR) | |
| # # return img | |
| # # def _sharpen(img): | |
| # # kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]]) | |
| # # return cv2.filter2D(img, -1, kernel) | |
| # # def _high_contrast(img): | |
| # # lab = cv2.cvtColor(img, cv2.COLOR_BGR2Lab) | |
| # # l, a, b = cv2.split(lab) | |
| # # # Match the registration limit (5.0) | |
| # # clahe = cv2.createCLAHE(clipLimit=5.0, tileGridSize=(8, 8)) | |
| # # l = clahe.apply(l) | |
| # # return cv2.cvtColor(cv2.merge((l, a, b)), cv2.COLOR_Lab2BGR) | |
| # # def preprocess_iris(img): | |
| # # if img is None: | |
| # # return None | |
| # # # 1. INITIAL CROP (Middle 50%) | |
| # # h, w = img.shape[:2] | |
| # # img = img[h // 4: 3 * h // 4, w // 4: 3 * w // 4] | |
| # # cv2.imwrite('static/debug/1_initial_crop.png', img) | |
| # # if len(img.shape) == 2 or img.shape[2] == 1: | |
| # # img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) | |
| # # # 2. LIGHTING NORMALIZATION | |
| # # img = normalize_lighting(img) | |
| # # cv2.imwrite('static/debug/2_normalized.png', img) | |
| # # # 3. FINAL RESIZE | |
| # # img = cv2.resize(img, IMG_SIZE) | |
| # # cv2.imwrite('static/debug/3_final_input.png', img) | |
| # # img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) | |
| # # return img | |
| # # # ============================== | |
| # # # EMBEDDING | |
| # # # ============================== | |
| # # # def augment_lighting_variants(img): | |
| # # # """ | |
| # # # Creates a diverse set of environmental variants for registration. | |
| # # # These are added to the gallery ONLY for new registrations. | |
| # # # """ | |
| # # # variants = [img] | |
| # # # # 1. Brightness variants (stronger range) | |
| # # # variants.append(np.clip(img.astype(np.float32) * 1.6, 0, 255).astype(np.uint8)) | |
| # # # variants.append(np.clip(img.astype(np.float32) * 0.4, 0, 255).astype(np.uint8)) | |
| # # # # 2. High Contrast (High CLAHE) | |
| # # # lab = cv2.cvtColor(cv2.cvtColor(img, cv2.COLOR_RGB2BGR), cv2.COLOR_BGR2Lab) | |
| # # # l, a, b = cv2.split(lab) | |
| # # # clahe_high = cv2.createCLAHE(clipLimit=5.0, tileGridSize=(8, 8)) | |
| # # # l = clahe_high.apply(l) | |
| # # # lab = cv2.merge((l, a, b)) | |
| # # # variants.append(cv2.cvtColor(cv2.cvtColor(lab, cv2.COLOR_Lab2BGR), cv2.COLOR_BGR2RGB)) | |
| # # # # 3. Sharpening (Added as an augmentation variant only) | |
| # # # kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]]) | |
| # # # sharpened = cv2.filter2D(img, -1, kernel) | |
| # # # variants.append(sharpened) | |
| # # # # 4. Blur variant (simulates slight out-of-focus) | |
| # # # variants.append(cv2.GaussianBlur(img, (3, 3), 0)) | |
| # # # # 5. Noise variant (simulates sensor noise) | |
| # # # noise = np.random.normal(0, 8, img.shape).astype(np.int16) | |
| # # # variants.append(np.clip(img.astype(np.int16) + noise, 0, 255).astype(np.uint8)) | |
| # # # return variants | |
| # # def augment_lighting_variants(img): | |
| # # """ | |
| # # Creates a diverse set of environmental variants for registration. | |
| # # These are added to the gallery ONLY for new registrations. | |
| # # """ | |
| # # variants = [] | |
| # # # Ensure debug directory exists | |
| # # os.makedirs('static/debug', exist_ok=True) | |
| # # # 0. Original preprocessed image | |
| # # variants.append(img) | |
| # # cv2.imwrite('static/debug/reg_0_original.png', cv2.cvtColor(img, cv2.COLOR_RGB2BGR)) # Change: Save original | |
| # # # 1. Brightness variants (stronger range) | |
| # # bright = np.clip(img.astype(np.float32) * 1.6, 0, 255).astype(np.uint8) | |
| # # variants.append(bright) | |
| # # cv2.imwrite('static/debug/reg_1_bright.png', cv2.cvtColor(bright, cv2.COLOR_RGB2BGR)) # Change: Save bright variant | |
| # # dark = np.clip(img.astype(np.float32) * 0.4, 0, 255).astype(np.uint8) | |
| # # variants.append(dark) | |
| # # cv2.imwrite('static/debug/reg_1_dark.png', cv2.cvtColor(dark, cv2.COLOR_RGB2BGR)) # Change: Save dark variant | |
| # # # 2. High Contrast (High CLAHE) | |
| # # lab = cv2.cvtColor(cv2.cvtColor(img, cv2.COLOR_RGB2BGR), cv2.COLOR_BGR2Lab) | |
| # # l, a, b = cv2.split(lab) | |
| # # clahe_high = cv2.createCLAHE(clipLimit=5.0, tileGridSize=(8, 8)) | |
| # # l = clahe_high.apply(l) | |
| # # lab = cv2.merge((l, a, b)) | |
| # # hc = cv2.cvtColor(cv2.cvtColor(lab, cv2.COLOR_Lab2BGR), cv2.COLOR_BGR2RGB) | |
| # # variants.append(hc) | |
| # # cv2.imwrite('static/debug/reg_2_high_contrast.png', cv2.cvtColor(hc, cv2.COLOR_RGB2BGR)) # Change: Save high contrast variant | |
| # # # 3. Sharpening (Added as an augmentation variant only) | |
| # # kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]]) | |
| # # sharpened = cv2.filter2D(img, -1, kernel) | |
| # # variants.append(sharpened) | |
| # # cv2.imwrite('static/debug/reg_3_sharpened.png', cv2.cvtColor(sharpened, cv2.COLOR_RGB2BGR)) # Change: Save sharpened variant | |
| # # # 4. Blur variant (simulates slight out-of-focus) | |
| # # blurred = cv2.GaussianBlur(img, (3, 3), 0) | |
| # # variants.append(blurred) | |
| # # cv2.imwrite('static/debug/reg_4_blurred.png', cv2.cvtColor(blurred, cv2.COLOR_RGB2BGR)) # Change: Save blurred variant | |
| # # # 5. Noise variant (simulates sensor noise) | |
| # # noise = np.random.normal(0, 8, img.shape).astype(np.int16) | |
| # # noisy = np.clip(img.astype(np.int16) + noise, 0, 255).astype(np.uint8) | |
| # # variants.append(noisy) | |
| # # cv2.imwrite('static/debug/reg_5_noisy.png', cv2.cvtColor(noisy, cv2.COLOR_RGB2BGR)) # Change: Save noisy variant | |
| # # return variants | |
| # # def embed_array(img_rgb): | |
| # # arr = preprocess_input(np.expand_dims(img_rgb.astype(np.float32), axis=0)) | |
| # # return model.predict(arr, verbose=0).flatten() | |
| # # # ============================== | |
| # # # LOAD GALLERY (FROM HUGGING FACE) | |
| # # # ============================== | |
| # # HF_REPO_ID = "Omamaa12/iris-models" | |
| # # HF_FILENAME = "iris_gallery_robustt.pkl" | |
| # # PKL_PATH = os.path.join('models', HF_FILENAME) | |
| # # def sync_gallery_from_hf(): | |
| # # """Downloads the latest gallery from Hugging Face.""" | |
| # # print(f"β³ Syncing gallery from Hugging Face ({HF_REPO_ID})...") | |
| # # try: | |
| # # # Download to the models folder | |
| # # downloaded_path = hf_hub_download( | |
| # # repo_id=HF_REPO_ID, | |
| # # filename=HF_FILENAME, | |
| # # repo_type="model", | |
| # # local_dir="models", | |
| # # local_dir_use_symlinks=False, | |
| # # force_download=True, | |
| # # ) | |
| # # print(f"β Gallery synced: {downloaded_path}") | |
| # # return downloaded_path | |
| # # except Exception as e: | |
| # # print(f"β οΈ HF Sync failed, using local fallback: {e}") | |
| # # return PKL_PATH | |
| # # # Sync on startup | |
| # # PKL_PATH = sync_gallery_from_hf() | |
| # # if os.path.exists(PKL_PATH): | |
| # # with open(PKL_PATH, 'rb') as f: | |
| # # data = pickle.load(f) | |
| # # gallery = data['gallery'] | |
| # # weibull_models = data['weibull_models'] | |
| # # mean_all = data['mean_all'] | |
| # # std_all = data['std_all'] | |
| # # print(f"β Gallery loaded - {len(gallery)} identities.") | |
| # # else: | |
| # # print("β Gallery file not found! Initializing empty.") | |
| # # gallery = {} | |
| # # weibull_models = {} | |
| # # mean_all = None # These should ideally be pre-set | |
| # # std_all = None | |
| # # # ============================== | |
| # # # EMBED IMAGE (LOGIN) | |
| # # # ============================== | |
| # # ANGLE_AUGS = (-12, -6, 0, 6, 12) | |
| # # def _embed_rgb(rgb_img): | |
| # # arr = preprocess_input(np.expand_dims(rgb_img.astype(np.float32), axis=0)) | |
| # # emb = model.predict(arr, verbose=0).flatten() | |
| # # emb = (emb - mean_all) / std_all | |
| # # emb = emb / (np.linalg.norm(emb) + 1e-10) | |
| # # return emb | |
| # # def embed_image(image_path): | |
| # # """ | |
| # # Extracts multiple embeddings (TTA variants). | |
| # # Returns a list of vectors. | |
| # # """ | |
| # # img = cv2.imread(image_path) | |
| # # if img is None: | |
| # # return None | |
| # # pp = preprocess_iris(img) | |
| # # if pp is None: | |
| # # return None | |
| # # # TTA variants: Standard, Sharpened, High Contrast | |
| # # s = _sharpen(pp) | |
| # # hc = _high_contrast(pp) | |
| # # cv2.imwrite('static/debug/tta_sharpened.png', s) | |
| # # cv2.imwrite('static/debug/tta_high_contrast.png', hc) | |
| # # tta_variants = [pp, s, hc] | |
| # # h, w = pp.shape[:2] | |
| # # center = (w // 2, h // 2) | |
| # # final_vectors = [] | |
| # # for v in tta_variants: | |
| # # embs = [] | |
| # # for angle in ANGLE_AUGS: | |
| # # M = cv2.getRotationMatrix2D(center, angle, 1.0) | |
| # # rot = cv2.warpAffine(v, M, (w, h), borderMode=cv2.BORDER_REFLECT_101) | |
| # # embs.append(_embed_rgb(rot)) | |
| # # # Average rotations for THIS variant | |
| # # v_emb = np.mean(np.stack(embs), axis=0) | |
| # # v_emb = v_emb / (np.linalg.norm(v_emb) + 1e-10) | |
| # # final_vectors.append(v_emb) | |
| # # return final_vectors | |
| # # # ============================== | |
| # # # PREDICTION | |
| # # # ============================== | |
| # # COSINE_THRESHOLD = 0.62 | |
| # # # COSINE_THRESHOLD = 0.67 | |
| # # COSINE_WEIGHT = 0.85 | |
| # # # HYBRID_THRESHOLD = 0.55 | |
| # # HYBRID_THRESHOLD = 0.63 | |
| # # TOP2_MARGIN = 0.005 | |
| # # def _class_similarity(class_embs, vec): | |
| # # sims = cosine_similarity(class_embs, vec.reshape(1, -1)).ravel() | |
| # # return float(np.mean(np.sort(sims)[-2:])) | |
| # # def predict_robust(vectors): | |
| # # """ | |
| # # Matches multiple TTA vectors and takes the BEST (MAX) similarity. | |
| # # """ | |
| # # best_identity = 'unknown' | |
| # # best_cosine = 0 | |
| # # best_hybrid = 0 | |
| # # best_second = 0 | |
| # # # Try each TTA variant | |
| # # for vec in vectors: | |
| # # sims = {c: _class_similarity(emb, vec) for c, emb in gallery.items()} | |
| # # sorted_sims = sorted(sims.items(), key=lambda x: x[1], reverse=True) | |
| # # current_class, current_cos = sorted_sims[0] | |
| # # current_second = sorted_sims[1][1] if len(sorted_sims) > 1 else 0 | |
| # # mean_vec = gallery[current_class].mean(axis=0) | |
| # # dist = np.linalg.norm(vec - mean_vec) | |
| # # shape, loc, scale = weibull_models[current_class] | |
| # # evt_prob = 1 - weibull_min.cdf(dist, shape, loc=loc, scale=scale) | |
| # # current_hybrid = COSINE_WEIGHT * current_cos + (1 - COSINE_WEIGHT) * evt_prob | |
| # # # If THIS variant is better than our previous best, update | |
| # # if current_cos > best_cosine: | |
| # # best_identity = current_class | |
| # # best_cosine = current_cos | |
| # # best_hybrid = current_hybrid | |
| # # best_second = current_second | |
| # # # LOGGING FOR DEBUGGING | |
| # # print(f"\n--- TTA Max-Score Debug ---") | |
| # # print(f"Final Predicted: {best_identity}") | |
| # # print(f"Max Cosine Score: {best_cosine:.4f} (Threshold: {COSINE_THRESHOLD})") | |
| # # print(f"Hybrid Score: {best_hybrid:.4f} (Threshold: {HYBRID_THRESHOLD})") | |
| # # print(f"---------------------------\n") | |
| # # if best_cosine < COSINE_THRESHOLD: | |
| # # return 'unknown', best_cosine, 0 | |
| # # if best_cosine - best_second < TOP2_MARGIN: | |
| # # return 'unknown', best_cosine, 0 | |
| # # if best_hybrid < HYBRID_THRESHOLD: | |
| # # return 'unknown', best_cosine, best_hybrid | |
| # # return best_identity, best_cosine, best_hybrid | |
| # # # ============================== | |
| # # # LOGIN SYSTEM | |
| # # # ============================== | |
| # # attempt_log = defaultdict(list) | |
| # # def login(image_path, session_id='default', silent=False): | |
| # # vec = embed_image(image_path) | |
| # # if vec is None: | |
| # # return {'status': 'error'} | |
| # # identity, cos, hyb = predict_robust(vec) | |
| # # if identity == 'unknown': | |
| # # return {'status': 'denied', 'identity': None, 'score': cos} | |
| # # return {'status': 'granted', 'identity': identity, 'score': hyb} | |
| # # # ============================== | |
| # # # REGISTRATION (UNCHANGED) | |
| # # # ============================== | |
| # # def _embed_for_registration(image_path): | |
| # # img = cv2.imread(image_path) | |
| # # if img is None: | |
| # # return [] | |
| # # pp = preprocess_iris(img) | |
| # # if pp is None: | |
| # # return [] | |
| # # embs = [] | |
| # # for v in augment_lighting_variants(pp): | |
| # # emb = embed_array(v) | |
| # # emb = (emb - mean_all) / (std_all + 1e-10) | |
| # # emb = emb / (np.linalg.norm(emb) + 1e-10) | |
| # # embs.append(emb) | |
| # # return embs | |
| # # # def register_person(person_label, image_paths, gallery_pkl_path=PKL_PATH, overwrite=False): | |
| # # # global gallery, weibull_models | |
| # # # new_embs = [] | |
| # # # for p in image_paths: | |
| # # # new_embs.extend(_embed_for_registration(p)) | |
| # # # new_embs = np.asarray(new_embs) | |
| # # # if person_label in gallery: | |
| # # # gallery[person_label] = np.vstack([gallery[person_label], new_embs]) | |
| # # # else: | |
| # # # gallery[person_label] = new_embs | |
| # # # # weibull_models[person_label] = (1, 0, 1) | |
| # # # # FIXED β real Weibull fit on actual embedding distances | |
| # # # mean_vec = gallery[person_label].mean(axis=0) | |
| # # # dists = np.linalg.norm(gallery[person_label] - mean_vec, axis=1) | |
| # # # if len(dists) >= 3: | |
| # # # # Need at least 3 points to fit Weibull reliably | |
| # # # tail_size = max(3, int(0.3 * len(dists))) # use top 30% of distances | |
| # # # tail = np.sort(dists)[-tail_size:] | |
| # # # shape, loc, scale = weibull_min.fit(tail, floc=0) | |
| # # # weibull_models[person_label] = (shape, loc, scale) | |
| # # # print(f"β Weibull fitted for {person_label}: shape={shape:.3f}, scale={scale:.3f}") | |
| # # # else: | |
| # # # # Fallback if somehow less than 3 embeddings | |
| # # # weibull_models[person_label] = (1, 0, 1) | |
| # # # print(f"β οΈ Not enough embeddings for Weibull fit, using fallback") | |
| # # # with open(gallery_pkl_path, 'wb') as f: | |
| # # # pickle.dump({ | |
| # # # 'gallery': gallery, | |
| # # # 'weibull_models': weibull_models, | |
| # # # 'mean_all': mean_all, | |
| # # # 'std_all': std_all | |
| # # # }, f) | |
| # # # # UPLOAD TO HUGGING FACE | |
| # # # try: | |
| # # # print(f"π€ Uploading updated gallery to Hugging Face...") | |
| # # # api = HfApi() | |
| # # # api.upload_file( | |
| # # # path_or_fileobj=gallery_pkl_path, | |
| # # # path_in_repo=HF_FILENAME, | |
| # # # repo_id=HF_REPO_ID, | |
| # # # repo_type="model" | |
| # # # ) | |
| # # # print("β Gallery updated on Hugging Face!") | |
| # # # except Exception as e: | |
| # # # print(f"β Failed to upload to Hugging Face: {e}") | |
| # # # return {'status': 'success', 'identity': person_label} | |
| # # def register_person(person_label, image_paths, gallery_pkl_path=PKL_PATH, overwrite=False): | |
| # # global gallery, weibull_models | |
| # # new_embs = [] | |
| # # for p in image_paths: | |
| # # new_embs.extend(_embed_for_registration(p)) | |
| # # new_embs = np.asarray(new_embs) | |
| # # if person_label in gallery: | |
| # # gallery[person_label] = np.vstack([gallery[person_label], new_embs]) | |
| # # else: | |
| # # gallery[person_label] = new_embs | |
| # # # FIXED β real Weibull fit on actual embedding distances | |
| # # mean_vec = gallery[person_label].mean(axis=0) | |
| # # dists = np.linalg.norm(gallery[person_label] - mean_vec, axis=1) | |
| # # if len(dists) >= 3: | |
| # # # Need at least 3 points to fit Weibull reliably | |
| # # tail_size = max(3, int(0.3 * len(dists))) | |
| # # tail = np.sort(dists)[-tail_size:] | |
| # # shape, loc, scale = weibull_min.fit(tail, floc=0) | |
| # # weibull_models[person_label] = (shape, loc, scale) | |
| # # print(f"β Weibull fitted for {person_label}: shape={shape:.3f}, scale={scale:.3f}") | |
| # # else: | |
| # # # Fallback if somehow less than 3 embeddings | |
| # # weibull_models[person_label] = (1, 0, 1) | |
| # # print(f"β οΈ Not enough embeddings for Weibull fit, using fallback") | |
| # # with open(gallery_pkl_path, 'wb') as f: | |
| # # pickle.dump({ | |
| # # 'gallery': gallery, | |
| # # 'weibull_models': weibull_models, | |
| # # 'mean_all': mean_all, | |
| # # 'std_all': std_all | |
| # # }, f) | |
| # # # UPLOAD TO HUGGING FACE | |
| # # try: | |
| # # print(f"π€ Uploading updated gallery to Hugging Face...") | |
| # # api = HfApi() | |
| # # api.upload_file( | |
| # # path_or_fileobj=gallery_pkl_path, | |
| # # path_in_repo=HF_FILENAME, | |
| # # repo_id=HF_REPO_ID, | |
| # # repo_type="model" | |
| # # ) | |
| # # print("β Gallery updated on Hugging Face!") | |
| # # except Exception as e: | |
| # # print(f"β Failed to upload to Hugging Face: {e}") | |
| # # return {'status': 'success', 'identity': person_label} | |
| # # print("β iris_recognition ready") | |
| # import os | |
| # import cv2 | |
| # import time | |
| # import csv | |
| # import pickle | |
| # import numpy as np | |
| # from collections import defaultdict | |
| # from datetime import datetime | |
| # from scipy.stats import weibull_min | |
| # from sklearn.metrics.pairwise import cosine_similarity | |
| # from tensorflow.keras.applications import ResNet50 | |
| # from tensorflow.keras.applications.resnet import preprocess_input | |
| # from huggingface_hub import hf_hub_download, HfApi | |
| # # ============================== | |
| # # MODEL LOAD | |
| # # ============================== | |
| # model = ResNet50(weights='imagenet', include_top=False, pooling='avg') | |
| # os.makedirs('static/debug', exist_ok=True) | |
| # # ============================== | |
| # # PREPROCESSING | |
| # # ============================== | |
| # IMG_SIZE = (224, 224) | |
| # def normalize_lighting(img): | |
| # """ | |
| # Standard illumination normalization to maintain gallery compatibility. | |
| # """ | |
| # if img is None: return None | |
| # gray_mean = np.mean(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)) | |
| # gamma = np.log(128) / (np.log(gray_mean + 1e-5)) | |
| # gamma = np.clip(gamma, 0.4, 2.5) | |
| # lut = np.array([((i / 255.0) ** (1.0 / gamma)) * 255 for i in range(256)], dtype=np.uint8) | |
| # img = cv2.LUT(img, lut) | |
| # lab = cv2.cvtColor(img, cv2.COLOR_BGR2Lab) | |
| # l, a, b = cv2.split(lab) | |
| # clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) | |
| # l = clahe.apply(l) | |
| # lab = cv2.merge((l, a, b)) | |
| # img = cv2.cvtColor(lab, cv2.COLOR_Lab2BGR) | |
| # return img | |
| # def _sharpen(img): | |
| # kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]]) | |
| # return cv2.filter2D(img, -1, kernel) | |
| # def _high_contrast(img): | |
| # lab = cv2.cvtColor(img, cv2.COLOR_BGR2Lab) | |
| # l, a, b = cv2.split(lab) | |
| # # Match the registration limit (5.0) | |
| # clahe = cv2.createCLAHE(clipLimit=5.0, tileGridSize=(8, 8)) | |
| # l = clahe.apply(l) | |
| # return cv2.cvtColor(cv2.merge((l, a, b)), cv2.COLOR_Lab2BGR) | |
| # def preprocess_iris(img): | |
| # if img is None: | |
| # return None | |
| # # 1. INITIAL CROP (Middle 50%) | |
| # h, w = img.shape[:2] | |
| # img = img[h // 4: 3 * h // 4, w // 4: 3 * w // 4] | |
| # cv2.imwrite('static/debug/1_initial_crop.png', img) | |
| # if len(img.shape) == 2 or img.shape[2] == 1: | |
| # img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) | |
| # # 2. LIGHTING NORMALIZATION | |
| # img = normalize_lighting(img) | |
| # cv2.imwrite('static/debug/2_normalized.png', img) | |
| # # 3. FINAL RESIZE | |
| # img = cv2.resize(img, IMG_SIZE) | |
| # cv2.imwrite('static/debug/3_final_input.png', img) | |
| # img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) | |
| # return img | |
| # # ============================== | |
| # # EMBEDDING | |
| # # ============================== | |
| # # def augment_lighting_variants(img): | |
| # # """ | |
| # # Creates a diverse set of environmental variants for registration. | |
| # # These are added to the gallery ONLY for new registrations. | |
| # # """ | |
| # # variants = [img] | |
| # # # 1. Brightness variants (stronger range) | |
| # # variants.append(np.clip(img.astype(np.float32) * 1.6, 0, 255).astype(np.uint8)) | |
| # # variants.append(np.clip(img.astype(np.float32) * 0.4, 0, 255).astype(np.uint8)) | |
| # # # 2. High Contrast (High CLAHE) | |
| # # lab = cv2.cvtColor(cv2.cvtColor(img, cv2.COLOR_RGB2BGR), cv2.COLOR_BGR2Lab) | |
| # # l, a, b = cv2.split(lab) | |
| # # clahe_high = cv2.createCLAHE(clipLimit=5.0, tileGridSize=(8, 8)) | |
| # # l = clahe_high.apply(l) | |
| # # lab = cv2.merge((l, a, b)) | |
| # # variants.append(cv2.cvtColor(cv2.cvtColor(lab, cv2.COLOR_Lab2BGR), cv2.COLOR_BGR2RGB)) | |
| # # # 3. Sharpening (Added as an augmentation variant only) | |
| # # kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]]) | |
| # # sharpened = cv2.filter2D(img, -1, kernel) | |
| # # variants.append(sharpened) | |
| # # # 4. Blur variant (simulates slight out-of-focus) | |
| # # variants.append(cv2.GaussianBlur(img, (3, 3), 0)) | |
| # # # 5. Noise variant (simulates sensor noise) | |
| # # noise = np.random.normal(0, 8, img.shape).astype(np.int16) | |
| # # variants.append(np.clip(img.astype(np.int16) + noise, 0, 255).astype(np.uint8)) | |
| # # return variants | |
| # def augment_lighting_variants(img): | |
| # """ | |
| # Creates a diverse set of environmental variants for registration. | |
| # These are added to the gallery ONLY for new registrations. | |
| # """ | |
| # variants = [] | |
| # # Ensure debug directory exists | |
| # os.makedirs('static/debug', exist_ok=True) | |
| # # 0. Original preprocessed image | |
| # variants.append(img) | |
| # cv2.imwrite('static/debug/reg_0_original.png', cv2.cvtColor(img, cv2.COLOR_RGB2BGR)) # Change: Save original | |
| # # 1. Brightness variants (stronger range) | |
| # bright = np.clip(img.astype(np.float32) * 1.6, 0, 255).astype(np.uint8) | |
| # variants.append(bright) | |
| # cv2.imwrite('static/debug/reg_1_bright.png', cv2.cvtColor(bright, cv2.COLOR_RGB2BGR)) # Change: Save bright variant | |
| # dark = np.clip(img.astype(np.float32) * 0.4, 0, 255).astype(np.uint8) | |
| # variants.append(dark) | |
| # cv2.imwrite('static/debug/reg_1_dark.png', cv2.cvtColor(dark, cv2.COLOR_RGB2BGR)) # Change: Save dark variant | |
| # # 2. High Contrast (High CLAHE) | |
| # lab = cv2.cvtColor(cv2.cvtColor(img, cv2.COLOR_RGB2BGR), cv2.COLOR_BGR2Lab) | |
| # l, a, b = cv2.split(lab) | |
| # clahe_high = cv2.createCLAHE(clipLimit=5.0, tileGridSize=(8, 8)) | |
| # l = clahe_high.apply(l) | |
| # lab = cv2.merge((l, a, b)) | |
| # hc = cv2.cvtColor(cv2.cvtColor(lab, cv2.COLOR_Lab2BGR), cv2.COLOR_BGR2RGB) | |
| # variants.append(hc) | |
| # cv2.imwrite('static/debug/reg_2_high_contrast.png', cv2.cvtColor(hc, cv2.COLOR_RGB2BGR)) # Change: Save high contrast variant | |
| # # 3. Sharpening (Added as an augmentation variant only) | |
| # kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]]) | |
| # sharpened = cv2.filter2D(img, -1, kernel) | |
| # variants.append(sharpened) | |
| # cv2.imwrite('static/debug/reg_3_sharpened.png', cv2.cvtColor(sharpened, cv2.COLOR_RGB2BGR)) # Change: Save sharpened variant | |
| # # 4. Blur variant (simulates slight out-of-focus) | |
| # blurred = cv2.GaussianBlur(img, (3, 3), 0) | |
| # variants.append(blurred) | |
| # cv2.imwrite('static/debug/reg_4_blurred.png', cv2.cvtColor(blurred, cv2.COLOR_RGB2BGR)) # Change: Save blurred variant | |
| # # 5. Noise variant (simulates sensor noise) | |
| # noise = np.random.normal(0, 8, img.shape).astype(np.int16) | |
| # noisy = np.clip(img.astype(np.int16) + noise, 0, 255).astype(np.uint8) | |
| # variants.append(noisy) | |
| # cv2.imwrite('static/debug/reg_5_noisy.png', cv2.cvtColor(noisy, cv2.COLOR_RGB2BGR)) # Change: Save noisy variant | |
| # return variants | |
| # def embed_array(img_rgb): | |
| # arr = preprocess_input(np.expand_dims(img_rgb.astype(np.float32), axis=0)) | |
| # return model.predict(arr, verbose=0).flatten() | |
| # # ============================== | |
| # # LOAD GALLERY (FROM HUGGING FACE) | |
| # # ============================== | |
| # HF_REPO_ID = "Omamaa12/iris-models" | |
| # HF_FILENAME = "iris_gallery_robustt.pkl" | |
| # PKL_PATH = os.path.join('models', HF_FILENAME) | |
| # def sync_gallery_from_hf(): | |
| # """Downloads the latest gallery from Hugging Face.""" | |
| # print(f"β³ Syncing gallery from Hugging Face ({HF_REPO_ID})...") | |
| # try: | |
| # # Download to the models folder | |
| # downloaded_path = hf_hub_download( | |
| # repo_id=HF_REPO_ID, | |
| # filename=HF_FILENAME, | |
| # repo_type="model", | |
| # local_dir="models", | |
| # local_dir_use_symlinks=False, | |
| # force_download=True, | |
| # ) | |
| # print(f"β Gallery synced: {downloaded_path}") | |
| # return downloaded_path | |
| # except Exception as e: | |
| # print(f"β οΈ HF Sync failed, using local fallback: {e}") | |
| # return PKL_PATH | |
| # # Sync on startup | |
| # PKL_PATH = sync_gallery_from_hf() | |
| # if os.path.exists(PKL_PATH): | |
| # with open(PKL_PATH, 'rb') as f: | |
| # data = pickle.load(f) | |
| # gallery = data['gallery'] | |
| # weibull_models = data['weibull_models'] | |
| # mean_all = data['mean_all'] | |
| # std_all = data['std_all'] | |
| # print(f"β Gallery loaded - {len(gallery)} identities.") | |
| # else: | |
| # print("β Gallery file not found! Initializing empty.") | |
| # gallery = {} | |
| # weibull_models = {} | |
| # mean_all = None # These should ideally be pre-set | |
| # std_all = None | |
| # # ============================== | |
| # # EMBED IMAGE (LOGIN) | |
| # # ============================== | |
| # ANGLE_AUGS = (-12, -6, 0, 6, 12) | |
| # def _embed_rgb(rgb_img): | |
| # arr = preprocess_input(np.expand_dims(rgb_img.astype(np.float32), axis=0)) | |
| # emb = model.predict(arr, verbose=0).flatten() | |
| # emb = (emb - mean_all) / (std_all + 1e-10) # β ADD THIS LINE BACK | |
| # emb = emb / (np.linalg.norm(emb) + 1e-10) | |
| # return emb | |
| # def embed_image(image_path): | |
| # """ | |
| # Extracts multiple embeddings (TTA variants). | |
| # Returns a list of vectors. | |
| # """ | |
| # img = cv2.imread(image_path) | |
| # if img is None: | |
| # return None | |
| # pp = preprocess_iris(img) | |
| # if pp is None: | |
| # return None | |
| # # TTA variants: Standard, Sharpened, High Contrast | |
| # s = _sharpen(pp) | |
| # hc = _high_contrast(pp) | |
| # cv2.imwrite('static/debug/tta_sharpened.png', s) | |
| # cv2.imwrite('static/debug/tta_high_contrast.png', hc) | |
| # tta_variants = [pp, s, hc] | |
| # h, w = pp.shape[:2] | |
| # center = (w // 2, h // 2) | |
| # final_vectors = [] | |
| # for v in tta_variants: | |
| # embs = [] | |
| # for angle in ANGLE_AUGS: | |
| # M = cv2.getRotationMatrix2D(center, angle, 1.0) | |
| # rot = cv2.warpAffine(v, M, (w, h), borderMode=cv2.BORDER_REFLECT_101) | |
| # embs.append(_embed_rgb(rot)) | |
| # # Average rotations for THIS variant | |
| # v_emb = np.mean(np.stack(embs), axis=0) | |
| # v_emb = v_emb / (np.linalg.norm(v_emb) + 1e-10) | |
| # final_vectors.append(v_emb) | |
| # return final_vectors | |
| # # ============================== | |
| # # PREDICTION | |
| # # ============================== | |
| # COSINE_THRESHOLD = 0.62 | |
| # # COSINE_THRESHOLD = 0.67 | |
| # COSINE_WEIGHT = 0.85 | |
| # # HYBRID_THRESHOLD = 0.55 | |
| # HYBRID_THRESHOLD = 0.63 | |
| # TOP2_MARGIN = 0.005 | |
| # def _class_similarity(class_embs, vec): | |
| # sims = cosine_similarity(class_embs, vec.reshape(1, -1)).ravel() | |
| # return float(np.mean(np.sort(sims)[-2:])) | |
| # def predict_robust(vectors): | |
| # """ | |
| # Matches multiple TTA vectors and takes the BEST (MAX) similarity. | |
| # """ | |
| # best_identity = 'unknown' | |
| # best_cosine = 0 | |
| # best_hybrid = 0 | |
| # best_second = 0 | |
| # # Try each TTA variant | |
| # for vec in vectors: | |
| # sims = {c: _class_similarity(emb, vec) for c, emb in gallery.items()} | |
| # sorted_sims = sorted(sims.items(), key=lambda x: x[1], reverse=True) | |
| # current_class, current_cos = sorted_sims[0] | |
| # current_second = sorted_sims[1][1] if len(sorted_sims) > 1 else 0 | |
| # mean_vec = gallery[current_class].mean(axis=0) | |
| # dist = np.linalg.norm(vec - mean_vec) | |
| # shape, loc, scale = weibull_models[current_class] | |
| # evt_prob = 1 - weibull_min.cdf(dist, shape, loc=loc, scale=scale) | |
| # current_hybrid = COSINE_WEIGHT * current_cos + (1 - COSINE_WEIGHT) * evt_prob | |
| # # If THIS variant is better than our previous best, update | |
| # if current_cos > best_cosine: | |
| # best_identity = current_class | |
| # best_cosine = current_cos | |
| # best_hybrid = current_hybrid | |
| # best_second = current_second | |
| # # LOGGING FOR DEBUGGING | |
| # print(f"\n--- TTA Max-Score Debug ---") | |
| # print(f"Final Predicted: {best_identity}") | |
| # print(f"Max Cosine Score: {best_cosine:.4f} (Threshold: {COSINE_THRESHOLD})") | |
| # print(f"Hybrid Score: {best_hybrid:.4f} (Threshold: {HYBRID_THRESHOLD})") | |
| # print(f"---------------------------\n") | |
| # if best_cosine < COSINE_THRESHOLD: | |
| # return 'unknown', best_cosine, 0 | |
| # if best_cosine - best_second < TOP2_MARGIN: | |
| # return 'unknown', best_cosine, 0 | |
| # if best_hybrid < HYBRID_THRESHOLD: | |
| # return 'unknown', best_cosine, best_hybrid | |
| # return best_identity, best_cosine, best_hybrid | |
| # # ============================== | |
| # # LOGIN SYSTEM | |
| # # ============================== | |
| # attempt_log = defaultdict(list) | |
| # def login(image_path, session_id='default', silent=False): | |
| # vec = embed_image(image_path) | |
| # if vec is None: | |
| # return {'status': 'error'} | |
| # identity, cos, hyb = predict_robust(vec) | |
| # if identity == 'unknown': | |
| # return {'status': 'denied', 'identity': None, 'score': cos} | |
| # return {'status': 'granted', 'identity': identity, 'score': hyb} | |
| # # ============================== | |
| # # REGISTRATION (UNCHANGED) | |
| # # ============================== | |
| # def _embed_for_registration(image_path): | |
| # img = cv2.imread(image_path) | |
| # if img is None: | |
| # return [] | |
| # pp = preprocess_iris(img) | |
| # if pp is None: | |
| # return [] | |
| # embs = [] | |
| # for v in augment_lighting_variants(pp): | |
| # emb = embed_array(v) | |
| # # Match gallery format: only L2 normalize, no mean_all/std_all standardization | |
| # emb = emb / (np.linalg.norm(emb) + 1e-10) | |
| # embs.append(emb) | |
| # return embs | |
| # # def register_person(person_label, image_paths, gallery_pkl_path=PKL_PATH, overwrite=False): | |
| # # global gallery, weibull_models | |
| # # new_embs = [] | |
| # # for p in image_paths: | |
| # # new_embs.extend(_embed_for_registration(p)) | |
| # # new_embs = np.asarray(new_embs) | |
| # # if person_label in gallery: | |
| # # gallery[person_label] = np.vstack([gallery[person_label], new_embs]) | |
| # # else: | |
| # # gallery[person_label] = new_embs | |
| # # # weibull_models[person_label] = (1, 0, 1) | |
| # # # FIXED β real Weibull fit on actual embedding distances | |
| # # mean_vec = gallery[person_label].mean(axis=0) | |
| # # dists = np.linalg.norm(gallery[person_label] - mean_vec, axis=1) | |
| # # if len(dists) >= 3: | |
| # # # Need at least 3 points to fit Weibull reliably | |
| # # tail_size = max(3, int(0.3 * len(dists))) # use top 30% of distances | |
| # # tail = np.sort(dists)[-tail_size:] | |
| # # shape, loc, scale = weibull_min.fit(tail, floc=0) | |
| # # weibull_models[person_label] = (shape, loc, scale) | |
| # # print(f"β Weibull fitted for {person_label}: shape={shape:.3f}, scale={scale:.3f}") | |
| # # else: | |
| # # # Fallback if somehow less than 3 embeddings | |
| # # weibull_models[person_label] = (1, 0, 1) | |
| # # print(f"β οΈ Not enough embeddings for Weibull fit, using fallback") | |
| # # with open(gallery_pkl_path, 'wb') as f: | |
| # # pickle.dump({ | |
| # # 'gallery': gallery, | |
| # # 'weibull_models': weibull_models, | |
| # # 'mean_all': mean_all, | |
| # # 'std_all': std_all | |
| # # }, f) | |
| # # # UPLOAD TO HUGGING FACE | |
| # # try: | |
| # # print(f"π€ Uploading updated gallery to Hugging Face...") | |
| # # api = HfApi() | |
| # # api.upload_file( | |
| # # path_or_fileobj=gallery_pkl_path, | |
| # # path_in_repo=HF_FILENAME, | |
| # # repo_id=HF_REPO_ID, | |
| # # repo_type="model" | |
| # # ) | |
| # # print("β Gallery updated on Hugging Face!") | |
| # # except Exception as e: | |
| # # print(f"β Failed to upload to Hugging Face: {e}") | |
| # # return {'status': 'success', 'identity': person_label} | |
| # def register_person(person_label, image_paths, gallery_pkl_path=PKL_PATH, overwrite=False): | |
| # global gallery, weibull_models | |
| # new_embs = [] | |
| # for p in image_paths: | |
| # new_embs.extend(_embed_for_registration(p)) | |
| # new_embs = np.asarray(new_embs) | |
| # if person_label in gallery: | |
| # gallery[person_label] = np.vstack([gallery[person_label], new_embs]) | |
| # else: | |
| # gallery[person_label] = new_embs | |
| # # FIXED β real Weibull fit on actual embedding distances | |
| # mean_vec = gallery[person_label].mean(axis=0) | |
| # dists = np.linalg.norm(gallery[person_label] - mean_vec, axis=1) | |
| # if len(dists) >= 3: | |
| # # Need at least 3 points to fit Weibull reliably | |
| # tail_size = max(3, int(0.3 * len(dists))) | |
| # tail = np.sort(dists)[-tail_size:] | |
| # shape, loc, scale = weibull_min.fit(tail, floc=0) | |
| # weibull_models[person_label] = (shape, loc, scale) | |
| # print(f"β Weibull fitted for {person_label}: shape={shape:.3f}, scale={scale:.3f}") | |
| # else: | |
| # # Fallback if somehow less than 3 embeddings | |
| # weibull_models[person_label] = (1, 0, 1) | |
| # print(f"β οΈ Not enough embeddings for Weibull fit, using fallback") | |
| # with open(gallery_pkl_path, 'wb') as f: | |
| # pickle.dump({ | |
| # 'gallery': gallery, | |
| # 'weibull_models': weibull_models, | |
| # 'mean_all': mean_all, | |
| # 'std_all': std_all | |
| # }, f) | |
| # # UPLOAD TO HUGGING FACE | |
| # try: | |
| # print(f"π€ Uploading updated gallery to Hugging Face...") | |
| # api = HfApi() | |
| # api.upload_file( | |
| # path_or_fileobj=gallery_pkl_path, | |
| # path_in_repo=HF_FILENAME, | |
| # repo_id=HF_REPO_ID, | |
| # repo_type="model" | |
| # ) | |
| # print("β Gallery updated on Hugging Face!") | |
| # except Exception as e: | |
| # print(f"β Failed to upload to Hugging Face: {e}") | |
| # return {'status': 'success', 'identity': person_label} | |
| # print("β iris_recognition ready") | |
| # # # import numpy as np | |
| # # # import cv2 | |
| # # # from sklearn.metrics.pairwise import cosine_similarity | |
| # # # from scipy.stats import weibull_min | |
| # # # from tensorflow.keras.models import load_model | |
| # # # from tensorflow.keras.applications.resnet import preprocess_input | |
| # # # # from config import MODEL_PATH, IMG_SIZE, COSINE_WEIGHT, COSINE_THRESHOLD, HYBRID_THRESHOLD | |
| # # # from config import Config | |
| # # # from gallery import load_gallery | |
| # # # import os | |
| # # # from huggingface_hub import hf_hub_download | |
| # # # MODEL_REPO = "Omamaa12/iris-models" | |
| # # # model_path = hf_hub_download( | |
| # # # repo_id=MODEL_REPO, | |
| # # # filename="resnet50_imagenet.h5", | |
| # # # token=os.getenv("HF_TOKEN") | |
| # # # ) | |
| # # # base_model = load_model(model_path) | |
| # # # # Load model | |
| # # # # base_model = load_model( Config.MODEL_PATH) | |
| # # # # Load gallery | |
| # # # gallery_data = load_gallery() | |
| # # # gallery = gallery_data["gallery"] | |
| # # # weibull_models = gallery_data["weibull_models"] | |
| # # # mean_all = gallery_data["mean_all"] | |
| # # # std_all = gallery_data["std_all"] | |
| # # # # def embed_image(path): | |
| # # # # img = cv2.imread(path) | |
| # # # # h, w = img.shape[:2] | |
| # # # # crop = img[h//4:3*h//4, w//4:3*w//4] | |
| # # # # img = cv2.resize(crop, Config.IMG_SIZE) | |
| # # # # if len(img.shape) == 2 or img.shape[2] == 1: | |
| # # # # img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) | |
| # # # # img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) | |
| # # # # img_prep = preprocess_input(np.expand_dims(img.astype(np.float32), axis=0)) | |
| # # # # emb = base_model.predict(img_prep, verbose=0).flatten() | |
| # # # # emb = (emb - mean_all) / std_all | |
| # # # # emb = emb / (np.linalg.norm(emb)+1e-10) | |
| # # # # return emb | |
| # # # # def predict_hybrid(vec): | |
| # # # # sims = {c: cosine_similarity(emb, vec.reshape(1,-1)).max() for c, emb in gallery.items()} | |
| # # # # pred_class = max(sims, key=sims.get) | |
| # # # # cosine_score = sims[pred_class] | |
| # # # # if cosine_score < Config.COSINE_THRESHOLD: | |
| # # # # return "unknown", cosine_score | |
| # # # # mean_vec = gallery[pred_class].mean(axis=0) | |
| # # # # dist = np.linalg.norm(vec - mean_vec) | |
| # # # # shape, loc, scale = weibull_models[pred_class] | |
| # # # # evt_prob = 1 - weibull_min.cdf(dist, shape, loc=loc, scale=scale) | |
| # # # # hybrid = Config.COSINE_WEIGHT * cosine_score + (1 - Config.COSINE_WEIGHT) * evt_prob | |
| # # # # if hybrid < Config.HYBRID_THRESHOLD: | |
| # # # # return "unknown", hybrid | |
| # # # # return pred_class, hybrid | |
| # # # def embed_image(path): | |
| # # # img = cv2.imread(path) | |
| # # # if img is None: | |
| # # # raise ValueError(f"Cannot read image: {path}") | |
| # # # h, w = img.shape[:2] | |
| # # # crop = img[h//4:3*h//4, w//4:3*w//4] | |
| # # # img = cv2.resize(crop, Config.IMG_SIZE) | |
| # # # if len(img.shape) == 2 or img.shape[2] == 1: | |
| # # # img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) | |
| # # # img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) | |
| # # # img_prep = preprocess_input(np.expand_dims(img.astype(np.float32), axis=0)) | |
| # # # emb = base_model.predict(img_prep, verbose=0).flatten() | |
| # # # emb = (emb - mean_all) / std_all | |
| # # # emb = emb / (np.linalg.norm(emb) + 1e-10) | |
| # # # return emb | |
| # # # def predict_hybrid(vec): | |
| # # # sims = { | |
| # # # c: cosine_similarity(emb, vec.reshape(1, -1)).max() | |
| # # # for c, emb in gallery.items() | |
| # # # } | |
| # # # pred_class = max(sims, key=sims.get) | |
| # # # cosine_score = sims[pred_class] | |
| # # # if cosine_score < Config.COSINE_THRESHOLD: | |
| # # # return "unknown", cosine_score | |
| # # # mean_vec = gallery[pred_class].mean(axis=0) | |
| # # # dist = np.linalg.norm(vec - mean_vec) | |
| # # # shape, loc, scale = weibull_models[pred_class] | |
| # # # evt_prob = 1 - weibull_min.cdf(dist, shape, loc=loc, scale=scale) | |
| # # # hybrid = Config.COSINE_WEIGHT * cosine_score + (1 - Config.COSINE_WEIGHT) * evt_prob | |
| # # # if hybrid < Config.HYBRID_THRESHOLD: | |
| # # # return "unknown", hybrid | |
| # # # return pred_class, hybrid | |
| # # # import os | |
| # # # import numpy as np | |
| # # # import cv2 | |
| # # # from sklearn.metrics.pairwise import cosine_similarity | |
| # # # from scipy.stats import weibull_min | |
| # # # from tensorflow.keras.models import load_model | |
| # # # from tensorflow.keras.applications.resnet import preprocess_input | |
| # # # from huggingface_hub import hf_hub_download | |
| # # # from config import Config | |
| # # # from gallery import load_gallery | |
| # # import os | |
| # # import numpy as np | |
| # # import cv2 | |
| # # from sklearn.metrics.pairwise import cosine_similarity | |
| # # from scipy.stats import weibull_min | |
| # # from tensorflow.keras.applications import ResNet50 # β add this | |
| # # from tensorflow.keras.applications.resnet import preprocess_input | |
| # # from huggingface_hub import hf_hub_download | |
| # # from config import Config | |
| # # from gallery import load_gallery | |
| # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # # HuggingFace repo (set HF_TOKEN env var if repo is private) | |
| # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # MODEL_REPO = "Omamaa12/iris-models" | |
| # # HF_TOKEN = os.getenv("HF_TOKEN") | |
| # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # # Load ResNet50 from HuggingFace | |
| # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # # print("β³ Downloading ResNet50 from HuggingFaceβ¦") | |
| # # # _resnet_path = hf_hub_download( | |
| # # # repo_id=MODEL_REPO, | |
| # # # filename="resnet50_imagenet.keras", | |
| # # # # token=HF_TOKEN, | |
| # # # ) | |
| # # # # base_model = load_model(_resnet_path) | |
| # # # # base_model = load_model(_resnet_path, compile=False, safe_mode=False) | |
| # # # base_model = load_model( | |
| # # # _resnet_path, | |
| # # # compile=False, | |
| # # # custom_objects={} | |
| # # # ) | |
| # # # print("β ResNet50 ready.") | |
| # # print("β³ Building ResNet50 with ImageNet weightsβ¦") | |
| # # base_model = ResNet50(weights='imagenet', include_top=False, pooling='avg') | |
| # # print("β ResNet50 ready.") | |
| # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # # Load gallery from HuggingFace | |
| # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # print("β³ Downloading iris gallery from HuggingFaceβ¦") | |
| # # _gallery_path = hf_hub_download( | |
| # # repo_id=MODEL_REPO, | |
| # # filename="iris_gallery_fixed.pkl", | |
| # # token=HF_TOKEN, | |
| # # ) | |
| # # # Temporarily point Config.GALLERY_PATH to the downloaded file so | |
| # # # gallery.py's load_gallery() can find it without modification. | |
| # # Config.GALLERY_PATH = _gallery_path | |
| # # gallery_data = load_gallery() | |
| # # gallery = gallery_data["gallery"] # {class: np.array of normed embeddings} | |
| # # weibull_models = gallery_data["weibull_models"] # {class: (shape, loc, scale)} | |
| # # mean_all = gallery_data["mean_all"] | |
| # # std_all = gallery_data["std_all"] | |
| # # print(f"β Gallery loaded β {len(gallery)} identities.") | |
| # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # # Feature extraction | |
| # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # def embed_image(path): | |
| # # img = cv2.imread(path) | |
| # # if img is None: | |
| # # raise ValueError(f"Cannot read image: {path}") | |
| # # h, w = img.shape[:2] | |
| # # crop = img[h//4:3*h//4, w//4:3*w//4] | |
| # # img = cv2.resize(crop, Config.IMG_SIZE) | |
| # # # if len(img.shape) == 2 or img.shape[2] == 1: | |
| # # # img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) | |
| # # if len(img.shape) == 2 or (len(img.shape) == 3 and img.shape[2] == 1): | |
| # # img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) # make it 3ch BGR first | |
| # # img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) | |
| # # img_arr = preprocess_input(np.expand_dims(img.astype(np.float32), axis=0)) | |
| # # emb = base_model.predict(img_arr, verbose=0).flatten() | |
| # # emb = (emb - mean_all) / std_all | |
| # # emb = emb / (np.linalg.norm(emb) + 1e-10) | |
| # # return emb | |
| # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # # Hybrid prediction (cosine + Weibull EVT) | |
| # # # βββββββββββββββββββββββββββββββββββββββββ | |
| # # def predict_hybrid(vec): | |
| # # sims = { | |
| # # c: cosine_similarity(emb, vec.reshape(1, -1)).max() | |
| # # for c, emb in gallery.items() | |
| # # } | |
| # # pred_class = max(sims, key=sims.get) | |
| # # cosine_score = sims[pred_class] | |
| # # if cosine_score < Config.COSINE_THRESHOLD: | |
| # # return "unknown", cosine_score | |
| # # mean_vec = gallery[pred_class].mean(axis=0) | |
| # # dist = np.linalg.norm(vec - mean_vec) | |
| # # shape, loc, scale = weibull_models[pred_class] | |
| # # evt_prob = 1 - weibull_min.cdf(dist, shape, loc=loc, scale=scale) | |
| # # hybrid = Config.COSINE_WEIGHT * cosine_score + (1 - Config.COSINE_WEIGHT) * evt_prob | |
| # # if hybrid < Config.HYBRID_THRESHOLD: | |
| # # return "unknown", hybrid | |
| # # return pred_class, hybrid | |
| # import os | |
| # import cv2 | |
| # import time | |
| # import csv | |
| # import pickle | |
| # import numpy as np | |
| # from collections import defaultdict | |
| # from datetime import datetime | |
| # from scipy.stats import weibull_min | |
| # from sklearn.metrics.pairwise import cosine_similarity | |
| # from tensorflow.keras.applications import ResNet50 | |
| # from tensorflow.keras.applications.resnet import preprocess_input | |
| # from huggingface_hub import hf_hub_download, HfApi | |
| # # ============================== | |
| # # MODEL LOAD | |
| # # ============================== | |
| # model = ResNet50(weights='imagenet', include_top=False, pooling='avg') | |
| # os.makedirs('static/debug', exist_ok=True) | |
| # # ============================== | |
| # # PREPROCESSING | |
| # # ============================== | |
| # IMG_SIZE = (224, 224) | |
| # def normalize_lighting(img): | |
| # """ | |
| # Standard illumination normalization to maintain gallery compatibility. | |
| # """ | |
| # if img is None: return None | |
| # gray_mean = np.mean(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)) | |
| # gamma = np.log(128) / (np.log(gray_mean + 1e-5)) | |
| # gamma = np.clip(gamma, 0.4, 2.5) | |
| # lut = np.array([((i / 255.0) ** (1.0 / gamma)) * 255 for i in range(256)], dtype=np.uint8) | |
| # img = cv2.LUT(img, lut) | |
| # lab = cv2.cvtColor(img, cv2.COLOR_BGR2Lab) | |
| # l, a, b = cv2.split(lab) | |
| # clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) | |
| # l = clahe.apply(l) | |
| # lab = cv2.merge((l, a, b)) | |
| # img = cv2.cvtColor(lab, cv2.COLOR_Lab2BGR) | |
| # return img | |
| # def _sharpen(img): | |
| # kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]]) | |
| # return cv2.filter2D(img, -1, kernel) | |
| # def _high_contrast(img): | |
| # lab = cv2.cvtColor(img, cv2.COLOR_BGR2Lab) | |
| # l, a, b = cv2.split(lab) | |
| # # Match the registration limit (5.0) | |
| # clahe = cv2.createCLAHE(clipLimit=5.0, tileGridSize=(8, 8)) | |
| # l = clahe.apply(l) | |
| # return cv2.cvtColor(cv2.merge((l, a, b)), cv2.COLOR_Lab2BGR) | |
| # def preprocess_iris(img): | |
| # if img is None: | |
| # return None | |
| # # 1. INITIAL CROP (Middle 50%) | |
| # h, w = img.shape[:2] | |
| # img = img[h // 4: 3 * h // 4, w // 4: 3 * w // 4] | |
| # cv2.imwrite('static/debug/1_initial_crop.png', img) | |
| # if len(img.shape) == 2 or img.shape[2] == 1: | |
| # img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) | |
| # # 2. LIGHTING NORMALIZATION | |
| # img = normalize_lighting(img) | |
| # cv2.imwrite('static/debug/2_normalized.png', img) | |
| # # 3. FINAL RESIZE | |
| # img = cv2.resize(img, IMG_SIZE) | |
| # cv2.imwrite('static/debug/3_final_input.png', img) | |
| # img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) | |
| # return img | |
| # # ============================== | |
| # # EMBEDDING | |
| # # ============================== | |
| # # def augment_lighting_variants(img): | |
| # # """ | |
| # # Creates a diverse set of environmental variants for registration. | |
| # # These are added to the gallery ONLY for new registrations. | |
| # # """ | |
| # # variants = [img] | |
| # # # 1. Brightness variants (stronger range) | |
| # # variants.append(np.clip(img.astype(np.float32) * 1.6, 0, 255).astype(np.uint8)) | |
| # # variants.append(np.clip(img.astype(np.float32) * 0.4, 0, 255).astype(np.uint8)) | |
| # # # 2. High Contrast (High CLAHE) | |
| # # lab = cv2.cvtColor(cv2.cvtColor(img, cv2.COLOR_RGB2BGR), cv2.COLOR_BGR2Lab) | |
| # # l, a, b = cv2.split(lab) | |
| # # clahe_high = cv2.createCLAHE(clipLimit=5.0, tileGridSize=(8, 8)) | |
| # # l = clahe_high.apply(l) | |
| # # lab = cv2.merge((l, a, b)) | |
| # # variants.append(cv2.cvtColor(cv2.cvtColor(lab, cv2.COLOR_Lab2BGR), cv2.COLOR_BGR2RGB)) | |
| # # # 3. Sharpening (Added as an augmentation variant only) | |
| # # kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]]) | |
| # # sharpened = cv2.filter2D(img, -1, kernel) | |
| # # variants.append(sharpened) | |
| # # # 4. Blur variant (simulates slight out-of-focus) | |
| # # variants.append(cv2.GaussianBlur(img, (3, 3), 0)) | |
| # # # 5. Noise variant (simulates sensor noise) | |
| # # noise = np.random.normal(0, 8, img.shape).astype(np.int16) | |
| # # variants.append(np.clip(img.astype(np.int16) + noise, 0, 255).astype(np.uint8)) | |
| # # return variants | |
| # def augment_lighting_variants(img): | |
| # """ | |
| # Creates a diverse set of environmental variants for registration. | |
| # These are added to the gallery ONLY for new registrations. | |
| # """ | |
| # variants = [] | |
| # # Ensure debug directory exists | |
| # os.makedirs('static/debug', exist_ok=True) | |
| # # 0. Original preprocessed image | |
| # variants.append(img) | |
| # cv2.imwrite('static/debug/reg_0_original.png', cv2.cvtColor(img, cv2.COLOR_RGB2BGR)) # Change: Save original | |
| # # 1. Brightness variants (stronger range) | |
| # bright = np.clip(img.astype(np.float32) * 1.6, 0, 255).astype(np.uint8) | |
| # variants.append(bright) | |
| # cv2.imwrite('static/debug/reg_1_bright.png', cv2.cvtColor(bright, cv2.COLOR_RGB2BGR)) # Change: Save bright variant | |
| # dark = np.clip(img.astype(np.float32) * 0.4, 0, 255).astype(np.uint8) | |
| # variants.append(dark) | |
| # cv2.imwrite('static/debug/reg_1_dark.png', cv2.cvtColor(dark, cv2.COLOR_RGB2BGR)) # Change: Save dark variant | |
| # # 2. High Contrast (High CLAHE) | |
| # lab = cv2.cvtColor(cv2.cvtColor(img, cv2.COLOR_RGB2BGR), cv2.COLOR_BGR2Lab) | |
| # l, a, b = cv2.split(lab) | |
| # clahe_high = cv2.createCLAHE(clipLimit=5.0, tileGridSize=(8, 8)) | |
| # l = clahe_high.apply(l) | |
| # lab = cv2.merge((l, a, b)) | |
| # hc = cv2.cvtColor(cv2.cvtColor(lab, cv2.COLOR_Lab2BGR), cv2.COLOR_BGR2RGB) | |
| # variants.append(hc) | |
| # cv2.imwrite('static/debug/reg_2_high_contrast.png', cv2.cvtColor(hc, cv2.COLOR_RGB2BGR)) # Change: Save high contrast variant | |
| # # 3. Sharpening (Added as an augmentation variant only) | |
| # kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]]) | |
| # sharpened = cv2.filter2D(img, -1, kernel) | |
| # variants.append(sharpened) | |
| # cv2.imwrite('static/debug/reg_3_sharpened.png', cv2.cvtColor(sharpened, cv2.COLOR_RGB2BGR)) # Change: Save sharpened variant | |
| # # 4. Blur variant (simulates slight out-of-focus) | |
| # blurred = cv2.GaussianBlur(img, (3, 3), 0) | |
| # variants.append(blurred) | |
| # cv2.imwrite('static/debug/reg_4_blurred.png', cv2.cvtColor(blurred, cv2.COLOR_RGB2BGR)) # Change: Save blurred variant | |
| # # 5. Noise variant (simulates sensor noise) | |
| # noise = np.random.normal(0, 8, img.shape).astype(np.int16) | |
| # noisy = np.clip(img.astype(np.int16) + noise, 0, 255).astype(np.uint8) | |
| # variants.append(noisy) | |
| # cv2.imwrite('static/debug/reg_5_noisy.png', cv2.cvtColor(noisy, cv2.COLOR_RGB2BGR)) # Change: Save noisy variant | |
| # return variants | |
| # def embed_array(img_rgb): | |
| # arr = preprocess_input(np.expand_dims(img_rgb.astype(np.float32), axis=0)) | |
| # return model.predict(arr, verbose=0).flatten() | |
| # # ============================== | |
| # # LOAD GALLERY (FROM HUGGING FACE) | |
| # # ============================== | |
| # HF_REPO_ID = "Omamaa12/iris-models" | |
| # HF_FILENAME = "iris_gallery_robustt.pkl" | |
| # PKL_PATH = os.path.join('models', HF_FILENAME) | |
| # def sync_gallery_from_hf(): | |
| # """Downloads the latest gallery from Hugging Face.""" | |
| # print(f"β³ Syncing gallery from Hugging Face ({HF_REPO_ID})...") | |
| # try: | |
| # # Download to the models folder | |
| # downloaded_path = hf_hub_download( | |
| # repo_id=HF_REPO_ID, | |
| # filename=HF_FILENAME, | |
| # repo_type="model", | |
| # local_dir="models", | |
| # local_dir_use_symlinks=False, | |
| # force_download=True, | |
| # ) | |
| # print(f"β Gallery synced: {downloaded_path}") | |
| # return downloaded_path | |
| # except Exception as e: | |
| # print(f"β οΈ HF Sync failed, using local fallback: {e}") | |
| # return PKL_PATH | |
| # # Sync on startup | |
| # PKL_PATH = sync_gallery_from_hf() | |
| # if os.path.exists(PKL_PATH): | |
| # with open(PKL_PATH, 'rb') as f: | |
| # data = pickle.load(f) | |
| # gallery = data['gallery'] | |
| # weibull_models = data['weibull_models'] | |
| # mean_all = data['mean_all'] | |
| # std_all = data['std_all'] | |
| # print(f"β Gallery loaded - {len(gallery)} identities.") | |
| # else: | |
| # print("β Gallery file not found! Initializing empty.") | |
| # gallery = {} | |
| # weibull_models = {} | |
| # mean_all = None # These should ideally be pre-set | |
| # std_all = None | |
| # # ============================== | |
| # # EMBED IMAGE (LOGIN) | |
| # # ============================== | |
| # ANGLE_AUGS = (-12, -6, 0, 6, 12) | |
| # def _embed_rgb(rgb_img): | |
| # arr = preprocess_input(np.expand_dims(rgb_img.astype(np.float32), axis=0)) | |
| # emb = model.predict(arr, verbose=0).flatten() | |
| # emb = (emb - mean_all) / std_all | |
| # emb = emb / (np.linalg.norm(emb) + 1e-10) | |
| # return emb | |
| # def embed_image(image_path): | |
| # """ | |
| # Extracts multiple embeddings (TTA variants). | |
| # Returns a list of vectors. | |
| # """ | |
| # img = cv2.imread(image_path) | |
| # if img is None: | |
| # return None | |
| # pp = preprocess_iris(img) | |
| # if pp is None: | |
| # return None | |
| # # TTA variants: Standard, Sharpened, High Contrast | |
| # s = _sharpen(pp) | |
| # hc = _high_contrast(pp) | |
| # cv2.imwrite('static/debug/tta_sharpened.png', s) | |
| # cv2.imwrite('static/debug/tta_high_contrast.png', hc) | |
| # tta_variants = [pp, s, hc] | |
| # h, w = pp.shape[:2] | |
| # center = (w // 2, h // 2) | |
| # final_vectors = [] | |
| # for v in tta_variants: | |
| # embs = [] | |
| # for angle in ANGLE_AUGS: | |
| # M = cv2.getRotationMatrix2D(center, angle, 1.0) | |
| # rot = cv2.warpAffine(v, M, (w, h), borderMode=cv2.BORDER_REFLECT_101) | |
| # embs.append(_embed_rgb(rot)) | |
| # # Average rotations for THIS variant | |
| # v_emb = np.mean(np.stack(embs), axis=0) | |
| # v_emb = v_emb / (np.linalg.norm(v_emb) + 1e-10) | |
| # final_vectors.append(v_emb) | |
| # return final_vectors | |
| # # ============================== | |
| # # PREDICTION | |
| # # ============================== | |
| # COSINE_THRESHOLD = 0.62 | |
| # # COSINE_THRESHOLD = 0.67 | |
| # COSINE_WEIGHT = 0.85 | |
| # # HYBRID_THRESHOLD = 0.55 | |
| # HYBRID_THRESHOLD = 0.63 | |
| # TOP2_MARGIN = 0.005 | |
| # def _class_similarity(class_embs, vec): | |
| # sims = cosine_similarity(class_embs, vec.reshape(1, -1)).ravel() | |
| # return float(np.mean(np.sort(sims)[-2:])) | |
| # def predict_robust(vectors): | |
| # """ | |
| # Matches multiple TTA vectors and takes the BEST (MAX) similarity. | |
| # """ | |
| # best_identity = 'unknown' | |
| # best_cosine = 0 | |
| # best_hybrid = 0 | |
| # best_second = 0 | |
| # # Try each TTA variant | |
| # for vec in vectors: | |
| # sims = {c: _class_similarity(emb, vec) for c, emb in gallery.items()} | |
| # sorted_sims = sorted(sims.items(), key=lambda x: x[1], reverse=True) | |
| # current_class, current_cos = sorted_sims[0] | |
| # current_second = sorted_sims[1][1] if len(sorted_sims) > 1 else 0 | |
| # mean_vec = gallery[current_class].mean(axis=0) | |
| # dist = np.linalg.norm(vec - mean_vec) | |
| # shape, loc, scale = weibull_models[current_class] | |
| # evt_prob = 1 - weibull_min.cdf(dist, shape, loc=loc, scale=scale) | |
| # current_hybrid = COSINE_WEIGHT * current_cos + (1 - COSINE_WEIGHT) * evt_prob | |
| # # If THIS variant is better than our previous best, update | |
| # if current_cos > best_cosine: | |
| # best_identity = current_class | |
| # best_cosine = current_cos | |
| # best_hybrid = current_hybrid | |
| # best_second = current_second | |
| # # LOGGING FOR DEBUGGING | |
| # print(f"\n--- TTA Max-Score Debug ---") | |
| # print(f"Final Predicted: {best_identity}") | |
| # print(f"Max Cosine Score: {best_cosine:.4f} (Threshold: {COSINE_THRESHOLD})") | |
| # print(f"Hybrid Score: {best_hybrid:.4f} (Threshold: {HYBRID_THRESHOLD})") | |
| # print(f"---------------------------\n") | |
| # if best_cosine < COSINE_THRESHOLD: | |
| # return 'unknown', best_cosine, 0 | |
| # if best_cosine - best_second < TOP2_MARGIN: | |
| # return 'unknown', best_cosine, 0 | |
| # if best_hybrid < HYBRID_THRESHOLD: | |
| # return 'unknown', best_cosine, best_hybrid | |
| # return best_identity, best_cosine, best_hybrid | |
| # # ============================== | |
| # # LOGIN SYSTEM | |
| # # ============================== | |
| # attempt_log = defaultdict(list) | |
| # def login(image_path, session_id='default', silent=False): | |
| # vec = embed_image(image_path) | |
| # if vec is None: | |
| # return {'status': 'error'} | |
| # identity, cos, hyb = predict_robust(vec) | |
| # if identity == 'unknown': | |
| # return {'status': 'denied', 'identity': None, 'score': cos} | |
| # return {'status': 'granted', 'identity': identity, 'score': hyb} | |
| # # ============================== | |
| # # REGISTRATION (UNCHANGED) | |
| # # ============================== | |
| # def _embed_for_registration(image_path): | |
| # img = cv2.imread(image_path) | |
| # if img is None: | |
| # return [] | |
| # pp = preprocess_iris(img) | |
| # if pp is None: | |
| # return [] | |
| # embs = [] | |
| # for v in augment_lighting_variants(pp): | |
| # emb = embed_array(v) | |
| # emb = (emb - mean_all) / (std_all + 1e-10) | |
| # emb = emb / (np.linalg.norm(emb) + 1e-10) | |
| # embs.append(emb) | |
| # return embs | |
| # # def register_person(person_label, image_paths, gallery_pkl_path=PKL_PATH, overwrite=False): | |
| # # global gallery, weibull_models | |
| # # new_embs = [] | |
| # # for p in image_paths: | |
| # # new_embs.extend(_embed_for_registration(p)) | |
| # # new_embs = np.asarray(new_embs) | |
| # # if person_label in gallery: | |
| # # gallery[person_label] = np.vstack([gallery[person_label], new_embs]) | |
| # # else: | |
| # # gallery[person_label] = new_embs | |
| # # # weibull_models[person_label] = (1, 0, 1) | |
| # # # FIXED β real Weibull fit on actual embedding distances | |
| # # mean_vec = gallery[person_label].mean(axis=0) | |
| # # dists = np.linalg.norm(gallery[person_label] - mean_vec, axis=1) | |
| # # if len(dists) >= 3: | |
| # # # Need at least 3 points to fit Weibull reliably | |
| # # tail_size = max(3, int(0.3 * len(dists))) # use top 30% of distances | |
| # # tail = np.sort(dists)[-tail_size:] | |
| # # shape, loc, scale = weibull_min.fit(tail, floc=0) | |
| # # weibull_models[person_label] = (shape, loc, scale) | |
| # # print(f"β Weibull fitted for {person_label}: shape={shape:.3f}, scale={scale:.3f}") | |
| # # else: | |
| # # # Fallback if somehow less than 3 embeddings | |
| # # weibull_models[person_label] = (1, 0, 1) | |
| # # print(f"β οΈ Not enough embeddings for Weibull fit, using fallback") | |
| # # with open(gallery_pkl_path, 'wb') as f: | |
| # # pickle.dump({ | |
| # # 'gallery': gallery, | |
| # # 'weibull_models': weibull_models, | |
| # # 'mean_all': mean_all, | |
| # # 'std_all': std_all | |
| # # }, f) | |
| # # # UPLOAD TO HUGGING FACE | |
| # # try: | |
| # # print(f"π€ Uploading updated gallery to Hugging Face...") | |
| # # api = HfApi() | |
| # # api.upload_file( | |
| # # path_or_fileobj=gallery_pkl_path, | |
| # # path_in_repo=HF_FILENAME, | |
| # # repo_id=HF_REPO_ID, | |
| # # repo_type="model" | |
| # # ) | |
| # # print("β Gallery updated on Hugging Face!") | |
| # # except Exception as e: | |
| # # print(f"β Failed to upload to Hugging Face: {e}") | |
| # # return {'status': 'success', 'identity': person_label} | |
| # def register_person(person_label, image_paths, gallery_pkl_path=PKL_PATH, overwrite=False): | |
| # global gallery, weibull_models | |
| # new_embs = [] | |
| # for p in image_paths: | |
| # new_embs.extend(_embed_for_registration(p)) | |
| # new_embs = np.asarray(new_embs) | |
| # if person_label in gallery: | |
| # gallery[person_label] = np.vstack([gallery[person_label], new_embs]) | |
| # else: | |
| # gallery[person_label] = new_embs | |
| # # FIXED β real Weibull fit on actual embedding distances | |
| # mean_vec = gallery[person_label].mean(axis=0) | |
| # dists = np.linalg.norm(gallery[person_label] - mean_vec, axis=1) | |
| # if len(dists) >= 3: | |
| # # Need at least 3 points to fit Weibull reliably | |
| # tail_size = max(3, int(0.3 * len(dists))) | |
| # tail = np.sort(dists)[-tail_size:] | |
| # shape, loc, scale = weibull_min.fit(tail, floc=0) | |
| # weibull_models[person_label] = (shape, loc, scale) | |
| # print(f"β Weibull fitted for {person_label}: shape={shape:.3f}, scale={scale:.3f}") | |
| # else: | |
| # # Fallback if somehow less than 3 embeddings | |
| # weibull_models[person_label] = (1, 0, 1) | |
| # print(f"β οΈ Not enough embeddings for Weibull fit, using fallback") | |
| # with open(gallery_pkl_path, 'wb') as f: | |
| # pickle.dump({ | |
| # 'gallery': gallery, | |
| # 'weibull_models': weibull_models, | |
| # 'mean_all': mean_all, | |
| # 'std_all': std_all | |
| # }, f) | |
| # # UPLOAD TO HUGGING FACE | |
| # try: | |
| # print(f"π€ Uploading updated gallery to Hugging Face...") | |
| # api = HfApi() | |
| # api.upload_file( | |
| # path_or_fileobj=gallery_pkl_path, | |
| # path_in_repo=HF_FILENAME, | |
| # repo_id=HF_REPO_ID, | |
| # repo_type="model" | |
| # ) | |
| # print("β Gallery updated on Hugging Face!") | |
| # except Exception as e: | |
| # print(f"β Failed to upload to Hugging Face: {e}") | |
| # return {'status': 'success', 'identity': person_label} | |
| # print("β iris_recognition ready") | |
| import os | |
| import cv2 | |
| import time | |
| import csv | |
| import pickle | |
| import numpy as np | |
| from collections import defaultdict | |
| from datetime import datetime | |
| from scipy.stats import weibull_min | |
| from sklearn.metrics.pairwise import cosine_similarity | |
| from tensorflow.keras.applications import ResNet50 | |
| from tensorflow.keras.applications.resnet import preprocess_input | |
| from huggingface_hub import hf_hub_download, HfApi | |
| # ============================== | |
| # MODEL LOAD | |
| # ============================== | |
| model = ResNet50(weights='imagenet', include_top=False, pooling='avg') | |
| os.makedirs('static/debug', exist_ok=True) | |
| # ============================== | |
| # PREPROCESSING | |
| # ============================== | |
| IMG_SIZE = (224, 224) | |
| def normalize_lighting(img): | |
| """ | |
| Standard illumination normalization to maintain gallery compatibility. | |
| """ | |
| if img is None: return None | |
| gray_mean = np.mean(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)) | |
| gamma = np.log(128) / (np.log(gray_mean + 1e-5)) | |
| gamma = np.clip(gamma, 0.4, 2.5) | |
| lut = np.array([((i / 255.0) ** (1.0 / gamma)) * 255 for i in range(256)], dtype=np.uint8) | |
| img = cv2.LUT(img, lut) | |
| lab = cv2.cvtColor(img, cv2.COLOR_BGR2Lab) | |
| l, a, b = cv2.split(lab) | |
| clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) | |
| l = clahe.apply(l) | |
| lab = cv2.merge((l, a, b)) | |
| img = cv2.cvtColor(lab, cv2.COLOR_Lab2BGR) | |
| return img | |
| def _sharpen(img): | |
| kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]]) | |
| return cv2.filter2D(img, -1, kernel) | |
| # def _high_contrast(img): | |
| # lab = cv2.cvtColor(img, cv2.COLOR_BGR2Lab) | |
| # l, a, b = cv2.split(lab) | |
| # # Match the registration limit (5.0) | |
| # clahe = cv2.createCLAHE(clipLimit=5.0, tileGridSize=(8, 8)) | |
| # l = clahe.apply(l) | |
| # return cv2.cvtColor(cv2.merge((l, a, b)), cv2.COLOR_Lab2BGR) | |
| def preprocess_iris(img): | |
| if img is None: | |
| return None | |
| # 1. INITIAL CROP (Middle 50%) | |
| h, w = img.shape[:2] | |
| img = img[h // 4: 3 * h // 4, w // 4: 3 * w // 4] | |
| cv2.imwrite('static/debug/1_initial_crop.png', img) | |
| if len(img.shape) == 2 or img.shape[2] == 1: | |
| img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) | |
| # 2. LIGHTING NORMALIZATION | |
| img = normalize_lighting(img) | |
| cv2.imwrite('static/debug/2_normalized.png', img) | |
| # 3. FINAL RESIZE | |
| img = cv2.resize(img, IMG_SIZE) | |
| cv2.imwrite('static/debug/3_final_input.png', img) | |
| img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) | |
| return img | |
| # ============================== | |
| # EMBEDDING | |
| # ============================== | |
| # def augment_lighting_variants(img): | |
| # """ | |
| # Creates a diverse set of environmental variants for registration. | |
| # These are added to the gallery ONLY for new registrations. | |
| # """ | |
| # variants = [img] | |
| # # 1. Brightness variants (stronger range) | |
| # variants.append(np.clip(img.astype(np.float32) * 1.6, 0, 255).astype(np.uint8)) | |
| # variants.append(np.clip(img.astype(np.float32) * 0.4, 0, 255).astype(np.uint8)) | |
| # # 2. High Contrast (High CLAHE) | |
| # lab = cv2.cvtColor(cv2.cvtColor(img, cv2.COLOR_RGB2BGR), cv2.COLOR_BGR2Lab) | |
| # l, a, b = cv2.split(lab) | |
| # clahe_high = cv2.createCLAHE(clipLimit=5.0, tileGridSize=(8, 8)) | |
| # l = clahe_high.apply(l) | |
| # lab = cv2.merge((l, a, b)) | |
| # variants.append(cv2.cvtColor(cv2.cvtColor(lab, cv2.COLOR_Lab2BGR), cv2.COLOR_BGR2RGB)) | |
| # # 3. Sharpening (Added as an augmentation variant only) | |
| # kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]]) | |
| # sharpened = cv2.filter2D(img, -1, kernel) | |
| # variants.append(sharpened) | |
| # # 4. Blur variant (simulates slight out-of-focus) | |
| # variants.append(cv2.GaussianBlur(img, (3, 3), 0)) | |
| # # 5. Noise variant (simulates sensor noise) | |
| # noise = np.random.normal(0, 8, img.shape).astype(np.int16) | |
| # variants.append(np.clip(img.astype(np.int16) + noise, 0, 255).astype(np.uint8)) | |
| # return variants | |
| def augment_lighting_variants(img): | |
| """ | |
| Creates a diverse set of environmental variants for registration. | |
| These are added to the gallery ONLY for new registrations. | |
| """ | |
| variants = [] | |
| # Ensure debug directory exists | |
| os.makedirs('static/debug', exist_ok=True) | |
| # 0. Original preprocessed image | |
| variants.append(img) | |
| cv2.imwrite('static/debug/reg_0_original.png', cv2.cvtColor(img, cv2.COLOR_RGB2BGR)) # Change: Save original | |
| # 1. Brightness variants (stronger range) | |
| bright = np.clip(img.astype(np.float32) * 1.6, 0, 255).astype(np.uint8) | |
| variants.append(bright) | |
| cv2.imwrite('static/debug/reg_1_bright.png', cv2.cvtColor(bright, cv2.COLOR_RGB2BGR)) # Change: Save bright variant | |
| dark = np.clip(img.astype(np.float32) * 0.4, 0, 255).astype(np.uint8) | |
| variants.append(dark) | |
| cv2.imwrite('static/debug/reg_1_dark.png', cv2.cvtColor(dark, cv2.COLOR_RGB2BGR)) # Change: Save dark variant | |
| # 2. High Contrast (High CLAHE) | |
| lab = cv2.cvtColor(cv2.cvtColor(img, cv2.COLOR_RGB2BGR), cv2.COLOR_BGR2Lab) | |
| l, a, b = cv2.split(lab) | |
| clahe_high = cv2.createCLAHE(clipLimit=5.0, tileGridSize=(8, 8)) | |
| l = clahe_high.apply(l) | |
| lab = cv2.merge((l, a, b)) | |
| hc = cv2.cvtColor(cv2.cvtColor(lab, cv2.COLOR_Lab2BGR), cv2.COLOR_BGR2RGB) | |
| variants.append(hc) | |
| cv2.imwrite('static/debug/reg_2_high_contrast.png', cv2.cvtColor(hc, cv2.COLOR_RGB2BGR)) # Change: Save high contrast variant | |
| # 3. Sharpening (Added as an augmentation variant only) | |
| kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]]) | |
| sharpened = cv2.filter2D(img, -1, kernel) | |
| variants.append(sharpened) | |
| cv2.imwrite('static/debug/reg_3_sharpened.png', cv2.cvtColor(sharpened, cv2.COLOR_RGB2BGR)) # Change: Save sharpened variant | |
| # 4. Blur variant (simulates slight out-of-focus) | |
| blurred = cv2.GaussianBlur(img, (3, 3), 0) | |
| variants.append(blurred) | |
| cv2.imwrite('static/debug/reg_4_blurred.png', cv2.cvtColor(blurred, cv2.COLOR_RGB2BGR)) # Change: Save blurred variant | |
| # 5. Noise variant (simulates sensor noise) | |
| noise = np.random.normal(0, 8, img.shape).astype(np.int16) | |
| noisy = np.clip(img.astype(np.int16) + noise, 0, 255).astype(np.uint8) | |
| variants.append(noisy) | |
| cv2.imwrite('static/debug/reg_5_noisy.png', cv2.cvtColor(noisy, cv2.COLOR_RGB2BGR)) # Change: Save noisy variant | |
| return variants | |
| def embed_array(img_rgb): | |
| arr = preprocess_input(np.expand_dims(img_rgb.astype(np.float32), axis=0)) | |
| return model.predict(arr, verbose=0).flatten() | |
| # ============================== | |
| # LOAD GALLERY (FROM HUGGING FACE) | |
| # ============================== | |
| HF_REPO_ID = "Omamaa12/iris-models" | |
| HF_FILENAME = "iris_gallery_robustt.pkl" | |
| PKL_PATH = os.path.join('models', HF_FILENAME) | |
| def sync_gallery_from_hf(): | |
| """Downloads the latest gallery from Hugging Face.""" | |
| print(f"β³ Syncing gallery from Hugging Face ({HF_REPO_ID})...") | |
| try: | |
| # Download to the models folder | |
| downloaded_path = hf_hub_download( | |
| repo_id=HF_REPO_ID, | |
| filename=HF_FILENAME, | |
| repo_type="model", | |
| local_dir="models", | |
| local_dir_use_symlinks=False, | |
| force_download=True, | |
| ) | |
| print(f"β Gallery synced: {downloaded_path}") | |
| return downloaded_path | |
| except Exception as e: | |
| print(f"β οΈ HF Sync failed, using local fallback: {e}") | |
| return PKL_PATH | |
| # Sync on startup | |
| PKL_PATH = sync_gallery_from_hf() | |
| if os.path.exists(PKL_PATH): | |
| with open(PKL_PATH, 'rb') as f: | |
| data = pickle.load(f) | |
| gallery = data['gallery'] | |
| weibull_models = data['weibull_models'] | |
| mean_all = data['mean_all'] | |
| std_all = data['std_all'] | |
| print(f"β Gallery loaded - {len(gallery)} identities.") | |
| else: | |
| print("β Gallery file not found! Initializing empty.") | |
| gallery = {} | |
| weibull_models = {} | |
| mean_all = None # These should ideally be pre-set | |
| std_all = None | |
| # ============================== | |
| # EMBED IMAGE (LOGIN) | |
| # ============================== | |
| ANGLE_AUGS = (-12, -6, 0, 6, 12) | |
| def _embed_rgb(rgb_img): | |
| arr = preprocess_input(np.expand_dims(rgb_img.astype(np.float32), axis=0)) | |
| emb = model.predict(arr, verbose=0).flatten() | |
| emb = (emb - mean_all) / (std_all + 1e-10) # β ADD THIS LINE BACK | |
| emb = emb / (np.linalg.norm(emb) + 1e-10) | |
| return emb | |
| # def embed_image(image_path): | |
| # """ | |
| # Extracts multiple embeddings (TTA variants). | |
| # Returns a list of vectors. | |
| # """ | |
| # img = cv2.imread(image_path) | |
| # if img is None: | |
| # return None | |
| # pp = preprocess_iris(img) | |
| # if pp is None: | |
| # return None | |
| # # TTA variants: Standard, Sharpened, High Contrast | |
| # s = _sharpen(pp) | |
| # hc = _high_contrast(pp) | |
| # cv2.imwrite('static/debug/tta_sharpened.png', s) | |
| # cv2.imwrite('static/debug/tta_high_contrast.png', hc) | |
| # tta_variants = [pp, s, hc] | |
| # h, w = pp.shape[:2] | |
| # center = (w // 2, h // 2) | |
| # final_vectors = [] | |
| # for v in tta_variants: | |
| # embs = [] | |
| # for angle in ANGLE_AUGS: | |
| # M = cv2.getRotationMatrix2D(center, angle, 1.0) | |
| # rot = cv2.warpAffine(v, M, (w, h), borderMode=cv2.BORDER_REFLECT_101) | |
| # embs.append(_embed_rgb(rot)) | |
| # # Average rotations for THIS variant | |
| # v_emb = np.mean(np.stack(embs), axis=0) | |
| # v_emb = v_emb / (np.linalg.norm(v_emb) + 1e-10) | |
| # final_vectors.append(v_emb) | |
| # return final_vectors | |
| def _high_contrast(img): | |
| # img is RGB (from preprocess_iris) | |
| lab = cv2.cvtColor(cv2.cvtColor(img, cv2.COLOR_RGB2BGR), cv2.COLOR_BGR2Lab) | |
| l, a, b = cv2.split(lab) | |
| clahe = cv2.createCLAHE(clipLimit=5.0, tileGridSize=(8, 8)) | |
| l = clahe.apply(l) | |
| lab = cv2.merge((l, a, b)) | |
| return cv2.cvtColor(cv2.cvtColor(lab, cv2.COLOR_Lab2BGR), cv2.COLOR_BGR2RGB) | |
| def embed_image(image_path): | |
| """ | |
| Extracts multiple embeddings (TTA variants). | |
| Returns a list of vectors. | |
| """ | |
| img = cv2.imread(image_path) | |
| if img is None: | |
| return None | |
| pp = preprocess_iris(img) | |
| if pp is None: | |
| return None | |
| # TTA variants: now 6 to match registration gallery coverage | |
| s = _sharpen(pp) | |
| hc = _high_contrast(pp) | |
| bright = np.clip(pp.astype(np.float32) * 1.6, 0, 255).astype(np.uint8) | |
| dark = np.clip(pp.astype(np.float32) * 0.4, 0, 255).astype(np.uint8) | |
| blurred = cv2.GaussianBlur(pp, (3, 3), 0) | |
| cv2.imwrite('static/debug/tta_sharpened.png', s) | |
| cv2.imwrite('static/debug/tta_high_contrast.png', hc) | |
| # cv2.imwrite('static/debug/tta_bright.png', bright) | |
| # cv2.imwrite('static/debug/tta_dark.png', dark) | |
| # cv2.imwrite('static/debug/tta_blurred.png', blurred) | |
| tta_variants = [pp, bright, dark, hc, s, blurred] | |
| h, w = pp.shape[:2] | |
| center = (w // 2, h // 2) | |
| final_vectors = [] | |
| for v in tta_variants: | |
| embs = [] | |
| for angle in ANGLE_AUGS: | |
| M = cv2.getRotationMatrix2D(center, angle, 1.0) | |
| rot = cv2.warpAffine(v, M, (w, h), borderMode=cv2.BORDER_REFLECT_101) | |
| embs.append(_embed_rgb(rot)) | |
| # Average rotations for THIS variant | |
| v_emb = np.mean(np.stack(embs), axis=0) | |
| v_emb = v_emb / (np.linalg.norm(v_emb) + 1e-10) | |
| final_vectors.append(v_emb) | |
| return final_vectors | |
| # ============================== | |
| # PREDICTION | |
| # ============================== | |
| COSINE_THRESHOLD = 0.62 | |
| # COSINE_THRESHOLD = 0.67 | |
| COSINE_WEIGHT = 0.85 | |
| # HYBRID_THRESHOLD = 0.55 | |
| HYBRID_THRESHOLD = 0.63 | |
| TOP2_MARGIN = 0.005 | |
| def _class_similarity(class_embs, vec): | |
| sims = cosine_similarity(class_embs, vec.reshape(1, -1)).ravel() | |
| k = min(6, len(sims)) | |
| return float(np.mean(np.sort(sims)[-k:])) | |
| def predict_robust(vectors): | |
| """ | |
| Matches multiple TTA vectors and takes the BEST (MAX) similarity. | |
| """ | |
| best_identity = 'unknown' | |
| best_cosine = 0 | |
| best_hybrid = 0 | |
| best_second = 0 | |
| # Try each TTA variant | |
| for vec in vectors: | |
| sims = {c: _class_similarity(emb, vec) for c, emb in gallery.items()} | |
| sorted_sims = sorted(sims.items(), key=lambda x: x[1], reverse=True) | |
| current_class, current_cos = sorted_sims[0] | |
| current_second = sorted_sims[1][1] if len(sorted_sims) > 1 else 0 | |
| mean_vec = gallery[current_class].mean(axis=0) | |
| dist = np.linalg.norm(vec - mean_vec) | |
| shape, loc, scale = weibull_models[current_class] | |
| evt_prob = 1 - weibull_min.cdf(dist, shape, loc=loc, scale=scale) | |
| current_hybrid = COSINE_WEIGHT * current_cos + (1 - COSINE_WEIGHT) * evt_prob | |
| # If THIS variant is better than our previous best, update | |
| if current_cos > best_cosine: | |
| best_identity = current_class | |
| best_cosine = current_cos | |
| best_hybrid = current_hybrid | |
| best_second = current_second | |
| # LOGGING FOR DEBUGGING | |
| print(f"\n--- TTA Max-Score Debug ---") | |
| print(f"Final Predicted: {best_identity}") | |
| print(f"Max Cosine Score: {best_cosine:.4f} (Threshold: {COSINE_THRESHOLD})") | |
| print(f"Hybrid Score: {best_hybrid:.4f} (Threshold: {HYBRID_THRESHOLD})") | |
| print(f"---------------------------\n") | |
| if best_cosine < COSINE_THRESHOLD: | |
| return 'unknown', best_cosine, 0 | |
| if best_cosine - best_second < TOP2_MARGIN: | |
| return 'unknown', best_cosine, 0 | |
| if best_hybrid < HYBRID_THRESHOLD: | |
| return 'unknown', best_cosine, best_hybrid | |
| return best_identity, best_cosine, best_hybrid | |
| # ============================== | |
| # LOGIN SYSTEM | |
| # ============================== | |
| attempt_log = defaultdict(list) | |
| def login(image_path, session_id='default', silent=False): | |
| vec = embed_image(image_path) | |
| if vec is None: | |
| return {'status': 'error'} | |
| identity, cos, hyb = predict_robust(vec) | |
| if identity == 'unknown': | |
| return {'status': 'denied', 'identity': None, 'score': cos} | |
| return {'status': 'granted', 'identity': identity, 'score': hyb} | |
| # ============================== | |
| # REGISTRATION (UNCHANGED) | |
| # ============================== | |
| def _embed_for_registration(image_path): | |
| img = cv2.imread(image_path) | |
| if img is None: | |
| return [] | |
| pp = preprocess_iris(img) | |
| if pp is None: | |
| return [] | |
| embs = [] | |
| for v in augment_lighting_variants(pp): | |
| emb = embed_array(v) | |
| # Must match the notebook gallery pipeline exactly: z-score then L2 | |
| emb = (emb - mean_all) / (std_all + 1e-10) | |
| emb = emb / (np.linalg.norm(emb) + 1e-10) | |
| embs.append(emb) | |
| return embs | |
| # def register_person(person_label, image_paths, gallery_pkl_path=PKL_PATH, overwrite=False): | |
| # global gallery, weibull_models | |
| # new_embs = [] | |
| # for p in image_paths: | |
| # new_embs.extend(_embed_for_registration(p)) | |
| # new_embs = np.asarray(new_embs) | |
| # if person_label in gallery: | |
| # gallery[person_label] = np.vstack([gallery[person_label], new_embs]) | |
| # else: | |
| # gallery[person_label] = new_embs | |
| # # weibull_models[person_label] = (1, 0, 1) | |
| # # FIXED β real Weibull fit on actual embedding distances | |
| # mean_vec = gallery[person_label].mean(axis=0) | |
| # dists = np.linalg.norm(gallery[person_label] - mean_vec, axis=1) | |
| # if len(dists) >= 3: | |
| # # Need at least 3 points to fit Weibull reliably | |
| # tail_size = max(3, int(0.3 * len(dists))) # use top 30% of distances | |
| # tail = np.sort(dists)[-tail_size:] | |
| # shape, loc, scale = weibull_min.fit(tail, floc=0) | |
| # weibull_models[person_label] = (shape, loc, scale) | |
| # print(f"β Weibull fitted for {person_label}: shape={shape:.3f}, scale={scale:.3f}") | |
| # else: | |
| # # Fallback if somehow less than 3 embeddings | |
| # weibull_models[person_label] = (1, 0, 1) | |
| # print(f"β οΈ Not enough embeddings for Weibull fit, using fallback") | |
| # with open(gallery_pkl_path, 'wb') as f: | |
| # pickle.dump({ | |
| # 'gallery': gallery, | |
| # 'weibull_models': weibull_models, | |
| # 'mean_all': mean_all, | |
| # 'std_all': std_all | |
| # }, f) | |
| # # UPLOAD TO HUGGING FACE | |
| # try: | |
| # print(f"π€ Uploading updated gallery to Hugging Face...") | |
| # api = HfApi() | |
| # api.upload_file( | |
| # path_or_fileobj=gallery_pkl_path, | |
| # path_in_repo=HF_FILENAME, | |
| # repo_id=HF_REPO_ID, | |
| # repo_type="model" | |
| # ) | |
| # print("β Gallery updated on Hugging Face!") | |
| # except Exception as e: | |
| # print(f"β Failed to upload to Hugging Face: {e}") | |
| # return {'status': 'success', 'identity': person_label} | |
| def register_person(person_label, image_paths, gallery_pkl_path=PKL_PATH, overwrite=False): | |
| global gallery, weibull_models | |
| new_embs = [] | |
| for p in image_paths: | |
| new_embs.extend(_embed_for_registration(p)) | |
| new_embs = np.asarray(new_embs) | |
| if person_label in gallery: | |
| gallery[person_label] = np.vstack([gallery[person_label], new_embs]) | |
| else: | |
| gallery[person_label] = new_embs | |
| # FIXED β real Weibull fit on actual embedding distances | |
| mean_vec = gallery[person_label].mean(axis=0) | |
| dists = np.linalg.norm(gallery[person_label] - mean_vec, axis=1) | |
| if len(dists) >= 3: | |
| # Need at least 3 points to fit Weibull reliably | |
| tail_size = max(3, int(0.3 * len(dists))) | |
| tail = np.sort(dists)[-tail_size:] | |
| shape, loc, scale = weibull_min.fit(tail, floc=0) | |
| weibull_models[person_label] = (shape, loc, scale) | |
| print(f"β Weibull fitted for {person_label}: shape={shape:.3f}, scale={scale:.3f}") | |
| else: | |
| # Fallback if somehow less than 3 embeddings | |
| weibull_models[person_label] = (1, 0, 1) | |
| print(f"β οΈ Not enough embeddings for Weibull fit, using fallback") | |
| with open(gallery_pkl_path, 'wb') as f: | |
| pickle.dump({ | |
| 'gallery': gallery, | |
| 'weibull_models': weibull_models, | |
| 'mean_all': mean_all, | |
| 'std_all': std_all | |
| }, f) | |
| # UPLOAD TO HUGGING FACE | |
| try: | |
| print(f"π€ Uploading updated gallery to Hugging Face...") | |
| api = HfApi() | |
| api.upload_file( | |
| path_or_fileobj=gallery_pkl_path, | |
| path_in_repo=HF_FILENAME, | |
| repo_id=HF_REPO_ID, | |
| repo_type="model" | |
| ) | |
| print("β Gallery updated on Hugging Face!") | |
| except Exception as e: | |
| print(f"β Failed to upload to Hugging Face: {e}") | |
| return {'status': 'success', 'identity': person_label} | |
| print("β iris_recognition ready") |