{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "32b02faa-51d1-43d1-a906-6b3f4d07e804",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.metrics.pairwise import cosine_similarity\n",
    "from sklearn.metrics import roc_curve\n",
    "from joblib import Parallel, delayed\n",
    "from tqdm.notebook import tqdm\n",
    "from collections import Counter"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "3768f61e-3b91-4703-9ba2-de36ee3e6ebe",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import networkx as nx\n",
    "import seaborn as sns\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import pickle\n",
    "\n",
    "sns.set_theme()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "926ffa6e-9bf2-4393-80e0-1886d3b0de0f",
   "metadata": {},
   "source": [
    "#### FUNCTIONS to load data "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "998e70bd-faa8-4929-9208-d9006bce933c",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_embeddings(model):\n",
    "    return np.load(f'../RFW/embd_{model}.npy')\n",
    "def get_names():\n",
    "    return np.load('../RFW/x_names.npy')\n",
    "def get_ids():\n",
    "    return np.load('../RFW/x_ids.npy')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fa0fc869-9e19-482d-8edc-561f0fb68ae8",
   "metadata": {},
   "source": [
    "#### FUNCTIONS of score comparisons"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "67b475fa-5750-4547-b714-a46d2dcc209e",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_impostors(id_label):\n",
    "    # Generate the index matrix of same-id comparisons\n",
    "    comparison_matrix = (id_label[:, None] == id_label[None, :]).astype(int)\n",
    "    genuines_matrix = comparison_matrix.copy()\n",
    "    genuines_matrix[np.tril_indices(genuines_matrix.shape[0])] = 0\n",
    "    # Invert index matrix to get diff-id comparisons matrix\n",
    "    impostors_matrix = (1 - comparison_matrix)\n",
    "    impostors_matrix[np.tril_indices(impostors_matrix.shape[0])] = 0\n",
    "    return impostors_matrix, genuines_matrix"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "5cb9819a-652e-4319-bb95-f435185e5c1e",
   "metadata": {},
   "outputs": [],
   "source": [
    "def compute_eer_and_fnmr(genuine_scores, impostor_scores, fmr_targets=[0.01, 0.001]):\n",
    "    y_true = np.concatenate([np.ones_like(genuine_scores), np.zeros_like(impostor_scores)])\n",
    "    y_scores = np.concatenate([genuine_scores, impostor_scores])\n",
    "\n",
    "    fpr, tpr, thresholds = roc_curve(y_true, y_scores)\n",
    "\n",
    "    # Compute EER (where FPR ~= 1 - TPR)\n",
    "    fnr = 1 - tpr\n",
    "    eer_idx = np.nanargmin(np.abs(fpr - fnr))\n",
    "    eer = (fpr[eer_idx] + fnr[eer_idx]) / 2\n",
    "    eer_threshold = thresholds[eer_idx]\n",
    "\n",
    "    print(f\"\\n=== EER: {eer:.4f} at threshold {eer_threshold:.4f} ===\")\n",
    "\n",
    "    # Compute FNMR @ target FMRs\n",
    "    for target in fmr_targets:\n",
    "        idx = np.searchsorted(fpr, target)\n",
    "        if idx >= len(fnr):\n",
    "            print(f\"FMR {target:.4f} is out of range.\")\n",
    "            continue\n",
    "        print(f\"FNMR @ FMR={target:.4f}: {fnr[idx]:.4f} (threshold={thresholds[idx]:.4f})\")\n",
    "\n",
    "    return eer, eer_threshold"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "6d5aa6d9-c313-4168-8eb3-f65945c2611e",
   "metadata": {},
   "outputs": [],
   "source": [
    "def compute_eer_and_fnmr_bootstrap(genuine_scores, impostor_scores, fmr_targets=[0.01, 0.001], n_bootstrap=1000):\n",
    "    eers = []\n",
    "    fnmrs = {fmr: [] for fmr in fmr_targets}\n",
    "\n",
    "    rng = np.random.default_rng()\n",
    "\n",
    "    for _ in tqdm(range(n_bootstrap), desc=\"Bootstrapping\"):\n",
    "        # Sample with replacement\n",
    "        gen_sample = rng.choice(genuine_scores, size=len(genuine_scores), replace=True)\n",
    "        imp_sample = rng.choice(impostor_scores, size=len(impostor_scores), replace=True)\n",
    "\n",
    "        y_true = np.concatenate([np.ones_like(gen_sample), np.zeros_like(imp_sample)])\n",
    "        y_scores = np.concatenate([gen_sample, imp_sample])\n",
    "\n",
    "        fpr, tpr, thresholds = roc_curve(y_true, y_scores)\n",
    "        fnr = 1 - tpr\n",
    "\n",
    "        # EER\n",
    "        eer_idx = np.nanargmin(np.abs(fpr - fnr))\n",
    "        eer = (fpr[eer_idx] + fnr[eer_idx]) / 2\n",
    "        eers.append(eer)\n",
    "\n",
    "        for fmr in fmr_targets:\n",
    "            idx = np.searchsorted(fpr, fmr)\n",
    "            if idx < len(fnr):\n",
    "                fnmrs[fmr].append(fnr[idx])\n",
    "            else:\n",
    "                fnmrs[fmr].append(np.nan)\n",
    "\n",
    "    # Report mean and 95% CI\n",
    "    print(\"\\n=== Bootstrap Results ===\")\n",
    "    print(f\"EER: {np.mean(eers):.4f} (95% CI: {np.percentile(eers, 2.5):.4f} - {np.percentile(eers, 97.5):.4f})\")\n",
    "    for fmr in fmr_targets:\n",
    "        scores = np.array(fnmrs[fmr])\n",
    "        print(f\"FNMR @ FMR={fmr:.4f}: {np.nanmean(scores):.4f} (95% CI: {np.nanpercentile(scores, 2.5):.4f} - {np.nanpercentile(scores, 97.5):.4f})\")\n",
    "\n",
    "    return eers, fnmrs\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fd03f749-36d6-45ed-9026-3c3a8de64c1e",
   "metadata": {},
   "source": [
    "### CLUSTER FILTERING"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "003f69dd-4a8a-4274-9060-33989a412c21",
   "metadata": {},
   "outputs": [],
   "source": [
    "def biased_clusters(clusters, scores, impostors_matrix, label_id, min_ids=30, threshold=0.1):\n",
    "    filtered = []\n",
    "    indices = []\n",
    "    for i, val in enumerate(clusters):\n",
    "        B = impostors_matrix[val][:,val]\n",
    "        if np.any(B != 0) & (np.unique(label_id[val]).shape[0] >= min_ids):\n",
    "            _, first_indices = np.unique(label_id[val], return_index=True)\n",
    "            new_val = list(np.array(val)[first_indices])\n",
    "            A = scores[new_val][:,new_val]\n",
    "            B = impostors_matrix[new_val][:,new_val]\n",
    "            a = np.mean(A[B == 1])\n",
    "            if (a > threshold):\n",
    "                filtered.append(val)\n",
    "                indices.append(i)\n",
    "                # print(f\"{i} CLUSTER AVG {a:.2f} |  {len(A[B == 1])} comparisons\")\n",
    "    return filtered, indices"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eb3194e6-daa0-4707-8ce5-2ae3375d138d",
   "metadata": {},
   "source": [
    "### ALGORITHMS"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "ec162e93-37f8-4487-9c01-e7862cc14dc6",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_direction(id_labels, embeddings):\n",
    "    # Map labels to integers and get counts in one pass.\n",
    "    unique_labels, integer_labels, counts = np.unique(id_labels, return_inverse=True, return_counts=True)\n",
    "    # Compute per-sample weights: each sample gets weight = 1 / (number of times its label occurs).\n",
    "    weights = 1 / counts[integer_labels]\n",
    "    # Compute the weighted sum using @\n",
    "    v = weights.astype(np.float32) @ embeddings\n",
    "    # The sum of weights is equal to the number of unique labels, so we normalize by that\n",
    "    v /= unique_labels.size\n",
    "    # Normalize the vector v\n",
    "    return v / np.linalg.norm(v)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "a585aa91-f3c0-4362-bdc2-c7ee41763c0f",
   "metadata": {},
   "outputs": [],
   "source": [
    "def grow(seed, threshold = 0.3):\n",
    "    elements = seed\n",
    "    for _ in range(1000): # 1000 means it did not converge \n",
    "        v = get_direction(ids[elements], embeddings[elements])\n",
    "        \n",
    "        proj = embeddings @ v\n",
    "\n",
    "        for i in np.argsort(proj)[::-1]:\n",
    "            if (i not in elements):\n",
    "                if proj[i] < threshold:\n",
    "                    return elements\n",
    "                elements = elements + [i]\n",
    "                break\n",
    "            \n",
    "    return elements"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b7a9940-e4ae-403b-8195-6edb9196ec57",
   "metadata": {},
   "source": [
    "### Load Data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "1d697bfa-c85a-4068-9fc2-fcd42fbcf585",
   "metadata": {},
   "outputs": [],
   "source": [
    "names = get_names()\n",
    "ids = get_ids()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "dea834ec-de81-4cc2-b1fe-fd2e1381ac40",
   "metadata": {},
   "outputs": [],
   "source": [
    "models = {}\n",
    "for model_name in [\"arcface\", \"cosface\", \"elasticface\", \"partialFC\"]:\n",
    "    models[model_name] = get_embeddings(model_name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "1f2484db-68d7-40f2-bca7-b9be600d4fc7",
   "metadata": {},
   "outputs": [],
   "source": [
    "impostors_matrix, genuines_matrix = get_impostors(ids)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "6f8debac-c067-4211-b4bc-ee83083ea472",
   "metadata": {},
   "outputs": [],
   "source": [
    "scores = {}\n",
    "for model_name in models:\n",
    "    scores[model_name] = cosine_similarity(models[model_name])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5cd1b203-ce0b-4e73-b7ba-3d18cfe52f6c",
   "metadata": {},
   "source": [
    "#### If already computed and stored"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "5fd05739-18a8-42cf-9a2d-18b549904931",
   "metadata": {},
   "outputs": [],
   "source": [
    "groups = {}\n",
    "for model_name in models:\n",
    "    # Load\n",
    "    with open(f'groups/{model_name}.pkl', 'rb') as f:\n",
    "        groups[model_name] = pickle.load(f)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "928debd6-9526-4cbd-a5ca-352e0b6ed072",
   "metadata": {},
   "source": [
    "#### If not"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7a8e7492-49b4-4754-8b4e-eaa2421d55fc",
   "metadata": {},
   "outputs": [],
   "source": [
    "clusters = {}\n",
    "for model_name in models:        \n",
    "    clusters[model_name] = inital_clusters(models[model_name])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6c2d0e4c-6b5b-4c3c-a741-61c0de3a7cc3",
   "metadata": {},
   "outputs": [],
   "source": [
    "groups = {}\n",
    "for model_name in models:\n",
    "    groups[model_name] = Parallel(n_jobs=-1, backend=\"loky\")(\n",
    "        delayed(grow)(cl)\n",
    "        for cl in clusters[model_name]\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "5ca0a6c8-5aef-44f7-881a-64b3a220443a",
   "metadata": {
    "scrolled": true,
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 16360 characters]\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "arcface 67.35714835038068 1006 5\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 17788 characters]\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cosface 60.41558710033076 1005 5\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 16144 characters]\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "elasticface 63.76686667642324 1006 5\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 20532 characters]\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "partialFC 39.09617265436526 1003 5\n"
     ]
    }
   ],
   "source": [
    "for model_name in models:\n",
    "    aux = []\n",
    "    for g in groups[model_name]:\n",
    "        aux.append(len(g))\n",
    "    plt.hist(aux, bins=100)\n",
    "    plt.show()\n",
    "    print(model_name, np.mean(aux), np.max(aux), np.min(aux))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 141,
   "id": "53cde1a0-7b71-4427-aafa-82c9d76e58b9",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "ace67766-f12c-4ff7-b95c-814d61fd9a9c",
   "metadata": {},
   "outputs": [],
   "source": [
    "random_ids = np.random.choice(np.unique(ids), 75)\n",
    "selection = np.array([], dtype=int)\n",
    "for rids in random_ids:\n",
    " selection = np.concatenate((selection, np.where(ids == rids)[0]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "2a12ea50-3f8e-4f34-a516-48fa54f1276d",
   "metadata": {},
   "outputs": [],
   "source": [
    "clusters_to_plot = [selection]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "e6e0d072-f0f6-4d79-adb8-d3e29d55de62",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[26908, 26909, 26907, 26910, 20509, 20510, 20511, 22743, 22739, 22740, 22742, 22741, 27135, 27133, 23459, 25958, 25957, 26618, 26619, 26616, 26617, 29425, 20517, 20518, 20519, 20520, 20521, 27992, 27991, 27990, 25765, 22002, 22000, 22001, 20922, 28561, 28559, 28562, 28563, 21348, 21347, 21349, 29214, 29213, 29215, 27222, 33210, 33209, 33211, 23408, 23410, 23409, 23407, 21999, 21208, 21209, 21210, 21211, 21212, 21213, 21214, 11631, 24685, 24686, 24684, 26161, 26163, 26162, 24017, 24018, 22521, 22525, 22522, 22523, 28501, 28503, 28500, 28499, 28502, 23230, 23229, 23227, 21614, 21615, 21616, 21617, 21618, 21619, 27292, 27291, 27290, 24002, 24003, 26938, 27964, 27966, 27965, 23796, 23795, 23797, 26936, 26937, 25600, 29931, 29933, 29932, 29889, 29886, 29888, 29887, 25615, 25613, 25616, 25614, 11632, 20891, 25183, 26485, 26487, 26488, 26486, 28598, 25332, 27559, 27115, 24839, 24840, 24837, 24838, 24841]\n"
     ]
    }
   ],
   "source": [
    "target_elements = {26936, 26937, 26938, 29886, 29887, 29888, 29889, 25041, 25042, 28499, 28500, 28501, 28502, 28503, 23407, 23408, 23409, 23410, 26485, 25613, 25614, 25615, 25616, 26907, 26908, 26909, 26910, 22739, 22740, 22741, 22742, 22743, 27133}\n",
    "\n",
    "blonde = {}\n",
    "for sg in groups:\n",
    "    matching_indices = [\n",
    "        i for i, sublist in enumerate(groups[sg])\n",
    "        if sum(elem in target_elements for elem in sublist) >= 3 #4 #3\n",
    "    ]\n",
    "    blonde[sg] = matching_indices\n",
    "    \n",
    "list_of_lists = []\n",
    "for t in blonde:\n",
    "    for g in blonde[t]:\n",
    "        list_of_lists.append(groups[t][g])\n",
    "\n",
    "# Flatten the list of lists\n",
    "flattened = [item for sublist in list_of_lists for item in sublist]\n",
    "\n",
    "# Count occurrences\n",
    "counts = Counter(flattened)\n",
    "\n",
    "# Keep elements that appear at least 3 times\n",
    "result = [item for item in counts if counts[item] >= 10] #12  #10\n",
    "\n",
    "print(result)\n",
    "clusters_to_plot.append(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "17930e54-7392-445d-91c1-60111213f13b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[34890, 34891, 34892, 34893, 36533, 36531, 36534, 36532, 36733, 36731, 36732, 36169, 36168, 36170, 36356, 34440, 36862, 36861, 34441, 30448, 30446, 30449, 40548, 40547, 40549, 34705, 36309, 36310, 31395, 40546, 31890, 38622, 31893, 31396, 31891, 31892, 37375, 34571, 34573, 34056, 34058, 34059, 34057, 38281, 38279, 38280, 35974, 35815, 35813, 35812, 35814, 38625, 35973, 34060, 38624, 39952, 39953, 39956, 35566, 35567, 34706, 36066, 36064, 39955, 34299, 33862, 33623, 33627, 33625, 35076, 35077, 38492, 38494, 33624, 34449, 38482, 33030, 33029, 33863, 36795, 34707, 35972, 36797, 39954, 36796, 36273, 36274, 33864, 36065, 34439, 39207, 39206, 35381, 35568, 38479, 35565, 38186, 34448, 34450, 34451, 31348, 32844, 32843, 32076, 36920, 33535, 33532, 33534, 37553, 32077, 40193, 34243, 34244, 33533, 31981, 40195, 34865, 34864, 34246, 33116, 34245, 37554, 38800, 30882, 37434, 37432, 38493, 33117, 37396, 37430, 30880, 40194, 40151, 34863, 31266, 37874, 37876, 31985, 31817, 35078, 31979, 31980, 31983]\n"
     ]
    }
   ],
   "source": [
    "target_elements = {36862, 31395, 39952, 39953, 34440, 39956, 40548, 35076, 34299, 36168, 35814, 40547, 36273, 37375}\n",
    "\n",
    "indian = {}\n",
    "for sg in groups:\n",
    "    matching_indices = [\n",
    "        i for i, sublist in enumerate(groups[sg])\n",
    "        if sum(elem in target_elements for elem in sublist) >= 12 #10 #12 #11 #11\n",
    "    ]\n",
    "    indian[sg] = matching_indices\n",
    "\n",
    "list_of_lists = []\n",
    "for t in indian:\n",
    "    for g in indian[t]:\n",
    "        list_of_lists.append(groups[t][g])\n",
    "\n",
    "# Flatten the list of lists\n",
    "flattened = [item for sublist in list_of_lists for item in sublist]\n",
    "\n",
    "# Count occurrences\n",
    "counts = Counter(flattened)\n",
    "\n",
    "# Keep elements that appear at least 3 times\n",
    "result = [item for item in counts if counts[item] >= 8] #10 #8 #10 #11\n",
    "\n",
    "# 263, 143, 132, 111\n",
    "# 6903, 1485, 1378, 990\n",
    "# 34205, 10001, 8514, 5992\n",
    "\n",
    "print(result)\n",
    "clusters_to_plot.append(result)\n",
    "# clusters_to_plot[12] = result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "2e0fec85-3708-4968-9697-fdd0525ac996",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[37762, 37763, 37764, 37766, 38368, 38370, 38367, 38369, 36675, 36676, 36673, 36674, 30745, 30747, 30746, 39575, 39576, 39577, 37465, 33406, 37466, 37467, 39712, 39714, 35351, 39713, 34040, 34041, 37837, 37840, 37839, 35349, 35156, 35154, 39728, 40123, 40125, 35155, 36324, 36326, 36325, 36327, 33339, 33341, 33340, 32074, 32072, 35222, 35139, 35143, 35140, 34002, 35557, 35556, 35346, 35347, 37838, 35141, 30727, 38783, 38782, 35348, 30726, 38384, 31958, 35555, 32073, 32075, 35350, 39502, 39503, 32047, 32044, 32046, 32045, 38044, 38043, 33493, 33494, 32186, 32184, 32185, 39124, 39126, 39123, 39467, 39471, 31195, 39501, 31427, 31194, 39468, 38383, 30345, 30346, 35704]\n"
     ]
    }
   ],
   "source": [
    "target_elements = {34041, 37763, 33339, 32074, 34040, 31958, 35139, 30747, 39728, 38783, 39502, 30746, 37465, 30727}  # set for O(1) lookups\n",
    "\n",
    "turbans = {}\n",
    "for sg in groups:\n",
    "    matching_indices = [\n",
    "        i for i, sublist in enumerate(groups[sg])\n",
    "        if sum(elem in target_elements for elem in sublist) >= 14  #14 #10 #14\n",
    "    ]\n",
    "    turbans[sg] = matching_indices\n",
    "\n",
    "list_of_lists = []\n",
    "for t in turbans:\n",
    "    for g in turbans[t]:\n",
    "        list_of_lists.append(groups[t][g])\n",
    "\n",
    "# Flatten the list of lists\n",
    "flattened = [item for sublist in list_of_lists for item in sublist]\n",
    "\n",
    "# Count occurrences\n",
    "counts = Counter(flattened)\n",
    "\n",
    "# Keep elements that appear at least 3 times\n",
    "result = [item for item in counts if counts[item] >= 14] #10 #15 #14\n",
    "\n",
    "# 1770, 2145, 666\n",
    "\n",
    "print(result)\n",
    "clusters_to_plot.append(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "fcc36a22-8c8a-482b-8203-8786d2ce09ff",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[10982, 16479, 16478, 19744, 19741, 19743, 19742, 19740, 10960, 10962, 12014, 12013, 14831, 15628, 15625, 15627, 15626, 14830, 13939, 13940, 15363, 15362, 15361, 11305, 11302, 13941, 13938, 17706, 11304, 13509, 13507, 12190, 15364, 12456, 12454, 13942, 12455, 17704, 16095, 16093, 13535, 13506, 12457, 12035, 13704, 12037, 12191, 14833, 17945, 13565, 17707, 19736, 14832, 19734, 19735, 19477, 19281, 19280, 16444, 16974, 14267, 16443, 18446, 18447, 19478, 11258, 11260, 11261, 18201, 16094, 13906, 11341, 11291, 11290, 11340, 11259, 12189, 14266, 12192, 17944, 14728, 13566, 16442, 16408, 16410, 14992, 16689, 11339, 17943, 19476, 13563, 19479, 11306, 10970, 11303, 13567, 11292, 16409, 17705, 13564]\n"
     ]
    }
   ],
   "source": [
    "target_elements = {12456, 11305, 13535, 10962, 15362, 11304, 17706, 14830, 13938, 15627, 19744, 13942, 12014, 17704}\n",
    "\n",
    "chinese = {}\n",
    "for sg in groups:\n",
    "    matching_indices = [\n",
    "        i for i, sublist in enumerate(groups[sg])\n",
    "        if sum(elem in target_elements for elem in sublist) >= 13 #9 #7 #5\n",
    "    ]\n",
    "    chinese[sg] = matching_indices\n",
    "    \n",
    "list_of_lists = []\n",
    "for t in chinese:\n",
    "    for g in chinese[t]:\n",
    "        list_of_lists.append(groups[t][g])\n",
    "\n",
    "# Flatten the list of lists\n",
    "flattened = [item for sublist in list_of_lists for item in sublist]\n",
    "\n",
    "# Count occurrences\n",
    "counts = Counter(flattened)\n",
    "\n",
    "# Keep elements that appear at least 3 times\n",
    "result = [item for item in counts if counts[item] >= 58] #7 #8 #9\n",
    "\n",
    "# 0.154  561 34\n",
    "# 0.140  903 43\n",
    "# 0.140  946 44\n",
    "\n",
    "print(result)\n",
    "clusters_to_plot.append(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e029f972-73c0-4850-9ef3-19d92decfac6",
   "metadata": {},
   "source": [
    "# Plots"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "6df6118d-2733-4c04-aac8-bd673cae631c",
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.set_theme(style=\"white\", rc={\"axes.facecolor\": (0, 0, 0, 0)})\n",
    "\n",
    "def ridge_plot(df, bandwidth = 0.5):\n",
    "    # Initialize the FacetGrid object\n",
    "    pal = sns.cubehelix_palette(5, rot=-.5, light=.7)\n",
    "    g = sns.FacetGrid(df, row=\"Label\", hue=\"Label\", aspect=10, height=1.25, palette=pal)#, xlim=(-.2,.5)) \n",
    "    \n",
    "    g.map(sns.kdeplot, \"Genuine Score\",\n",
    "          bw_adjust=bandwidth, clip_on=True, color=\"lightseagreen\",\n",
    "          fill=True, alpha=1, linewidth=1.5)\n",
    "    g.map(sns.kdeplot, \"Genuine Score\", clip_on=True, color=\"w\", lw=2, bw_adjust=bandwidth)\n",
    "    \n",
    "    # Draw the densities in a few steps\n",
    "    g.map(sns.kdeplot, \"Similarity Score\",\n",
    "          bw_adjust=bandwidth, clip_on=True,\n",
    "          fill=True, alpha=1, linewidth=1.5)\n",
    "    g.map(sns.kdeplot, \"Similarity Score\", clip_on=True, color=\"w\", lw=2, bw_adjust=bandwidth)\n",
    "\n",
    "    # passing color=None to refline() uses the hue mapping\n",
    "    g.refline(y=0, linewidth=2, linestyle=\"-\", color=None, clip_on=True)\n",
    "\n",
    "\n",
    "    # Define and use a simple function to label the plot in axes coordinates\n",
    "    def label(x, color, label):\n",
    "        ax = plt.gca()\n",
    "        ax.text(0, .2, label, fontweight=\"bold\", color=color,\n",
    "                ha=\"left\", va=\"center\", transform=ax.transAxes)\n",
    "\n",
    "\n",
    "    g.map(label, \"Similarity Score\")\n",
    "\n",
    "    # Set the subplots to overlap\n",
    "    g.figure.subplots_adjust(hspace=-.5)\n",
    "    \n",
    "    # # Add a vertical line to each subplot\n",
    "    # for ax in g.axes.flat:\n",
    "    #     ax.axvline(x=0.0, color='g', linestyle='--')\n",
    "\n",
    "\n",
    "    # Remove axes details that don't play well with overlap\n",
    "    g.set_titles(\"\")\n",
    "    g.set(xlim=(-.2, 1))\n",
    "    g.set(yticks=[], ylabel=\"\")\n",
    "    g.despine(bottom=True, left=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2066fd5e-1cab-478d-bbd0-92cf54ef51b0",
   "metadata": {},
   "source": [
    "## ARCFACE"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "d8dcd95f-7998-4908-90cb-6e4691896aff",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "      μ     σ     μ   σ  Images Ids\n",
      "R   0.004 0.07 0.623 0.10 34340 75\n",
      "A   0.044 0.09 0.604 0.10 8211 45\n",
      "B   0.109 0.10 0.630 0.10 10001 55\n",
      "C   0.129 0.09 0.677 0.11 4464 37\n",
      "D   0.160 0.09 0.644 0.11 4831 37\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 97060 characters]\n",
      "text/plain": [
       "<Figure size 1250x625 with 5 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_labels = list(\"RABCDEFGHIJKLMNOPRSTUVWXYZ\")\n",
    "l = 0\n",
    "plot_df = pd.DataFrame(columns=['Similarity Score','Label'])\n",
    "print(f\"      μ     σ     μ   σ  Images Ids\")\n",
    "for group in clusters_to_plot:\n",
    "    A = scores['arcface'][group][:,group]\n",
    "    B = impostors_matrix[group][:,group]\n",
    "    C = genuines_matrix[group][:,group]\n",
    "    \n",
    "    new_df = pd.DataFrame({\n",
    "        'Genuine Score': A[C == 1],\n",
    "        'Label': [plot_labels[l]] * len(A[C == 1])\n",
    "    })\n",
    "    plot_df = pd.concat([plot_df, new_df], ignore_index=True)\n",
    "    \n",
    "    new_df = pd.DataFrame({\n",
    "        'Similarity Score': A[B == 1],\n",
    "        'Label': [plot_labels[l]] * len(A[B == 1])\n",
    "    })\n",
    "    plot_df = pd.concat([plot_df, new_df], ignore_index=True)\n",
    "    \n",
    "    print(f\"{plot_labels[l]}   {np.mean(A[B == 1]):.3f} {np.std(A[B == 1]):.2f} {np.mean(A[C == 1]):.3f} {np.std(A[C == 1]):.2f} {len(A[B == 1])} {len(np.unique(ids[group]))}\")\n",
    "    \n",
    "    l += 1\n",
    "    \n",
    "ridge_plot(plot_df)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dad51366-a230-4062-baec-810af3c4f145",
   "metadata": {},
   "source": [
    "## COSFACE"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "7e7bd0c4-63a4-4c41-84f3-e7e15222a4fe",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "      μ     σ     μ   σ  Images Ids\n",
      "R   0.010 0.07 0.587 0.11 34340 75\n",
      "A   0.054 0.08 0.579 0.10 8211 45\n",
      "B   0.141 0.09 0.609 0.10 10001 55\n",
      "C   0.128 0.08 0.654 0.12 4464 37\n",
      "D   0.156 0.08 0.586 0.13 4831 37\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 94944 characters]\n",
      "text/plain": [
       "<Figure size 1250x625 with 5 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_labels = list(\"RABCDEFGHIJKLMNOPRSTUVWXYZ\")\n",
    "l = 0\n",
    "plot_df = pd.DataFrame(columns=['Similarity Score','Label'])\n",
    "print(f\"      μ     σ     μ   σ  Images Ids\")\n",
    "for group in clusters_to_plot:\n",
    "    A = scores['cosface'][group][:,group]\n",
    "    B = impostors_matrix[group][:,group]\n",
    "    C = genuines_matrix[group][:,group]\n",
    "    \n",
    "    new_df = pd.DataFrame({\n",
    "        'Genuine Score': A[C == 1],\n",
    "        'Label': [plot_labels[l]] * len(A[C == 1])\n",
    "    })\n",
    "    plot_df = pd.concat([plot_df, new_df], ignore_index=True)\n",
    "    \n",
    "    new_df = pd.DataFrame({\n",
    "        'Similarity Score': A[B == 1],\n",
    "        'Label': [plot_labels[l]] * len(A[B == 1])\n",
    "    })\n",
    "    plot_df = pd.concat([plot_df, new_df], ignore_index=True)\n",
    "    \n",
    "    print(f\"{plot_labels[l]}   {np.mean(A[B == 1]):.3f} {np.std(A[B == 1]):.2f} {np.mean(A[C == 1]):.3f} {np.std(A[C == 1]):.2f} {len(A[B == 1])} {len(np.unique(ids[group]))}\")\n",
    "    \n",
    "    l += 1\n",
    "    \n",
    "ridge_plot(plot_df)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c1181198-1d59-4749-aae1-0521d84d46cc",
   "metadata": {},
   "source": [
    "## ELASTICFACE"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 181,
   "id": "bd25b72b-30c1-49e1-adb1-6b96ba174f23",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "      μ     σ     μ   σ  Images Ids\n",
      "R   0.004 0.07 0.605 0.11 34606 75\n",
      "A   0.045 0.09 0.608 0.10 8211 45\n",
      "B   0.101 0.10 0.622 0.10 10001 55\n",
      "C   0.150 0.09 0.674 0.11 4464 37\n",
      "D   0.185 0.09 0.642 0.12 4831 37\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 95352 characters]\n",
      "text/plain": [
       "<Figure size 1250x625 with 5 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_labels = list(\"RABCDEFGHIJKLMNOPRSTUVWXYZ\")\n",
    "l = 0\n",
    "plot_df = pd.DataFrame(columns=['Similarity Score','Label'])\n",
    "print(f\"      μ     σ     μ   σ  Images Ids\")\n",
    "for group in clusters_to_plot:\n",
    "    A = scores['elasticface'][group][:,group]\n",
    "    B = impostors_matrix[group][:,group]\n",
    "    C = genuines_matrix[group][:,group]\n",
    "    \n",
    "    new_df = pd.DataFrame({\n",
    "        'Genuine Score': A[C == 1],\n",
    "        'Label': [plot_labels[l]] * len(A[C == 1])\n",
    "    })\n",
    "    plot_df = pd.concat([plot_df, new_df], ignore_index=True)\n",
    "    \n",
    "    new_df = pd.DataFrame({\n",
    "        'Similarity Score': A[B == 1],\n",
    "        'Label': [plot_labels[l]] * len(A[B == 1])\n",
    "    })\n",
    "    plot_df = pd.concat([plot_df, new_df], ignore_index=True)\n",
    "    \n",
    "    print(f\"{plot_labels[l]}   {np.mean(A[B == 1]):.3f} {np.std(A[B == 1]):.2f} {np.mean(A[C == 1]):.3f} {np.std(A[C == 1]):.2f} {len(A[B == 1])} {len(np.unique(ids[group]))}\")\n",
    "    \n",
    "    l += 1\n",
    "    \n",
    "ridge_plot(plot_df)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ecd9ead6-16d0-4e0e-a454-a2c17334ea17",
   "metadata": {},
   "source": [
    "## PARTIALFC"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 182,
   "id": "fefde9f6-b832-4bc0-aa22-120e2ad3f56b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "      μ     σ     μ   σ  Images Ids\n",
      "R   0.005 0.05 0.573 0.12 34606 75\n",
      "A   0.034 0.07 0.566 0.10 8211 45\n",
      "B   0.093 0.08 0.598 0.11 10001 55\n",
      "C   0.087 0.08 0.636 0.13 4464 37\n",
      "D   0.112 0.08 0.590 0.13 4831 37\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 78564 characters]\n",
      "text/plain": [
       "<Figure size 1250x625 with 5 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_labels = list(\"RABCDEFGHIJKLMNOPRSTUVWXYZ\")\n",
    "l = 0\n",
    "plot_df = pd.DataFrame(columns=['Similarity Score','Label'])\n",
    "print(f\"      μ     σ     μ   σ  Images Ids\")\n",
    "for group in clusters_to_plot:\n",
    "    A = scores['partialFC'][group][:,group]\n",
    "    B = impostors_matrix[group][:,group]\n",
    "    C = genuines_matrix[group][:,group]\n",
    "    \n",
    "    new_df = pd.DataFrame({\n",
    "        'Genuine Score': A[C == 1],\n",
    "        'Label': [plot_labels[l]] * len(A[C == 1])\n",
    "    })\n",
    "    plot_df = pd.concat([plot_df, new_df], ignore_index=True)\n",
    "    \n",
    "    new_df = pd.DataFrame({\n",
    "        'Similarity Score': A[B == 1],\n",
    "        'Label': [plot_labels[l]] * len(A[B == 1])\n",
    "    })\n",
    "    plot_df = pd.concat([plot_df, new_df], ignore_index=True)\n",
    "    \n",
    "    print(f\"{plot_labels[l]}   {np.mean(A[B == 1]):.3f} {np.std(A[B == 1]):.2f} {np.mean(A[C == 1]):.3f} {np.std(A[C == 1]):.2f} {len(A[B == 1])} {len(np.unique(ids[group]))}\")\n",
    "    \n",
    "    l += 1\n",
    "    \n",
    "ridge_plot(plot_df)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "cba3f253-18cb-4436-a813-77439064609a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CLUSTER\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:06<00:00, 149.14it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0061 (95% CI: 0.0000 - 0.0139)\n",
      "FNMR @ FMR=0.0100: 0.0055 (95% CI: 0.0000 - 0.0139)\n",
      "FNMR @ FMR=0.0010: 0.0084 (95% CI: 0.0000 - 0.0195)\n",
      "arcface 34094 359\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:06<00:00, 151.70it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0069 (95% CI: 0.0001 - 0.0140)\n",
      "FNMR @ FMR=0.0100: 0.0058 (95% CI: 0.0000 - 0.0140)\n",
      "FNMR @ FMR=0.0010: 0.0086 (95% CI: 0.0000 - 0.0195)\n",
      "cosface 34094 359\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:06<00:00, 152.95it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0079 (95% CI: 0.0005 - 0.0158)\n",
      "FNMR @ FMR=0.0100: 0.0084 (95% CI: 0.0000 - 0.0195)\n",
      "FNMR @ FMR=0.0010: 0.0109 (95% CI: 0.0028 - 0.0223)\n",
      "elasticface 34094 359\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:06<00:00, 152.19it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0085 (95% CI: 0.0000 - 0.0195)\n",
      "FNMR @ FMR=0.0100: 0.0085 (95% CI: 0.0000 - 0.0195)\n",
      "FNMR @ FMR=0.0010: 0.0085 (95% CI: 0.0000 - 0.0195)\n",
      "partialFC 34094 359\n",
      "\n",
      "CLUSTER\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:01<00:00, 541.68it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0110 (95% CI: 0.0006 - 0.0229)\n",
      "FNMR @ FMR=0.0100: 0.0116 (95% CI: 0.0000 - 0.0345)\n",
      "FNMR @ FMR=0.0010: 0.0224 (95% CI: 0.0057 - 0.0460)\n",
      "arcface 8211 174\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:01<00:00, 534.57it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0067 (95% CI: 0.0001 - 0.0174)\n",
      "FNMR @ FMR=0.0100: 0.0058 (95% CI: 0.0000 - 0.0172)\n",
      "FNMR @ FMR=0.0010: 0.0171 (95% CI: 0.0000 - 0.0402)\n",
      "cosface 8211 174\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:01<00:00, 534.48it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0118 (95% CI: 0.0018 - 0.0287)\n",
      "FNMR @ FMR=0.0100: 0.0114 (95% CI: 0.0000 - 0.0287)\n",
      "FNMR @ FMR=0.0010: 0.0228 (95% CI: 0.0057 - 0.0460)\n",
      "elasticface 8211 174\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:01<00:00, 539.59it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0073 (95% CI: 0.0001 - 0.0173)\n",
      "FNMR @ FMR=0.0100: 0.0055 (95% CI: 0.0000 - 0.0172)\n",
      "FNMR @ FMR=0.0010: 0.0173 (95% CI: 0.0000 - 0.0402)\n",
      "partialFC 8211 174\n",
      "\n",
      "CLUSTER\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:02<00:00, 458.63it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0004 (95% CI: 0.0000 - 0.0007)\n",
      "FNMR @ FMR=0.0100: 0.0000 (95% CI: 0.0000 - 0.0000)\n",
      "FNMR @ FMR=0.0010: 0.0019 (95% CI: 0.0000 - 0.0197)\n",
      "arcface 10001 152\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:02<00:00, 459.85it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0052 (95% CI: 0.0003 - 0.0102)\n",
      "FNMR @ FMR=0.0100: 0.0000 (95% CI: 0.0000 - 0.0000)\n",
      "FNMR @ FMR=0.0010: 0.0220 (95% CI: 0.0000 - 0.0461)\n",
      "cosface 10001 152\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:02<00:00, 454.78it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0013 (95% CI: 0.0002 - 0.0051)\n",
      "FNMR @ FMR=0.0100: 0.0000 (95% CI: 0.0000 - 0.0000)\n",
      "FNMR @ FMR=0.0010: 0.0090 (95% CI: 0.0000 - 0.0329)\n",
      "elasticface 10001 152\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:02<00:00, 457.38it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0010 (95% CI: 0.0000 - 0.0016)\n",
      "FNMR @ FMR=0.0100: 0.0000 (95% CI: 0.0000 - 0.0000)\n",
      "FNMR @ FMR=0.0010: 0.0085 (95% CI: 0.0000 - 0.0263)\n",
      "partialFC 10001 152\n",
      "\n",
      "CLUSTER\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:01<00:00, 840.90it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0183 (95% CI: 0.0000 - 0.0355)\n",
      "FNMR @ FMR=0.0100: 0.0212 (95% CI: 0.0000 - 0.0521)\n",
      "FNMR @ FMR=0.0010: 0.0297 (95% CI: 0.0000 - 0.0729)\n",
      "arcface 4464 96\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:01<00:00, 848.75it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0160 (95% CI: 0.0029 - 0.0316)\n",
      "FNMR @ FMR=0.0100: 0.0312 (95% CI: 0.0000 - 0.0729)\n",
      "FNMR @ FMR=0.0010: 0.0424 (95% CI: 0.0104 - 0.0833)\n",
      "cosface 4464 96\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:01<00:00, 849.30it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0139 (95% CI: 0.0000 - 0.0314)\n",
      "FNMR @ FMR=0.0100: 0.0212 (95% CI: 0.0000 - 0.0521)\n",
      "FNMR @ FMR=0.0010: 0.0321 (95% CI: 0.0000 - 0.0729)\n",
      "elasticface 4464 96\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:01<00:00, 849.33it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0155 (95% CI: 0.0000 - 0.0313)\n",
      "FNMR @ FMR=0.0100: 0.0207 (95% CI: 0.0000 - 0.0521)\n",
      "FNMR @ FMR=0.0010: 0.0308 (95% CI: 0.0000 - 0.0625)\n",
      "partialFC 4464 96\n",
      "\n",
      "CLUSTER\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:01<00:00, 787.87it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0009 (95% CI: 0.0003 - 0.0018)\n",
      "FNMR @ FMR=0.0100: 0.0000 (95% CI: 0.0000 - 0.0000)\n",
      "FNMR @ FMR=0.0010: 0.0346 (95% CI: 0.0000 - 0.0840)\n",
      "arcface 4831 119\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:01<00:00, 782.84it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0252 (95% CI: 0.0035 - 0.0506)\n",
      "FNMR @ FMR=0.0100: 0.0257 (95% CI: 0.0000 - 0.0588)\n",
      "FNMR @ FMR=0.0010: 0.0723 (95% CI: 0.0252 - 0.1261)\n",
      "cosface 4831 119\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:01<00:00, 778.37it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0202 (95% CI: 0.0077 - 0.0385)\n",
      "FNMR @ FMR=0.0100: 0.0298 (95% CI: 0.0000 - 0.0672)\n",
      "FNMR @ FMR=0.0010: 0.0736 (95% CI: 0.0252 - 0.1261)\n",
      "elasticface 4831 119\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Bootstrapping: 100%|██████████| 1000/1000 [00:01<00:00, 789.24it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== Bootstrap Results ===\n",
      "EER: 0.0135 (95% CI: 0.0010 - 0.0239)\n",
      "FNMR @ FMR=0.0100: 0.0164 (95% CI: 0.0000 - 0.0420)\n",
      "FNMR @ FMR=0.0010: 0.0696 (95% CI: 0.0084 - 0.1681)\n",
      "partialFC 4831 119\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "for result in clusters_to_plot:\n",
    "    print(\"CLUSTER\")\n",
    "    for model_name in models:\n",
    "        A = scores[model_name][result][:,result]\n",
    "        I = impostors_matrix[result][:,result]\n",
    "        G = genuines_matrix[result][:,result]\n",
    "\n",
    "        impostor_scores = A[I == 1]\n",
    "        genuine_scores = A[G == 1]\n",
    "        # Example run\n",
    "        eer, threshold = compute_eer_and_fnmr_bootstrap(genuine_scores, impostor_scores, fmr_targets=[0.01, 0.001])\n",
    "        print(model_name, len(impostor_scores), len(genuine_scores))\n",
    "    print(\"\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "421c513a-9b81-49f9-8a2c-ac99ac246034",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CLUSTER\n",
      "\n",
      "=== EER: 0.0051 at threshold 0.2023 ===\n",
      "FNMR @ FMR=0.0100: 0.0056 (threshold=0.0740)\n",
      "FNMR @ FMR=0.0010: 0.0084 (threshold=0.2030)\n",
      "arcface 34094 359\n",
      "\n",
      "=== EER: 0.0062 at threshold 0.1817 ===\n",
      "FNMR @ FMR=0.0100: 0.0056 (threshold=0.1144)\n",
      "FNMR @ FMR=0.0010: 0.0084 (threshold=0.1817)\n",
      "cosface 34094 359\n",
      "\n",
      "=== EER: 0.0104 at threshold 0.1666 ===\n",
      "FNMR @ FMR=0.0100: 0.0084 (threshold=0.1666)\n",
      "FNMR @ FMR=0.0010: 0.0111 (threshold=0.2335)\n",
      "elasticface 34094 359\n",
      "\n",
      "=== EER: 0.0042 at threshold 0.2376 ===\n",
      "FNMR @ FMR=0.0100: 0.0084 (threshold=0.1053)\n",
      "FNMR @ FMR=0.0010: 0.0084 (threshold=0.1053)\n",
      "partialFC 34094 359\n",
      "\n",
      "CLUSTER\n",
      "\n",
      "=== EER: 0.0087 at threshold 0.2902 ===\n",
      "FNMR @ FMR=0.0100: 0.0115 (threshold=0.2523)\n",
      "FNMR @ FMR=0.0010: 0.0230 (threshold=0.3548)\n",
      "arcface 8211 174\n",
      "\n",
      "=== EER: 0.0051 at threshold 0.2964 ===\n",
      "FNMR @ FMR=0.0100: 0.0057 (threshold=0.2116)\n",
      "FNMR @ FMR=0.0010: 0.0172 (threshold=0.3221)\n",
      "cosface 8211 174\n",
      "\n",
      "=== EER: 0.0082 at threshold 0.3159 ===\n",
      "FNMR @ FMR=0.0100: 0.0115 (threshold=0.2264)\n",
      "FNMR @ FMR=0.0010: 0.0230 (threshold=0.3244)\n",
      "elasticface 8211 174\n",
      "\n",
      "=== EER: 0.0063 at threshold 0.2278 ===\n",
      "FNMR @ FMR=0.0100: 0.0057 (threshold=0.1704)\n",
      "FNMR @ FMR=0.0010: 0.0172 (threshold=0.2653)\n",
      "partialFC 8211 174\n",
      "\n",
      "CLUSTER\n",
      "\n",
      "=== EER: 0.0004 at threshold 0.3937 ===\n",
      "FNMR @ FMR=0.0100: 0.0000 (threshold=-0.2700)\n",
      "FNMR @ FMR=0.0010: 0.0000 (threshold=-0.2700)\n",
      "arcface 10001 152\n",
      "\n",
      "=== EER: 0.0064 at threshold 0.3543 ===\n",
      "FNMR @ FMR=0.0100: 0.0000 (threshold=0.2438)\n",
      "FNMR @ FMR=0.0010: 0.0197 (threshold=0.3637)\n",
      "cosface 10001 152\n",
      "\n",
      "=== EER: 0.0014 at threshold 0.3733 ===\n",
      "FNMR @ FMR=0.0100: 0.0000 (threshold=0.1182)\n",
      "FNMR @ FMR=0.0010: 0.0066 (threshold=0.3751)\n",
      "elasticface 10001 152\n",
      "\n",
      "=== EER: 0.0012 at threshold 0.3315 ===\n",
      "FNMR @ FMR=0.0100: 0.0000 (threshold=0.1536)\n",
      "FNMR @ FMR=0.0010: 0.0066 (threshold=0.3347)\n",
      "partialFC 10001 152\n",
      "\n",
      "CLUSTER\n",
      "\n",
      "=== EER: 0.0242 at threshold 0.3068 ===\n",
      "FNMR @ FMR=0.0100: 0.0208 (threshold=0.3068)\n",
      "FNMR @ FMR=0.0010: 0.0312 (threshold=0.4093)\n",
      "arcface 4464 96\n",
      "\n",
      "=== EER: 0.0183 at threshold 0.3272 ===\n",
      "FNMR @ FMR=0.0100: 0.0312 (threshold=0.3341)\n",
      "FNMR @ FMR=0.0010: 0.0417 (threshold=0.3527)\n",
      "cosface 4464 96\n",
      "\n",
      "=== EER: 0.0122 at threshold 0.3525 ===\n",
      "FNMR @ FMR=0.0100: 0.0208 (threshold=0.3539)\n",
      "FNMR @ FMR=0.0010: 0.0312 (threshold=0.4009)\n",
      "elasticface 4464 96\n",
      "\n",
      "=== EER: 0.0194 at threshold 0.2587 ===\n",
      "FNMR @ FMR=0.0100: 0.0208 (threshold=0.2587)\n",
      "FNMR @ FMR=0.0010: 0.0312 (threshold=0.2831)\n",
      "partialFC 4464 96\n",
      "\n",
      "CLUSTER\n",
      "\n",
      "=== EER: 0.0010 at threshold 0.4071 ===\n",
      "FNMR @ FMR=0.0100: 0.0000 (threshold=-0.0970)\n",
      "FNMR @ FMR=0.0010: 0.0420 (threshold=0.4551)\n",
      "arcface 4831 119\n",
      "\n",
      "=== EER: 0.0157 at threshold 0.3636 ===\n",
      "FNMR @ FMR=0.0100: 0.0252 (threshold=0.2885)\n",
      "FNMR @ FMR=0.0010: 0.0756 (threshold=0.4095)\n",
      "cosface 4831 119\n",
      "\n",
      "=== EER: 0.0189 at threshold 0.3598 ===\n",
      "FNMR @ FMR=0.0100: 0.0336 (threshold=0.3946)\n",
      "FNMR @ FMR=0.0010: 0.0840 (threshold=0.4604)\n",
      "elasticface 4831 119\n",
      "\n",
      "=== EER: 0.0178 at threshold 0.2880 ===\n",
      "FNMR @ FMR=0.0100: 0.0168 (threshold=0.2880)\n",
      "FNMR @ FMR=0.0010: 0.0840 (threshold=0.3977)\n",
      "partialFC 4831 119\n",
      "\n"
     ]
    }
   ],
   "source": [
    "for result in clusters_to_plot:\n",
    "    print(\"CLUSTER\")\n",
    "    for model_name in models:\n",
    "        A = scores[model_name][result][:,result]\n",
    "        I = impostors_matrix[result][:,result]\n",
    "        G = genuines_matrix[result][:,result]\n",
    "\n",
    "        impostor_scores = A[I == 1]\n",
    "        genuine_scores = A[G == 1]\n",
    "        # Example run\n",
    "        eer, threshold = compute_eer_and_fnmr(genuine_scores, impostor_scores, fmr_targets=[0.01, 0.001])\n",
    "        print(model_name, len(impostor_scores), len(genuine_scores))\n",
    "    print(\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bfd21b2a-ae07-45f1-87a1-d565c69537ad",
   "metadata": {},
   "source": [
    "### FMR @ t=0.2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 189,
   "id": "61615a99-fc8b-4c13-8c53-065167a7e4c2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.00572155117609663, 0.05200341005967604, 0.18048195180481952, 0.2152777777777778, 0.31794659490788657]\n",
      "[0.0044789920822978675, 0.05236877359639508, 0.24477552244775522, 0.19578853046594982, 0.2786172635065204]\n",
      "[0.004305611743628273, 0.05663134819145049, 0.16728327167283272, 0.2853942652329749, 0.43572759263092525]\n",
      "[0.0015604230480263537, 0.01619778346121057, 0.10618938106189381, 0.08131720430107527, 0.13992962119643967]\n"
     ]
    }
   ],
   "source": [
    "for model_name in models:\n",
    "    impostor_scores = []\n",
    "    for result in clusters_to_plot:\n",
    "        A = scores[model_name][result][:,result]\n",
    "        I = impostors_matrix[result][:,result]\n",
    "        G = genuines_matrix[result][:,result]\n",
    "\n",
    "        impostor_scores.append(A[I == 1])\n",
    "    print([(i >= 0.2).mean() for i in impostor_scores])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "734f1cf3-9879-4e6c-8d04-437ac3f307be",
   "metadata": {},
   "outputs": [],
   "source": [
    "def compute_fmr_vs_threshold(impostor_scores, n_points=500):\n",
    "    scores = np.asarray(impostor_scores)\n",
    "    thresholds = np.linspace(scores.min(), scores.max(), n_points)\n",
    "    fmr = [(scores >= t).mean() for t in thresholds]       # “≥” because higher = more similar\n",
    "    return thresholds, fmr\n",
    "\n",
    "def build_fmr_dataframe(group_dict, n_points=500):\n",
    "    frames = []\n",
    "    for name, scores in group_dict.items():\n",
    "        thr, fmr = compute_fmr_vs_threshold(scores, n_points)\n",
    "        frames.append(pd.DataFrame({\"Threshold\": thr, \"FMR\": fmr, \"Group\": name}))\n",
    "    return pd.concat(frames, ignore_index=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "2929a446-c170-4014-bdf1-b7827ebccebb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 108004 characters]\n",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "model_name = 'arcface'\n",
    "impostor_scores = []\n",
    "for result in clusters_to_plot:\n",
    "    A = scores[model_name][result][:,result]\n",
    "    I = impostors_matrix[result][:,result]\n",
    "    G = genuines_matrix[result][:,result]\n",
    "    \n",
    "    impostor_scores.append(A[I == 1])\n",
    "    \n",
    "    \n",
    "    # Example group score dictionary (replace with your actual data)\n",
    "group_scores = {\n",
    "    \"R\": impostor_scores[0],\n",
    "    \"A\": impostor_scores[1],\n",
    "    \"B\": impostor_scores[2],\n",
    "    \"C\": impostor_scores[3],\n",
    "    \"D\": impostor_scores[4],\n",
    "}\n",
    "\n",
    "# Prepare the long-format DataFrame for seaborn\n",
    "df_fmr = build_fmr_dataframe(group_scores)\n",
    "\n",
    "sns.set_theme(style=\"dark\")\n",
    "palette = sns.cubehelix_palette(5, rot=-.5, light=.7)  # << your palette\n",
    "sns.set_palette(palette)                               # make it the default\n",
    "\n",
    "with plt.rc_context(rc={\"font.family\": \"DejaVu Sans Mono\"}):\n",
    "\n",
    "    # Plot with Seaborn\n",
    "    plt.figure(figsize=(10, 5))\n",
    "    sns.lineplot(data=df_fmr, x=\"Threshold\", y=\"FMR\", hue=\"Group\", linewidth=2)\n",
    "\n",
    "    plt.yscale(\"log\")  # Optional: Log scale to highlight low FMR regions\n",
    "    plt.title(\"FMR vs Threshold: ArcFace\")\n",
    "    plt.xlabel(\"Similarity Threshold\")\n",
    "    plt.ylabel(\"False Match Rate (FMR)\")\n",
    "    plt.xlim(left=-0.22)\n",
    "    plt.grid(True, which='both', linestyle='--', linewidth=0.5)\n",
    "    plt.legend(title=\"Group\", loc='upper right')\n",
    "    plt.tight_layout()\n",
    "    plt.show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "97b1c38d-68e2-48ba-8ea3-c6067494be6a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 102848 characters]\n",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "model_name = 'cosface'\n",
    "impostor_scores = []\n",
    "for result in clusters_to_plot:\n",
    "    A = scores[model_name][result][:,result]\n",
    "    I = impostors_matrix[result][:,result]\n",
    "    G = genuines_matrix[result][:,result]\n",
    "    \n",
    "    impostor_scores.append(A[I == 1])\n",
    "    \n",
    "    \n",
    "    # Example group score dictionary (replace with your actual data)\n",
    "group_scores = {\n",
    "    \"R\": impostor_scores[0],\n",
    "    \"A\": impostor_scores[1],\n",
    "    \"B\": impostor_scores[2],\n",
    "    \"C\": impostor_scores[3],\n",
    "    \"D\": impostor_scores[4],\n",
    "}\n",
    "\n",
    "# Prepare the long-format DataFrame for seaborn\n",
    "df_fmr = build_fmr_dataframe(group_scores)\n",
    "\n",
    "sns.set_theme(style=\"dark\")\n",
    "palette = sns.cubehelix_palette(5, rot=-.5, light=.7)  # << your palette\n",
    "sns.set_palette(palette)                               # make it the default\n",
    "\n",
    "with plt.rc_context(rc={\"font.family\": \"DejaVu Sans Mono\"}):\n",
    "\n",
    "    # Plot with Seaborn\n",
    "    plt.figure(figsize=(10, 5))\n",
    "    sns.lineplot(data=df_fmr, x=\"Threshold\", y=\"FMR\", hue=\"Group\", linewidth=2)\n",
    "\n",
    "    plt.yscale(\"log\")  # Optional: Log scale to highlight low FMR regions\n",
    "    plt.title(\"FMR vs Threshold: CosFace\")\n",
    "    plt.xlabel(\"Similarity Threshold\")\n",
    "    plt.ylabel(\"False Match Rate (FMR)\")\n",
    "    plt.xlim(left=-0.22)\n",
    "    plt.grid(True, which='both', linestyle='--', linewidth=0.5)\n",
    "    plt.legend(title=\"Group\", loc='upper right')\n",
    "    plt.tight_layout()\n",
    "    plt.show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "2e33321a-c66f-4c4b-9acc-8cfacc3c76a1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 109944 characters]\n",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "model_name = 'elasticface'\n",
    "impostor_scores = []\n",
    "for result in clusters_to_plot:\n",
    "    A = scores[model_name][result][:,result]\n",
    "    I = impostors_matrix[result][:,result]\n",
    "    G = genuines_matrix[result][:,result]\n",
    "    \n",
    "    impostor_scores.append(A[I == 1])\n",
    "    \n",
    "    \n",
    "    # Example group score dictionary (replace with your actual data)\n",
    "group_scores = {\n",
    "    \"R\": impostor_scores[0],\n",
    "    \"A\": impostor_scores[1],\n",
    "    \"B\": impostor_scores[2],\n",
    "    \"C\": impostor_scores[3],\n",
    "    \"D\": impostor_scores[4],\n",
    "}\n",
    "\n",
    "# Prepare the long-format DataFrame for seaborn\n",
    "df_fmr = build_fmr_dataframe(group_scores)\n",
    "\n",
    "sns.set_theme(style=\"dark\")\n",
    "palette = sns.cubehelix_palette(5, rot=-.5, light=.7)  # << your palette\n",
    "sns.set_palette(palette)                               # make it the default\n",
    "\n",
    "with plt.rc_context(rc={\"font.family\": \"DejaVu Sans Mono\"}):\n",
    "\n",
    "    # Plot with Seaborn\n",
    "    plt.figure(figsize=(10, 5))\n",
    "    sns.lineplot(data=df_fmr, x=\"Threshold\", y=\"FMR\", hue=\"Group\", linewidth=2)\n",
    "\n",
    "    plt.yscale(\"log\")  # Optional: Log scale to highlight low FMR regions\n",
    "    plt.title(\"FMR vs Threshold: ElasticFace\")\n",
    "    plt.xlabel(\"Similarity Threshold\")\n",
    "    plt.ylabel(\"False Match Rate (FMR)\")\n",
    "    plt.xlim(left=-0.22)\n",
    "    plt.grid(True, which='both', linestyle='--', linewidth=0.5)\n",
    "    plt.legend(title=\"Group\", loc='upper right')\n",
    "    plt.tight_layout()\n",
    "    plt.show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "4e974a70-b6b9-4bbe-bc82-b6cc92d8b3ec",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 106012 characters]\n",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "model_name = 'partialFC'\n",
    "impostor_scores = []\n",
    "for result in clusters_to_plot:\n",
    "    A = scores[model_name][result][:,result]\n",
    "    I = impostors_matrix[result][:,result]\n",
    "    G = genuines_matrix[result][:,result]\n",
    "    \n",
    "    impostor_scores.append(A[I == 1])\n",
    "    \n",
    "    \n",
    "    # Example group score dictionary (replace with your actual data)\n",
    "group_scores = {\n",
    "    \"R\": impostor_scores[0],\n",
    "    \"A\": impostor_scores[1],\n",
    "    \"B\": impostor_scores[2],\n",
    "    \"C\": impostor_scores[3],\n",
    "    \"D\": impostor_scores[4],\n",
    "}\n",
    "\n",
    "# Prepare the long-format DataFrame for seaborn\n",
    "df_fmr = build_fmr_dataframe(group_scores)\n",
    "\n",
    "sns.set_theme(style=\"dark\")\n",
    "palette = sns.cubehelix_palette(5, rot=-.5, light=.7)  # << your palette\n",
    "sns.set_palette(palette)                               # make it the default\n",
    "\n",
    "with plt.rc_context(rc={\"font.family\": \"DejaVu Sans Mono\"}):\n",
    "\n",
    "    # Plot with Seaborn\n",
    "    plt.figure(figsize=(10, 5))\n",
    "    sns.lineplot(data=df_fmr, x=\"Threshold\", y=\"FMR\", hue=\"Group\", linewidth=2)\n",
    "\n",
    "    plt.yscale(\"log\")  # Optional: Log scale to highlight low FMR regions\n",
    "    plt.title(\"FMR vs Threshold: partialFC\")\n",
    "    plt.xlabel(\"Similarity Threshold\")\n",
    "    plt.ylabel(\"False Match Rate (FMR)\")\n",
    "    plt.xlim(left=-0.22)\n",
    "    plt.grid(True, which='both', linestyle='--', linewidth=0.5)\n",
    "    plt.legend(title=\"Group\", loc='upper right')\n",
    "    plt.tight_layout()\n",
    "    plt.show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5a2de1dc-dbcd-4220-964e-16aa10b374fa",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
