{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Face Analysis with UniFace\n",
    "\n",
    "This notebook demonstrates comprehensive face analysis using the **FaceAnalyzer** class.\n",
    "\n",
    "## 1. Install UniFace"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Note: you may need to restart the kernel to use updated packages.\n"
     ]
    }
   ],
   "source": [
    "%pip install -q uniface"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Import Libraries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.3.1\n"
     ]
    }
   ],
   "source": [
    "import cv2\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "import uniface\n",
    "from uniface import FaceAnalyzer\n",
    "from uniface.detection import RetinaFace\n",
    "from uniface.recognition import ArcFace\n",
    "from uniface.attribute import AgeGender\n",
    "from uniface.visualization import draw_detections\n",
    "\n",
    "print(uniface.__version__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Initialize FaceAnalyzer\n",
    "\n",
    "The `FaceAnalyzer` combines detection, recognition, and attribute prediction in one class."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "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(conf_thresh=0.5),\n",
    "    recognizer=ArcFace(),\n",
    "    age_gender=AgeGender()\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Analyze Faces in Images"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "image0.jpg: Detected 1 face(s)\n",
      "  Face 1: Female, 28y\n",
      "\n",
      "image1.jpg: Detected 1 face(s)\n",
      "  Face 1: Female, 29y\n",
      "\n",
      "image2.jpg: Detected 1 face(s)\n",
      "  Face 1: Female, 28y\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 - returns list of Face objects\n",
    "    faces = analyzer.analyze(image)\n",
    "    print(f'\\n{image_path.split(\"/\")[-1]}: Detected {len(faces)} face(s)')\n",
    "\n",
    "    # Print face attributes\n",
    "    for i, face in enumerate(faces, 1):\n",
    "        print(f'  Face {i}: {face.sex}, {face.age}y')\n",
    "\n",
    "    # Prepare visualization (without text overlay)\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(image=vis_image, bboxes=bboxes, scores=scores, landmarks=landmarks, vis_threshold=0.5, fancy_bbox=True)\n",
    "\n",
    "    results.append((image_path, cv2.cvtColor(vis_image, cv2.COLOR_BGR2RGB), faces))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Visualize Results\n",
    "\n",
    "Display images with face information shown below each image."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 614908 characters]",
      "text/plain": [
       "<Figure size 1500x800 with 6 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axes = plt.subplots(2, len(results), figsize=(15, 8),\n",
    "                         gridspec_kw={'height_ratios': [4, 1]})\n",
    "\n",
    "for idx, (path, vis_image, faces) in enumerate(results):\n",
    "    # Display image\n",
    "    axes[0, idx].imshow(vis_image)\n",
    "    axes[0, idx].axis('off')\n",
    "\n",
    "    # Display face information below image\n",
    "    axes[1, idx].axis('off')\n",
    "    info_text = f'{len(faces)} face(s)\\n'\n",
    "    for i, face in enumerate(faces, 1):\n",
    "        info_text += f'Face {i}: {face.sex}, {face.age}y\\n'\n",
    "\n",
    "    axes[1, idx].text(0.5, 0.5, info_text,\n",
    "                      ha='center', va='center',\n",
    "                      fontsize=10, family='monospace')\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Access Face Attributes\n",
    "\n",
    "Each `Face` object contains detection, recognition, and attribute data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "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: Female\n",
      "  - Embedding shape: (1, 512)\n",
      "  - Embedding dimension: 512D\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: {face.sex}')\n",
    "    print(f'  - Embedding shape: {face.embedding.shape}')\n",
    "    print(f'  - Embedding dimension: {face.embedding.shape[1]}D')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7. Compare Face Similarity\n",
    "\n",
    "Use face embeddings to compute similarity between faces."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Similarity between faces: 0.1135\n",
      "Same person: No (threshold=0.6)\n"
     ]
    }
   ],
   "source": [
    "# Compare first two faces\n",
    "if len(results) >= 2:\n",
    "    face1 = results[0][2][0]  # First face from first image\n",
    "    face2 = results[1][2][0]  # First face from second image\n",
    "\n",
    "    similarity = face1.compute_similarity(face2)\n",
    "    print(f'Similarity between faces: {similarity:.4f}')\n",
    "    print(f'Same person: {\"Yes\" if similarity > 0.6 else \"No\"} (threshold=0.6)')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Notes\n",
    "\n",
    "- `analyzer.analyze()` performs detection, recognition, and attribute prediction in one call\n",
    "- Each `Face` object contains: `bbox`, `confidence`, `landmarks`, `embedding`, `age`, `gender`\n",
    "- Gender is available as both ID (0=Female, 1=Male) and string via `face.sex` property\n",
    "- Face embeddings are L2-normalized (norm ≈ 1.0) for similarity computation\n",
    "- Use `face.compute_similarity(other_face)` to compare faces (returns cosine similarity)\n",
    "- Typical similarity threshold: 0.6 (same person if similarity > 0.6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "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.13.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
