{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cbbdf3a2",
   "metadata": {},
   "source": [
    "# Face Analysis with UniFace\n",
    "\n",
    "This notebook demonstrates comprehensive face analysis using the **FaceAnalyzer** class.\n",
    "\n",
    "## 1. Install UniFace\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bb0037e2",
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install -q uniface\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eb93156a",
   "metadata": {},
   "source": [
    "## 2. Import Libraries\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "df9115e1",
   "metadata": {},
   "outputs": [],
   "source": [
    "import cv2\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "from uniface import FaceAnalyzer, RetinaFace, ArcFace, AgeGender\n",
    "from uniface.visualization import draw_detections\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3883457d",
   "metadata": {},
   "source": [
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "49346c0d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✓ Model loaded (CoreML (Apple Silicon))\n",
      "✓ Model loaded (CoreML (Apple Silicon))\n",
      "✓ Model loaded (CoreML (Apple Silicon))\n"
     ]
    }
   ],
   "source": [
    "analyzer = FaceAnalyzer(\n",
    "    detector=RetinaFace(),\n",
    "    recognizer=ArcFace(),\n",
    "    age_gender=AgeGender()\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bddc7700",
   "metadata": {},
   "source": [
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "1d32a1da",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "../assets/test_images/image0.jpg: Detected 1 face(s)\n",
      "  Face 1: Female, 28y, conf=1.000\n",
      "\n",
      "../assets/test_images/image1.jpg: Detected 1 face(s)\n",
      "  Face 1: Female, 29y, conf=1.000\n",
      "\n",
      "../assets/test_images/image2.jpg: Detected 1 face(s)\n",
      "  Face 1: Female, 28y, conf=1.000\n"
     ]
    }
   ],
   "source": [
    "image_paths = [\n",
    "    '../assets/test_images/image0.jpg',\n",
    "    '../assets/test_images/image1.jpg',\n",
    "    '../assets/test_images/image2.jpg',\n",
    "]\n",
    "\n",
    "results = []\n",
    "\n",
    "for image_path in image_paths:\n",
    "    # Load image\n",
    "    image = cv2.imread(image_path)\n",
    "    if image is None:\n",
    "        print(f'Error: Could not read {image_path}')\n",
    "        continue\n",
    "\n",
    "    # Analyze faces\n",
    "    faces = analyzer.analyze(image)\n",
    "\n",
    "    print(f'\\n{image_path}: Detected {len(faces)} face(s)')\n",
    "    for i, face in enumerate(faces, 1):\n",
    "        print(f'  Face {i}: {face.gender}, {face.age}y, conf={face.confidence:.3f}')\n",
    "\n",
    "    # Prepare visualization\n",
    "    vis_image = image.copy()\n",
    "    bboxes = [f.bbox for f in faces]\n",
    "    scores = [f.confidence for f in faces]\n",
    "    landmarks = [f.landmarks for f in faces]\n",
    "    draw_detections(vis_image, bboxes, scores, landmarks, vis_threshold=0.5)\n",
    "\n",
    "    # Add age/gender labels\n",
    "    for i, face in enumerate(faces, 1):\n",
    "        x1, y1 = int(face.bbox[0]), int(face.bbox[1])\n",
    "        text = f'{face.gender}, {face.age}y'\n",
    "        cv2.putText(vis_image, text, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)\n",
    "\n",
    "    results.append((image_path, cv2.cvtColor(vis_image, cv2.COLOR_BGR2RGB), faces))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "24f7d83a",
   "metadata": {},
   "source": [
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "5d072b12",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 592528 characters]",
      "text/plain": [
       "<Figure size 1500x500 with 3 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axes = plt.subplots(1, len(results), figsize=(15, 5))\n",
    "\n",
    "for idx, (path, vis_image, faces) in enumerate(results):\n",
    "    axes[idx].imshow(vis_image)\n",
    "    axes[idx].axis('off')\n",
    "    axes[idx].set_title(f'{len(faces)} face(s)', fontsize=10)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9ab7272c",
   "metadata": {},
   "source": [
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "f5009932",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Face Attributes:\n",
      "  - Bounding box: [88, 54, 442, 444]\n",
      "  - Confidence: 1.000\n",
      "  - Landmarks shape: (5, 2)\n",
      "  - Age: 28 years\n",
      "  - Gender string: Female\n",
      "  - Embedding shape: (1, 512)\n",
      "  - Embedding norm: 1D, L2-norm ≈ 1.0\n"
     ]
    }
   ],
   "source": [
    "# Get first face from first image\n",
    "_, _, faces = results[0]\n",
    "if faces:\n",
    "    face = faces[0]\n",
    "\n",
    "    print('Face Attributes:')\n",
    "    print(f'  - Bounding box: {face.bbox.astype(int).tolist()}')\n",
    "    print(f'  - Confidence: {face.confidence:.3f}')\n",
    "    print(f'  - Landmarks shape: {face.landmarks.shape}')\n",
    "    print(f'  - Age: {face.age} years')\n",
    "    print(f'  - Gender string: {face.gender}')\n",
    "    print(f'  - Embedding shape: {face.embedding.shape}')\n",
    "    print(f'  - Embedding norm: {face.embedding.shape[0]}D, L2-norm ≈ 1.0')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "154247c1",
   "metadata": {},
   "source": [
    "## Notes\n",
    "\n",
    "- `analyzer.analyze()` performs detection, recognition, and attribute prediction in one call\n",
    "- Gender is returned as integer ID: `0` for Female, `1` for Male\n",
    "- Use `face.gender` property to get string representation (\"Female\" or \"Male\")\n",
    "- Face embeddings are normalized (L2-norm ≈ 1.0) for similarity computation\n",
    "- Use `face.compute_similarity(other_face)` to compare faces\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "c3cadebf",
   "metadata": {},
   "outputs": [],
   "source": [
    "face1 = faces[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "d7bb6975",
   "metadata": {},
   "outputs": [],
   "source": [
    "face2 = faces[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "d3f5f834",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1, 512)"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "face1.embedding.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "1c8b72df",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.9999901056289673"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "face1.compute_similarity(face2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4a970f13",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
