{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "m4-s07-01",
   "metadata": {},
   "source": "# M4 · Sesión 07 — Detección de objetos y segmentación\n\n**Deep Learning y Visión Artificial** — 6 ECTS  \nMáster en Inteligencia Artificial, Automatización y Agentes Digitales · Next Educación · Próxima edición\n\n---\n\n## Práctica de la sesión: Detector entrenado sobre un conjunto propio\n\nCiclo completo de anotación asistida, entrenamiento y evaluación de un detector sobre imágenes de un caso real.\n\n| | |\n|---|---|\n| **Stack** | Ultralytics, PyTorch, COCO tools |\n| **Entregable** | Detector entrenado con informe de evaluación y análisis de fallos. |\n\n### Objetivos que se evalúan\n\n1. Distinguir clasificación, detección y segmentación y su coste de etiquetado.\n2. Entrenar un detector sobre un conjunto propio.\n3. Evaluar correctamente con métricas específicas de detección.\n\n> **Cómo se corrige.** No se evalúa solo el resultado: se evalúan la justificación escrita de cada decisión, la honestidad de la validación y que el cuaderno se ejecute de principio a fin en un equipo distinto al tuyo."
  },
  {
   "cell_type": "markdown",
   "id": "m4-s07-02",
   "metadata": {},
   "source": "---\n## 0. Entorno\n\nEjecuta esta celda antes que ninguna otra. Fija la semilla y el estilo de figuras que se usa en todo el máster, de modo que los resultados sean comparables entre alumnos."
  },
  {
   "cell_type": "code",
   "id": "m4-s07-03",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "import platform\nimport numpy as np\nimport pandas as pd\nimport matplotlib\nimport matplotlib.pyplot as plt\n\nRANDOM_STATE = 42\nrng = np.random.default_rng(RANDOM_STATE)\nnp.random.seed(RANDOM_STATE)\n\npd.set_option(\"display.width\", 118)\npd.set_option(\"display.max_columns\", 40)\nplt.rcParams.update({\n    \"figure.figsize\": (9, 4.2), \"figure.dpi\": 110, \"axes.grid\": True,\n    \"grid.alpha\": .25, \"axes.spines.top\": False, \"axes.spines.right\": False,\n    \"font.size\": 10, \"axes.titlesize\": 12, \"axes.titleweight\": \"bold\",\n    \"axes.titlecolor\": \"#12395e\", \"axes.labelcolor\": \"#34495e\",\n})\nAZUL, OSCURO, ROJO = \"#2a76dd\", \"#12395e\", \"#e74c3c\"\n\nprint(\"Python\", platform.python_version(), \"| pandas\", pd.__version__,\n      \"| numpy\", np.__version__, \"| matplotlib\", matplotlib.__version__)"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s07-04",
   "metadata": {},
   "source": "---\n## 1. Datos de la sesión: imágenes sintéticas etiquetadas\n\nSe generan formas geométricas de 32×32 píxeles con ruido controlado. El conjunto permite entrenar y depurar sin descargas, y traslada los mismos problemas que un dataset real: desbalanceo, ruido y ambigüedad entre clases."
  },
  {
   "cell_type": "code",
   "id": "m4-s07-05",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "TAM, N = 32, 1200\nCLASES = [\"circulo\", \"cuadrado\", \"franja\"]\n\ndef dibuja(clase, ruido=.16):\n    img = np.zeros((TAM, TAM), dtype=float)\n    yy, xx = np.mgrid[0:TAM, 0:TAM]\n    cx, cy = rng.integers(11, 21, 2)\n    if clase == \"circulo\":\n        img[(xx - cx) ** 2 + (yy - cy) ** 2 <= rng.integers(5, 9) ** 2] = 1\n    elif clase == \"cuadrado\":\n        l = rng.integers(9, 15); img[cy - l // 2:cy + l // 2, cx - l // 2:cx + l // 2] = 1\n    else:\n        g = rng.integers(4, 8); img[:, cx - g // 2:cx + g // 2] = 1\n    return np.clip(img + rng.normal(0, ruido, (TAM, TAM)), 0, 1)\n\netiquetas = rng.choice(CLASES, N, p=[.45, .35, .20])       # desbalanceo deliberado\nX = np.stack([dibuja(c) for c in etiquetas])\ny = pd.Series(etiquetas, name=\"clase\")\n\nprint(\"X:\", X.shape, \"| rango [%.2f, %.2f]\" % (X.min(), X.max()))\ny.value_counts().rename(\"imágenes\").to_frame()"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s07-06",
   "metadata": {},
   "source": "### 1.1 Perfilado\n\nAntes de tocar nada, mirar el dato. Este bloque es el mínimo exigible en cualquier entrega del módulo."
  },
  {
   "cell_type": "code",
   "id": "m4-s07-07",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "estad = pd.DataFrame({\n    \"media_intensidad\": X.reshape(len(X), -1).mean(1),\n    \"desv_intensidad\": X.reshape(len(X), -1).std(1),\n    \"pixeles_activos\": (X > .5).reshape(len(X), -1).sum(1),\n    \"clase\": y,\n})\nprint(\"Estadísticos por clase (útiles para detectar fuga por atajo visual):\")\nestad.groupby(\"clase\").agg([\"mean\", \"std\"]).round(3)"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s07-08",
   "metadata": {},
   "source": "### 1.2 Lectura visual\n\nUna figura que responde a una pregunta concreta, no un gráfico decorativo."
  },
  {
   "cell_type": "code",
   "id": "m4-s07-09",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "fig, axes = plt.subplots(3, 6, figsize=(10, 5.4))\nfor fila, clase in enumerate(CLASES):\n    idx = np.flatnonzero(y == clase)[:6]\n    for col, i in enumerate(idx):\n        axes[fila, col].imshow(X[i], cmap=\"gray\", vmin=0, vmax=1)\n        axes[fila, col].set_xticks([]); axes[fila, col].set_yticks([]); axes[fila, col].grid(False)\n    axes[fila, 0].set_ylabel(clase, color=OSCURO, fontweight=\"bold\")\nfig.suptitle(\"Muestra del conjunto por clase\", color=OSCURO, fontweight=\"bold\")\nplt.tight_layout(); plt.show()"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s07-10",
   "metadata": {},
   "source": "---\n## 2. Ejemplo trabajado: una línea base sin red neuronal\n\nAntes de entrenar nada profundo, conviene saber cuánto se consigue con estadísticos elementales. Ese número es la referencia contra la que se juzga después la red."
  },
  {
   "cell_type": "code",
   "id": "m4-s07-11",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "from collections import Counter\n\ncarac = np.c_[X.reshape(len(X), -1).mean(1), (X > .5).reshape(len(X), -1).sum(1)]\ncorte = int(.7 * len(X))\nXtr, Xte, ytr, yte = carac[:corte], carac[corte:], y[:corte].to_numpy(), y[corte:].to_numpy()\n\ncentroides = {c: Xtr[ytr == c].mean(0) for c in CLASES}\npred = np.array([min(centroides, key=lambda c: np.linalg.norm(x - centroides[c])) for x in Xte])\n\ntrivial = Counter(ytr).most_common(1)[0][0]\nprint(f\"Línea base trivial (siempre '{trivial}') : {(yte == trivial).mean():.1%}\")\nprint(f\"Centroide sobre 2 características        : {(pred == yte).mean():.1%}\")\nprint(\"\\nCualquier modelo posterior tiene que superar claramente estas dos cifras para justificarse.\")"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s07-12",
   "metadata": {},
   "source": "---\n## 3. Tu turno: Detector entrenado sobre un conjunto propio\n\nA partir de aquí trabajas tú. Cada paso tiene una celda de código y una celda de justificación: **las dos se corrigen**. Si adaptas la práctica a tu propio proyecto, indícalo en el primer paso."
  },
  {
   "cell_type": "markdown",
   "id": "m4-s07-13",
   "metadata": {},
   "source": "### 3.1 Anotar un lote reducido con asistencia de un modelo de segmentación."
  },
  {
   "cell_type": "code",
   "id": "m4-s07-14",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# Paso 1: Anotar un lote reducido con asistencia de un modelo de segmentación.\n# TODO\n"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s07-15",
   "metadata": {},
   "source": "> **Justificación del paso 1.** _(dos o tres frases)_ Qué has decidido, con qué criterio, y qué alternativa has descartado y por qué."
  },
  {
   "cell_type": "markdown",
   "id": "m4-s07-16",
   "metadata": {},
   "source": "### 3.2 Convertir las anotaciones al formato requerido y verificarlas."
  },
  {
   "cell_type": "code",
   "id": "m4-s07-17",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# Paso 2: Convertir las anotaciones al formato requerido y verificarlas.\n# TODO\n"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s07-18",
   "metadata": {},
   "source": "> **Justificación del paso 2.** _(dos o tres frases)_ Qué has decidido, con qué criterio, y qué alternativa has descartado y por qué."
  },
  {
   "cell_type": "markdown",
   "id": "m4-s07-19",
   "metadata": {},
   "source": "### 3.3 Entrenar el detector con pesos preentrenados."
  },
  {
   "cell_type": "code",
   "id": "m4-s07-20",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# Paso 3: Entrenar el detector con pesos preentrenados.\n# TODO\n"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s07-21",
   "metadata": {},
   "source": "> **Justificación del paso 3.** _(dos o tres frases)_ Qué has decidido, con qué criterio, y qué alternativa has descartado y por qué."
  },
  {
   "cell_type": "markdown",
   "id": "m4-s07-22",
   "metadata": {},
   "source": "### 3.4 Evaluar con precisión media promediada y analizar los fallos por tamaño de objeto."
  },
  {
   "cell_type": "code",
   "id": "m4-s07-23",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# Paso 4: Evaluar con precisión media promediada y analizar los fallos por tamaño de objeto.\n# TODO\n"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s07-24",
   "metadata": {},
   "source": "> **Justificación del paso 4.** _(dos o tres frases)_ Qué has decidido, con qué criterio, y qué alternativa has descartado y por qué."
  },
  {
   "cell_type": "markdown",
   "id": "m4-s07-25",
   "metadata": {},
   "source": "---\n## 4. Cierre y entrega\n\n**Entregable:** Detector entrenado con informe de evaluación y análisis de fallos.\n\nAntes de subir el cuaderno al Campus Virtual, repasa la lista. La celda de abajo verifica la parte que puede verificarse sola; el resto lo marcas tú al completarlo."
  },
  {
   "cell_type": "code",
   "id": "m4-s07-26",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "comprobaciones = {\n    \"Semilla aleatoria fijada\": \"RANDOM_STATE\" in dir(),\n    \"Sin rutas absolutas del equipo\": True,\n    \"Justificacion escrita en todos los pasos\": False,\n    \"Ejecutado de arriba abajo sin errores\": False,\n    \"Sin datos personales ni credenciales\": True,\n}\nfor k, v in comprobaciones.items():\n    print((\"  OK   \" if v else \" FALTA \") + \"| \" + k)\n\nprint()\nprint(\"Listo para entregar.\" if all(comprobaciones.values())\n      else \"Completa lo que falta antes de entregar.\")"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s07-27",
   "metadata": {},
   "source": "---\n*M4 · Sesión 07 — Detección de objetos y segmentación · Máster en Inteligencia Artificial, Automatización y Agentes Digitales · Next Educación*  \n*Material docente elaborado por Sami Halawa Ribas. Pendiente de validación por la Dirección del programa.*"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11",
   "mimetype": "text/x-python",
   "file_extension": ".py",
   "pygments_lexer": "ipython3",
   "nbconvert_exporter": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}