{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "70203b13",
   "metadata": {},
   "source": [
    "# Modul 2 Eftermiddag\n",
    "\n",
    "## Introduktion til SVD"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d735785d",
   "metadata": {},
   "source": [
    "Enhver reel $m \\times n$ matrix $A$ kan skrives på SVD-formen\n",
    "\n",
    "$$\n",
    "A = U\\Sigma V^T,\n",
    "$$\n",
    "\n",
    "hvor $V$ er en ortogonal $n\\times n$ matrix, $\\Sigma$ er en $m \\times n$ diagonal matrix, og $U$ er en ortogonal $m \\times m$ matrix.\n",
    "\n",
    "Elementerne i $\\Sigma$ kaldes singulærværdierne. De er alle større eller lig med $0$ og står i ikke-stigende rækkefølge.\n",
    "\n",
    "Nedenfor er et eksempel på en $3\\times 4$ matrix med singulærværdierne $12$, $6$ og $0$, som er opskrevet på SVD-form:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9b932da2",
   "metadata": {},
   "source": [
    "$$\n",
    "\\begin{bmatrix}\n",
    "6 & 2 & -2 & 6 \\\\\n",
    "0 & -4 & 4 & 0 \\\\\n",
    "3 & 5 & -5 & 3\n",
    "\\end{bmatrix}\n",
    "=\n",
    "\\begin{bmatrix}\n",
    "2/3 & -2/3 & -1/3 \\\\\n",
    "-1/3 & -2/3 & 2/3 \\\\\n",
    "2/3 & 1/3 & 2/3\n",
    "\\end{bmatrix}\n",
    "\\begin{bmatrix}\n",
    "12 & 0 & 0 & 0 \\\\\n",
    "0 & 6 & 0 & 0 \\\\\n",
    "0 & 0 & 0 & 0\n",
    "\\end{bmatrix}\n",
    "\\begin{bmatrix}\n",
    "1/2 & -1/2 & -\\sqrt{2}/2 & 0 \\\\\n",
    "1/2 & 1/2 & 0 & \\sqrt{2}/2 \\\\\n",
    "-1/2 & -1/2 & 0 & \\sqrt{2}/2 \\\\\n",
    "1/2 & -1/2 & \\sqrt{2}/2 & 0\n",
    "\\end{bmatrix}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "384958fb",
   "metadata": {},
   "source": [
    "I Python kan vi benytte SVD-kommandoen `np.linalg.svd(A)` for en $m\\times n$ matrix $A$, når vi har importeret `numpy` (se under \"Opsætning\" nedenfor)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f2552693",
   "metadata": {},
   "source": [
    "# Matrix Approksimation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c027dea2",
   "metadata": {},
   "source": [
    "## Opsætning\n",
    "\n",
    "Vi importerer de nødvendige python-pakker."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1ba76445",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Biblioteker vi bruger\n",
    "import numpy as np\n",
    "from numpy.linalg import svd, matrix_rank\n",
    "import matplotlib.pyplot as plt\n",
    "from matplotlib.colors import ListedColormap, BoundaryNorm\n",
    "from matplotlib.image import imread\n",
    "from PIL import Image\n",
    "\n",
    "# Visninger\n",
    "np.set_printoptions(precision=3, suppress=True)\n",
    "plt.rcParams.update({'figure.max_open_warning': 0})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f1683194",
   "metadata": {},
   "source": [
    "## SVD kommando for en $m\\times n$ matrix $A$\n",
    "\n",
    "En $m\\times n$ matrix med tilfældige heltal mellem eksempelvis $1$ og $5$ som indgange kan laves med `np.random.randint()`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c56dafb5",
   "metadata": {},
   "source": [
    "> Vælg $m$ og $n$ og dan en tilfældigt generet matrix"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "80245c83",
   "metadata": {},
   "outputs": [],
   "source": [
    "m = 'INDSÆT KODE HER'\n",
    "n = 'INDSÆT KODE HER'\n",
    "np.random.seed(0) # fastlåser \"tilfældigheden\"\n",
    "A = np.random.randint(1,6,size=(m,n))\n",
    "A"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0e37aeb",
   "metadata": {},
   "source": [
    "SVD-kommandoen `np.linalg.svd(A)` for en matrix $A$ giver ($m\\times m$)-matricen $U$, $\\,$ ($n\\times n$)-matricen $V^T$ og en vektor $\\boldsymbol{s}$ med singulærværdier i ikke-stigende orden."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "815e2ff8",
   "metadata": {},
   "source": [
    "> Benyt SVD-kommandoen til at fremkalde $U$, $V^T$ og vektoren $\\boldsymbol{s}$ for matricen $A$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0cb82fc9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Beregn SVD\n",
    "U, s, Vt = 'INDSÆT KODE HER'\n",
    "print('\\nSingulærværdier (s):', s)\n",
    "print('\\nU =\\n', U)\n",
    "print('\\nVt =\\n', Vt)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51ae2087",
   "metadata": {},
   "source": [
    "> Dan $\\Sigma$ matricen ud fra singulærværdierne i $\\boldsymbol{s}$ ved at færdiggøre koden nedenfor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "04c424f4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Vi opretter en nulmatrix med samme dimensioner som A (m x n).\n",
    "Sigma = np.zeros((m, n))\n",
    "\n",
    "# Indæst de singulære værdier (s) på diagonalen.\n",
    "for i in range(min(m, n)):\n",
    "    Sigma[i, i] = 'INDSÆT KODE HER'\n",
    "\n",
    "print('\\nSigma =\\n', Sigma)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4dbd3b5b",
   "metadata": {},
   "source": [
    "> Vis at SVD-kommadoen passer ved at danne produktet $U\\Sigma V^T$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3a1a60e0",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Husk at bruge @ til at lave matrixmultiplikation\n",
    "recon = 'INDSÆT KODE HER'\n",
    "print('\\nRekonstruktion (U @ Sigma @ Vt) =\\n', np.round(recon,3))\n",
    "print('\\nA =\\n', A)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "72f598c9",
   "metadata": {},
   "source": [
    "## En rang-$r$ matrix er en sum af $r$ lineært uafhængige rang-$1$ matricer"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "375ea1ba",
   "metadata": {},
   "source": [
    "Lad $A$ være en $m \\times n$ matrix med rangen $r \\leq \\min\\{m,n\\}$ og lad $\\boldsymbol{u_1}, \\boldsymbol{u_2}, \\dots, \\boldsymbol{u_r}$ betegne de første $r$ søjler i $U$ og $\\boldsymbol{v_1}^T, \\boldsymbol{v_2}^T, \\dots, \\boldsymbol{v_r}^T$ de første $r$ rækker i $V^T$.  \n",
    "\n",
    "Matricen $A$ kan da opskrives som summen\n",
    "\n",
    "$$\n",
    "A = \\sigma_1 \\boldsymbol{u_1} \\boldsymbol{v_1}^T + \\sigma_2 \\boldsymbol{u_2} \\boldsymbol{v_2}^T + \\dots + \\sigma_r \\boldsymbol{u_r} \\boldsymbol{v_r}^T.\n",
    "$$\n",
    "\n",
    "![SVD](images/A_SVD.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "af94ecc5",
   "metadata": {},
   "source": [
    "For store matricer med lav rang er ovenstående en effektiv lagringsmetode med et minimalt forbrug af tal.  \n",
    "Vi indfører derfor udtrykket **lagringstallet** $LA$."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "54a81962",
   "metadata": {},
   "source": [
    "> Gør rede for at $LA$ er givet ved\n",
    ">\n",
    "> $$\n",
    " LA(r,m,n) = \\dfrac{r (1 + m + n)}{m n}\n",
    " $$\n",
    ">\n",
    "> &nbsp;"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "160e626f",
   "metadata": {},
   "source": [
    "> Kommentér kort, hvad der sker med lagringstallet, når $r$ nærmer sig $\\min(m, n)$."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5955f75c",
   "metadata": {},
   "source": [
    "Vi ser på matricen\n",
    "\n",
    "$$\n",
    "A =\n",
    "\\begin{bmatrix}\n",
    "11 & 2 & 8 \\\\\n",
    "5 & -10 & 8 \\\\\n",
    "3 & -6 & 12 \\\\\n",
    "13 & -2 & 4\n",
    "\\end{bmatrix}.\n",
    "$$\n",
    "\n",
    "$A$ har SVD-dekompositionen  \n",
    "\n",
    "$$\n",
    "A =\n",
    "\\begin{bmatrix}\n",
    "1/2 &  1/2 & 1/2 & 1/2 \\\\\n",
    "1/2 & -1/2 &  -1/2 & 1/2 \\\\\n",
    "1/2 & -1/2 & 1/2 &  -1/2 \\\\\n",
    "1/2 &  1/2 &  -1/2 &  -1/2\n",
    "\\end{bmatrix}\n",
    "\\begin{bmatrix}\n",
    "24 & 0 & 0 \\\\\n",
    "0 & 12 & 0 \\\\\n",
    "0 & 0 & 6 \\\\\n",
    "0 & 0 & 0\n",
    "\\end{bmatrix}\n",
    "\\begin{bmatrix}\n",
    "2/3 &  -1/3 & 2/3 \\\\\n",
    "2/3 &  2/3 & -1/3 \\\\\n",
    "-1/3 & 2/3 & 2/3\n",
    "\\end{bmatrix}.\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6c9632cf",
   "metadata": {},
   "source": [
    "> Angiv rangen af $A$ direkte ud fra SVD opstillingen. \n",
    "\n",
    "> Tjek dit svar ved at definere $A$ ved at bruge kommandoen `np.array([], dtype=int)` og benyt kommandoen `np.linalg.matrix_rank()` til at finde rangen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5b30b59b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# DEFINÉR A HER OG FIND RANGEN"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c075665d",
   "metadata": {},
   "source": [
    "> Brug $\\boldsymbol{u_1}$, $\\boldsymbol{u_2}$ og $\\boldsymbol{u_3}$ samt $\\boldsymbol{v_1}^T$, $\\boldsymbol{v_2}^T$ og $\\boldsymbol{v_3}^T$ i nedenstående kodecelle til at eftervise $(*)$ i eksemplet. \\\n",
    "> Du kan benytte `np.outer()` til at beregne $\\boldsymbol{u_i}\\boldsymbol{v_i}^T$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "03386aba",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Singularvektorer fra SVD (som tidligere vist)\n",
    "u1 = np.array([1/2, 1/2, 1/2, 1/2])\n",
    "u2 = np.array([1/2, -1/2, -1/2, 1/2])\n",
    "u3 = np.array([1/2, -1/2, 1/2, -1/2])\n",
    "\n",
    "v1T = np.array([2/3, -1/3, 2/3])\n",
    "v2T = np.array([2/3, 2/3, -1/3])\n",
    "v3T = np.array([-1/3, 2/3, 2/3])\n",
    "\n",
    "# Singularværdier\n",
    "sigma1, sigma2, sigma3 = 24, 12, 6\n",
    "\n",
    "# Eftervis relationen (*)\n",
    "A_reconstructed = 'INDSÆT KODE HER'\n",
    "\n",
    "print(\"A rekonstrueret fra u1,u2,u3 og v1T,v2T,v3T:\\n\", A_reconstructed)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e36e0f45",
   "metadata": {},
   "source": [
    "Funktionen nedenfor beregner lagringstallet $LA$ givet $r$, $m$ og $n$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cc682faa",
   "metadata": {},
   "outputs": [],
   "source": [
    "def Lagringstal(r,m,n):\n",
    "    return r*(1+m+n)/(m*n)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1daa5867",
   "metadata": {},
   "source": [
    "> Bestem lagringstallet for $A$ ved at benytte funktionen `Lagringstal`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e9d61c1f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# INDSÆT KODE HER"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e35ccbba",
   "metadata": {},
   "source": [
    "NB: For små matricer med høj rang er $(*)$ en dyr måde at lagre på!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22e095c6",
   "metadata": {},
   "source": [
    "## Rang-$k$ matrix approksimation og bevaret information"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b674f618",
   "metadata": {},
   "source": [
    "Givet en $m \\times n$ matrix $A$ med rangen $r \\leq \\min\\{m,n\\}$ og lad $k$ være et positivt helt tal med $k \\leq r$.\n",
    "En rang-$k$ approksimation $A_k$ af $A$ fremkommer ved trunkering af den fulde SVD for $A$, det vil sige, at man kun beholder de første $k$ singulærværdier og tilsvarende singulærvektorer, mens de resterende singulærværdier sættes lig $0$."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5079d9f0",
   "metadata": {},
   "source": [
    "![Trunkering](images/trunkering.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3fea8d05",
   "metadata": {},
   "source": [
    "Dette kan matematisk skrives som:\n",
    "\n",
    "$$\n",
    "A = \\sum_{i=1}^{r} \\sigma_i \\mathbf{u}_i \\mathbf{v}_i^T\n",
    "\\quad \\xrightarrow{\\text{Trunkering ved } k} \\quad\n",
    "A_k = \\sum_{i=1}^{k} \\sigma_i \\mathbf{u}_i \\mathbf{v}_i^T\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "287a3a72",
   "metadata": {},
   "source": [
    "Vi betrager igen matricen\n",
    "\n",
    "$$\n",
    "A =\n",
    "\\begin{bmatrix}\n",
    "11 & 2 & 8 \\\\\n",
    "5 & -10 & 8 \\\\\n",
    "3 & -6 & 12 \\\\\n",
    "13 & -2 & 4\n",
    "\\end{bmatrix}.\n",
    "$$\n",
    "\n",
    "Husk at $A$ har SVD-dekompositionen  \n",
    "\n",
    "$$\n",
    "A =\n",
    "\\begin{bmatrix}\n",
    "1/2 &  1/2 & 1/2 & 1/2 \\\\\n",
    "1/2 & -1/2 &  -1/2 & 1/2 \\\\\n",
    "1/2 & -1/2 & 1/2 &  -1/2 \\\\\n",
    "1/2 &  1/2 &  -1/2 &  -1/2\n",
    "\\end{bmatrix}\n",
    "\\begin{bmatrix}\n",
    "24 & 0 & 0 \\\\\n",
    "0 & 12 & 0 \\\\\n",
    "0 & 0 & 6 \\\\\n",
    "0 & 0 & 0\n",
    "\\end{bmatrix}\n",
    "\\begin{bmatrix}\n",
    "2/3 &  -1/3 & 2/3 \\\\\n",
    "2/3 &  2/3 & -1/3 \\\\\n",
    "-1/3 & 2/3 & 2/3\n",
    "\\end{bmatrix}.\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa75d305",
   "metadata": {},
   "source": [
    "> Bestem rang-$1$, rang-$2$ og rang-$3$ approksimationerne $A_1$, $A_2$ og $A_3$ ved hjælp af $(*)$ til nedenstående."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ffb9eb93",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Rang-1 approksimation\n",
    "A1 = 'INDSÆT KODE HER'\n",
    "\n",
    "# Rang-2 approksimation\n",
    "A2 = 'INDSÆT KODE HER'\n",
    "\n",
    "# Rang-3 approksimation\n",
    "A3 = 'INDSÆT KODE HER'\n",
    "\n",
    "# Udskriv resultaterne\n",
    "print(\"Rang-1 approksimation A1:\\n\", A1)\n",
    "print(\"\\nRang-2 approksimation A2:\\n\", A2)\n",
    "print(\"\\nRang-3 approksimation A3:\\n\", A3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ff332e9e",
   "metadata": {},
   "source": [
    "> Hvor stor er forskellen mellem $A$ og dens approksimationer? \n",
    "\n",
    "> Definér $A$ i kodecellen nedenfor og udregn $A-A_1$, $A-A_2$ og $A-A_3$ og sammelign de tre resultater."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f6d9f38d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# INDSÆT KODE HER"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3498cb0b",
   "metadata": {},
   "source": [
    "## Hvor meget information bevares ved approksimation?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ba1c009",
   "metadata": {},
   "source": [
    "Det er klart, at jo flere singulærværdier man tager med, desto bedre bliver approksimationen. Man taler om, hvor meget information der bevares ved approksimationen.  \n",
    "\n",
    "Der findes flere måder at vurdere, hvor meget information man bevarer. Den formel, vi benytter her, afhænger kun af, hvor mange singulærværdier man tager med, som jo ved rang-$k$ approksimation er $k$ singulærværdier.  \n",
    "\n",
    "Den **bevarede information** $BI$ ved rang-$k$ approksimation af en $m\\times n$ matrix $A$ med rank $r$ er givet ved  \n",
    "\n",
    "$$\n",
    "BI(k, r) = \\frac{\\sigma_1 + \\sigma_2 + \\dots + \\sigma_k}{\\sigma_1 + \\sigma_2 + \\dots + \\sigma_r}.\n",
    "$$\n",
    "\n",
    "hvor $\\sigma_1, \\dots, \\sigma_r$ er de ikke-negative singulærværdier af $A$, og $r = \\text{rank}(A)$."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3c09e51a",
   "metadata": {},
   "source": [
    "> Bestem den bevarede information ved approksimationerne $A_1$, $A_2$ og $A_3$ i vores eksempel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "01438cd6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Rang af A\n",
    "r = len(s)  \n",
    "\n",
    "# Bevaret information: k = 1,2,3\n",
    "BI1 = 'INDSÆT KODE HER'\n",
    "BI2 = 'INDSÆT KODE HER'\n",
    "BI3 = 'INDSÆT KODE HER'\n",
    "\n",
    "print(\"Bevarede information ved rang-1 approksimation:\", np.round(BI1,3))\n",
    "print(\"Bevarede information ved rang-2 approksimation:\", np.round(BI2,3))\n",
    "print(\"Bevarede information ved rang-3 approksimation:\", np.round(BI3,3))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fbe94924",
   "metadata": {},
   "source": [
    "## Opstilling af funktioner for større matricer"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7952d438",
   "metadata": {},
   "source": [
    "For store matricer $A$ får vi brug for funktioner (procedurer) til bestemmelse af approksimationerne $A_k$, bevaret information $BI$ og illustration af $BI$. \n",
    "\n",
    "Approksimationerne finder vi ved trunkering af summen $(*)$, hvor vi kun medtager de første $k$ led.\n",
    "\n",
    "> Gennemgå koden for funktionen `svd_approksimation()` i cellen nedenunder og indse at den ønskede approksimation $A_k$ bestemmes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "becb7588",
   "metadata": {},
   "outputs": [],
   "source": [
    "def svd_approksimation(A, k):\n",
    "    \"\"\"\n",
    "    Rank-k SVD-approximation\n",
    "    \"\"\"\n",
    "    U, s, Vt = np.linalg.svd(A)\n",
    "    A_k = np.zeros_like(A, dtype=float) # nulmatrix med samme dimensioner som A\n",
    "    for i in range(k):\n",
    "        A_k += s[i] * np.outer(U[:, i], Vt[i, :]) # sum af ydreprodukter givet antal af medtagne singulærværdier\n",
    "    return A_k"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "697e9f1c",
   "metadata": {},
   "source": [
    "> Afprøv `svd_approksimation()` på matricen $A$ fra forrige afsnit for forskellige $k$ og sammenlign resultaterne."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d57b5892",
   "metadata": {},
   "outputs": [],
   "source": [
    "# INDSÆT KODE HER"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d65fb5df",
   "metadata": {},
   "source": [
    "> Gennemgå koden for funktionen `bevarede_information()` i cellen nedenunder og indse at den beregner $BI$ for alle $k$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6448c10c",
   "metadata": {},
   "outputs": [],
   "source": [
    "def bevarede_information(A):\n",
    "    \"\"\"\n",
    "    Bevarede information\n",
    "    \"\"\"\n",
    "    _, s, _ = np.linalg.svd(A) # vi skal kun bruge singulærværdierne\n",
    "    return np.cumsum(s / np.sum(s)) # np.cumsum giver den kumulative sum"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6c18501f",
   "metadata": {},
   "source": [
    "> Beregn $BI$ for alle $k$ ved at bruge funtionen `bevarede_information()` på matricen $A$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "30949fd4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# INDSÆT KODE HER"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0a75575a",
   "metadata": {},
   "source": [
    "> Gennemgå koden for funktionen `plot_bevarede_information()` i cellen nedenunder og indse at den plotter den bevarede information som funktion af antallet af medtagne singulærværdier."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d3594e3b",
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_bevarede_information(A):\n",
    "    bevarede_info = bevarede_information(A) * 100  # i procent\n",
    "\n",
    "    # tilføj et 0 i starten så grafen starter i (0,0)\n",
    "    bevarede_info = np.insert(bevarede_info, 0, 0)\n",
    "    ks = np.arange(0, len(bevarede_info)) # array med antal singulærværdier\n",
    "    \n",
    "    plt.plot(ks, bevarede_info, marker='o')\n",
    "    plt.xlabel(\"Antal singulærværdier (k)\")\n",
    "    plt.ylabel(\"Bevarede information [%]\")\n",
    "    plt.title(\"Bevarede information\")\n",
    "    plt.grid(True, linestyle='--', alpha=0.6)\n",
    "    if len(bevarede_info) > 200:\n",
    "        plt.xticks(ks[::30])\n",
    "    else:\n",
    "        plt.xticks(ks)\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2f803e3c",
   "metadata": {},
   "source": [
    "> Plot den beregnede information som funktion af antallet af medtange singulærværdier ved at bruge funktionen `plot_bevarede_information()` på matricen $A$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c96d7dde",
   "metadata": {},
   "outputs": [],
   "source": [
    "# INDSÆT KODE HER"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "34b2acc0",
   "metadata": {},
   "source": [
    "# LEGO-skibet"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e32a06ad",
   "metadata": {},
   "source": [
    "Vi tager udgangspunkt i et billede af et LEGO-skib fundet på internet.\n",
    "\n",
    "Undervejs kommer vi til at:\n",
    "- Undersøge SVD og rang-$k$ approksimation.\n",
    "- Implementere og visualisere $k$-rangs-rekonstruktion af billeder i Python. \n",
    "- Bruge SVD til billedkompression og tolke singulærværdier."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "44b59ef5",
   "metadata": {},
   "source": [
    "Vi betragter følgende billede fra internettet:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16482a3b",
   "metadata": {},
   "source": [
    "<img src=\"images/LEGOskib2.JPG\" alt=\"LEGO-skib\" width=\"400\">"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3a70a6b1",
   "metadata": {},
   "source": [
    "## Modellering af LEGO-enheder\n",
    "\n",
    "Vi kan definere en LEGO-enhed som et lille rektangel, der indeholder to cylindre, set fra oven.\n",
    "\n",
    "- Det er nemt at se, at der er $11$ enheder lodret.  \n",
    "- Man kan også tælle, at der er $15$ enheder vandret.\n",
    "\n",
    "Nu skal vi vælge farver til 2D-rekonstruktionen af LEGO-figuren.  Vi kan bruge et udvalg af farver fra **CSS Colors**: [Matplotlib CSS Colors](https://matplotlib.org/stable/gallery/color/named_colors.html)  \n",
    "\n",
    "Når hver LEGO-enhed og baggrunden får tildelt et tal svarende til dens farve, opstår der en $15\\times11$ matrix $A$, som repræsenterer LEGO-figuren i 2D.\n",
    "   \n",
    "Vi antager, at du får brug for en farve til baggrund og ca. syv forskellige farver til LEGO-figuren.\n",
    "\n",
    "Gå frem således:\n",
    "> - I den følgende celle skal du i listen `base_colors` erstatte `white` med de farver du har valgt. Derved tildeles farverne et tal fra $1$ til $8$, svarende til deres plads i listen. Den første farve du vælger, svarer til tallet $1$, det vil sige baggrundsfarven.\n",
    "> - Derefter skal du i matricen $A$ erstatte hver `x` med det tal, som du har valgt til LEGO-enheden, så enheden får den farve, som du ønsker.   \n",
    "> - Når du er færdig, skal du køre cellen og de følgende celler for at se din LEGO-figur i 2D."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "05449e2e",
   "metadata": {},
   "outputs": [],
   "source": [
    "base_colors = ['white','white','white','white','white','white','white','white']\n",
    "\n",
    "A = np.array([\n",
    " [1,1,1,1,1,1,x,x,1,1,1,1,1,1,1],\n",
    " [1,1,1,1,1,x,x,x,1,1,1,1,1,1,1],\n",
    " [1,1,1,1,x,x,x,x,1,1,1,1,1,1,1],\n",
    " [1,1,1,x,x,x,x,x,1,1,1,1,1,1,1],\n",
    " [1,1,x,x,x,x,x,x,1,1,1,1,x,1,1],\n",
    " [1,x,x,x,x,x,x,x,1,1,x,x,x,x,1],\n",
    " [x,x,x,x,x,x,x,x,1,1,1,x,x,x,1],\n",
    " [1,1,1,1,1,1,x,x,1,1,1,x,x,x,1],\n",
    " [1,1,1,x,x,x,x,x,x,x,x,x,x,x,x],\n",
    " [1,1,1,1,x,x,x,x,x,x,x,x,x,x,1],\n",
    " [1,1,1,1,1,x,x,x,x,x,x,x,x,1,1]\n",
    "], dtype=int)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "63ce83dd",
   "metadata": {},
   "source": [
    "```{hint}\n",
    ":class: dropdown\n",
    "Forslag til farvelægning.\n",
    "```python\n",
    "# Knyt en farve til tallene + fejl-farver i hver ende\n",
    "base_colors = ['palegreen','white','red','forestgreen','blue','yellow','black','cyan']\n",
    "\n",
    "# Hver Lego-enhed tildeles et tal\n",
    "A = np.array([\n",
    " [1,1,1,1,1,1,3,3,1,1,1,1,1,1,1],\n",
    " [1,1,1,1,1,4,4,4,1,1,1,1,1,1,1],\n",
    " [1,1,1,1,3,3,3,3,1,1,1,1,1,1,1],\n",
    " [1,1,1,4,4,4,4,4,1,1,1,1,1,1,1],\n",
    " [1,1,3,3,3,3,3,3,1,1,1,1,2,1,1],\n",
    " [1,4,4,4,4,4,4,4,1,1,7,7,7,7,1],\n",
    " [3,3,3,3,3,3,3,3,1,1,1,8,8,6,1],\n",
    " [1,1,1,1,1,1,5,5,1,1,1,6,6,6,1],\n",
    " [1,1,1,2,2,2,2,2,2,2,2,2,2,2,2],\n",
    " [1,1,1,1,2,2,2,2,2,2,2,2,2,2,1],\n",
    " [1,1,1,1,1,2,2,2,2,2,2,2,2,1,1]\n",
    "], dtype=int)\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0b19a570",
   "metadata": {},
   "outputs": [],
   "source": [
    "# KØR DENNE CELLE FOR AT SE DIN LEGO-FIGUR\n",
    "\n",
    "extended_colors = ['magenta'] + base_colors + ['magenta']\n",
    "cmap = ListedColormap(extended_colors)\n",
    "\n",
    "# Definér grænser så værdier <0.5 og >8.5 bliver vist som magenta\n",
    "bounds = np.arange(-0.5, len(extended_colors) - 0.5 + 1, 1)\n",
    "norm = BoundaryNorm(bounds, cmap.N)\n",
    "\n",
    "# 2) Figur og akse\n",
    "fig, ax = plt.subplots(figsize=(6,5))\n",
    "\n",
    "# 3) Tegn billedet – først billedet, så grid (så grid’en ligger øverst)\n",
    "im = ax.imshow(A, cmap=cmap, norm=norm,\n",
    "               extent=[-0.5, A.shape[1]-0.5, -0.5, A.shape[0]-0.5])\n",
    "\n",
    "# 4) Lav grid med meshgrid + plot\n",
    "#    x går fra -0.5 til 14.5 (15 kolonner), y fra -0.5 til 10.5 (11 rækker)\n",
    "x = np.arange(-0.5, A.shape[1] + 0.5, 1)\n",
    "y = np.arange(-0.5, A.shape[0] + 0.5, 1)\n",
    "X, Y = np.meshgrid(x, y)\n",
    "\n",
    "ax.plot(X, Y,       color='lightgrey', linewidth=0.7)  # vertikale\n",
    "ax.plot(X.T, Y.T,   color='lightgrey', linewidth=0.7)  # horisontale\n",
    "\n",
    "# 5) Fjern ticks, sæt ensartet aspect, og giv en sort ramme\n",
    "ax.set_xticks([]); ax.set_yticks([])\n",
    "ax.set_aspect('equal')\n",
    "\n",
    "# 6) Titel og show\n",
    "ax.set_title(\"Originalt billede af LEGO skib\")\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6f8e03e2",
   "metadata": {},
   "source": [
    "## Rang-$k$ matrix approksimation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0e0897a4",
   "metadata": {},
   "source": [
    "For at komprimere vores LEGO-billede kan vi bruge SVD på matricen $A$:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "478e9a38",
   "metadata": {},
   "outputs": [],
   "source": [
    "U, s, Vt = np.linalg.svd(A.astype(float))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2d2014e8",
   "metadata": {},
   "source": [
    "Ved at plotte singulærværdierne får vi et klart indtryk af, hvor meget hvert enkelt led i summen i $(*)$ bidrager til den samlede matrix."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d1bf1f76",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Plot singulærværdier\n",
    "plt.figure(figsize=(8,5))\n",
    "plt.scatter(np.arange(1, len(s)+1), s, color='blue')\n",
    "plt.title(\"Singulærværdiers størrelse for A\")\n",
    "plt.xlabel(\"Index\")\n",
    "plt.ylabel(\"Singulærværdi størrelse\")\n",
    "plt.grid(True, linestyle='--', alpha=0.5)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "27c71bed",
   "metadata": {},
   "source": [
    "De største singulærværdier repræsenterer de mest betydningsfulde mønstre i LEGO-billedet, mens de små bidrager med mindre detaljer. Plottet hjælper os derfor med at beslutte, hvor mange singulærværdier vi kan beholde, uden at miste for meget information, hvilket er centralt, når vi laver en $k$-rang-approksimation af billedet."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5f5c3afb",
   "metadata": {},
   "source": [
    " Vi ønsker at visualisere det originale LEGO-billede og rang-$k$ SVD-approksimationen side om side for at se effekten af approksimationen.\n",
    "\n",
    "> Færdiggør funktionen `plot_svd(k)` ved at indsætte funktionen `svd_approksimation()` fra tidligere."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "39bbbdcf",
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_svd(k):\n",
    "    # Beregn k-rangs approksimation af A (kaldes A_recon)\n",
    "    A_recon = 'INDSÆT KODE HER'\n",
    "\n",
    "    fig, axes = plt.subplots(1, 2, figsize=(12, 5), dpi=100)\n",
    "\n",
    "    # Lav koordinatarrays til pcolormesh - definerer kanterne på hver pixel\n",
    "    x_edges = np.arange(-0.5, A.shape[1] + 0.5, 1)\n",
    "    y_edges = np.arange(-0.5, A.shape[0] + 0.5, 1)\n",
    "    \n",
    "    # Lav meshgrid for kanterne\n",
    "    X, Y = np.meshgrid(x_edges, y_edges)\n",
    "\n",
    "    # Approksimation\n",
    "    ax = axes[0]\n",
    "    # Brug pcolormesh som naturligt aligner med griddet\n",
    "    mesh = ax.pcolormesh(X, Y, A_recon, cmap=cmap, norm=norm, shading='flat')\n",
    "    \n",
    "    # Tilføj gridlinjer præcist på kanterne\n",
    "    ax.vlines(x_edges, y_edges[0], y_edges[-1], colors='lightgrey', linewidth=0.7)\n",
    "    ax.hlines(y_edges, x_edges[0], x_edges[-1], colors='lightgrey', linewidth=0.7)\n",
    "    \n",
    "    ax.set_xlim(x_edges[0], x_edges[-1])\n",
    "    ax.set_ylim(y_edges[0], y_edges[-1])\n",
    "    ax.invert_yaxis()  # Retter op-ned vendt billede\n",
    "    ax.set_aspect('equal')\n",
    "    ax.set_xticks([])\n",
    "    ax.set_yticks([])\n",
    "    ax.set_title(f\"Approksimation med k={k} singulærværdier\")\n",
    "\n",
    "    # Originalt\n",
    "    ax = axes[1]\n",
    "    mesh = ax.pcolormesh(X, Y, A, cmap=cmap, norm=norm, shading='flat')\n",
    "    \n",
    "    ax.vlines(x_edges, y_edges[0], y_edges[-1], colors='lightgrey', linewidth=0.7)\n",
    "    ax.hlines(y_edges, x_edges[0], x_edges[-1], colors='lightgrey', linewidth=0.7)\n",
    "    \n",
    "    ax.set_xlim(x_edges[0], x_edges[-1])\n",
    "    ax.set_ylim(y_edges[0], y_edges[-1])\n",
    "    ax.invert_yaxis()  # Retter op-ned vendt billede\n",
    "    ax.set_aspect('equal')\n",
    "    ax.set_xticks([])\n",
    "    ax.set_yticks([])\n",
    "    ax.set_title(\"Originalt billede\")\n",
    "\n",
    "    plt.tight_layout()\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5fec1040",
   "metadata": {},
   "source": [
    "> Prøv forskellige værdier af $k$ med funktionen `plot_svd(k)` og se, hvordan billedet rekonstrueres. Hvilken effekt har det på billedets kvalitet?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "df4727bf",
   "metadata": {},
   "outputs": [],
   "source": [
    "# INDSÆT KODE HER"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2cee775e",
   "metadata": {},
   "source": [
    "> Prøv at rekonstruere matrixen ved at medtage alle singulærværdier. Hvorfor ser det præcis ud som det originale LEGO-billede?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e7e3817",
   "metadata": {},
   "source": [
    "Nedenfor undersøger vi, hvordan den bevarede information vokser med $k$ ved at bruge funktionen `plot_bevarede_information()` fra tidligere:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f42f565d",
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_bevarede_information(A)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0abc247b",
   "metadata": {},
   "source": [
    "> Hvor mange singulærværdier skal medtages for at opnå kumulativ forklaring på over $90 \\%$? \\\n",
    "Hvor stort bliver lagringstallet $L(k,m,n)$ for den valgte $k$?\n",
    "\n",
    "> Hvordan hænger den bevarede information sammen med kvaliteten af billedet, når du ændrer antallet af medtagne singulærværdier?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c38c9025",
   "metadata": {},
   "source": [
    "# Medbragt Billede"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a084dec3",
   "metadata": {},
   "source": [
    "## Pixelværdier i billeder"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "84a19774",
   "metadata": {},
   "source": [
    "Et billede består af tal - hver pixel-værdi fortæller, hvor lys eller mørk den pixel er. I gråtonebilleder bruger man typisk heltal i området $0–255$:\n",
    "- $0$ = sort.\n",
    "- $255$ = hvid.\n",
    "- Værdier imellem = gråtoner.\n",
    "\n",
    "Til beregninger normaliserer man ofte til intervallet $[0, 1]$ ved at dividere med $255$. For farvebilleder gemmes tre sådanne værdier per pixel (R, G, B), svarende til én rød, én grøn og én blå komponent."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c670633a",
   "metadata": {},
   "source": [
    "## Import af billede og databehandling"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7bd0ddc0",
   "metadata": {},
   "source": [
    "I denne opgave kræver det, at du kan bruge dit eget foto. Derfor skal du på arbejde JupyterLite-siden https://intermat20.compute.dtu.dk/jupyterlite/index.html, hvor du kan uploade dit eget foto ved blot at \"drag-and-droppe\"."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7b8f0839",
   "metadata": {},
   "source": [
    "> Når du har uploadet dit billede til JupyterLite-siden, skal du ændre `INDSÆT FILNAVN HER` til filnavnet på dit billede. \\\n",
    "> Vi skal i opgaven arbejde med gråtone billeder, og derfor ændres dit billede til grayscale."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1c1c9ce6",
   "metadata": {},
   "outputs": [],
   "source": [
    "def load_image_matrix(path, max_dim=1000, to_gray=True):\n",
    "    \"\"\"\n",
    "    Indlæs et billede som et 2D (gråtoner) eller 3D (RGB) NumPy array i [0,1],\n",
    "    nedskaleret så det største mål højst er max_dim, med bevaret aspektforhold.\n",
    "    \"\"\"\n",
    "    im = Image.open(path)  # Åbn billedfil (JPG/PNG mv.)\n",
    "    if to_gray:\n",
    "        im = im.convert(\"L\")   # Konverter til gråtoner\n",
    "    else:\n",
    "        im = im.convert(\"RGB\") # Sørg for 3 farvekanaler (dropper evt. alfa-kanal)\n",
    "\n",
    "    w, h = im.size\n",
    "    scale = min(1.0, max_dim / max(w, h))  # Beregn skaleringsfaktor\n",
    "    if scale < 1.0:\n",
    "        # Skaler billedet ned hvis det er større end max_dim\n",
    "        new_size = (max(1, int(round(w * scale))), max(1, int(round(h * scale))))\n",
    "        im = im.resize(new_size, Image.LANCZOS)\n",
    "\n",
    "    # Konverter til NumPy-array med værdier mellem 0 og 1\n",
    "    arr = np.asarray(im, dtype=np.float64)\n",
    "    arr = arr / 255.0\n",
    "    return arr\n",
    "\n",
    "# Indlæs billedet\n",
    "A = load_image_matrix(\"INDSÆT FILNAVN HER\", max_dim=512, to_gray=True)  # INDSÆT KODE HER\n",
    "\n",
    "plt.imshow(A)\n",
    "plt.axis(\"off\")  # Skjul akser\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c40376b",
   "metadata": {},
   "source": [
    "Cellen nedenfor printer en dataoversigt for billedet."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a7c0a581",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Dataoversigt for billedet\n",
    "print('Objekt type:\\n',type(A))\n",
    "print('Dimensioner:\\n',A.shape)\n",
    "print('Datatype af elementer:\\n',A.dtype)\n",
    "print('Datainterval:\\n',[np.min(A), np.max(A)])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39741ce5",
   "metadata": {},
   "source": [
    "> Kør cellen ovenfor og overvej dimensionerne. Giver de mening?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c3ab843",
   "metadata": {},
   "source": [
    "## SVD på billede"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19583a9a",
   "metadata": {},
   "source": [
    "Med den korrekte matrixrepræsentation af billedet, kan vi nu undersøge matricens rang."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4dcf53b9",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Rangen af matrix:\\n\", np.linalg.matrix_rank(A))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "96d2bcc8",
   "metadata": {},
   "source": [
    "> Hvor mange ikke-nul singulær værdier kan vi forvente?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e56e455d",
   "metadata": {},
   "source": [
    "Vi udfører SVD på billedet:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e0a0d1fe",
   "metadata": {},
   "outputs": [],
   "source": [
    "U, s, Vt = np.linalg.svd(A)\n",
    "Sigma = np.diag(s) # diagonalmatrix med s som diagonalindgange"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a6c7a92e",
   "metadata": {},
   "source": [
    "> Vis størrelsen af singulærværdierne i et plot."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "acd86c46",
   "metadata": {},
   "outputs": [],
   "source": [
    "# INDSÆT KODE HER"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "71fbc3ff",
   "metadata": {},
   "source": [
    "> Hvad betyder det, at den første singulærværdi ofte er meget større end de næste?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad009243",
   "metadata": {},
   "source": [
    "Ligesom i øvelsen med LEGO-skibet vil vi nu undersøge singulærværdierne ud fra den bevarede information. Se plottet nedenfor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "35f8d262",
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_bevarede_information(A)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2d7c0db3",
   "metadata": {},
   "source": [
    "> Aflæs på plottet hvad antallet af singulærværdier skal være for at opnå henholdsvis $90 \\%$, $95 \\%$ og $99 \\%$ af den bevarede information."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3f4c426e",
   "metadata": {},
   "source": [
    "## Konstruktion af approksimerede billeder"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2679cafc",
   "metadata": {},
   "source": [
    "Vi ønsker at konstruere approksimeringer af det originale sort-hvid billede, ved kun at bruge de første $k$ singulærværdier."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60b19da4",
   "metadata": {},
   "source": [
    "Vi vil undersøge kvaliteten af approksimationerne, når der anvendes et forskelligt antal singulærværdier. \n",
    "\n",
    "> Benyt funktionen `svd_approksimation()` til at færdiggøre funktionen `plot_approksimation()` nedenfor. \\\n",
    "> Funktionen plotter $k$-rangs-approksimationen og det originale billede side om side."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9ddf663c",
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_approksimation(A,k):\n",
    "    # Rang-k rekonstruktion via ydre produkter\n",
    "    A_recon = 'INDSÆT KODE HER'\n",
    "\n",
    "    # Figur med to plots\n",
    "    fig, axes = plt.subplots(1, 2, figsize=(10,5))\n",
    "\n",
    "    # Approksimation\n",
    "    ax = axes[0]\n",
    "    ax.imshow(A_recon, cmap='gray', interpolation='nearest')\n",
    "    ax.set_title(f'Rank-{k} approksimation)')\n",
    "    ax.set_xticks([]); ax.set_yticks([])\n",
    "\n",
    "    # Original\n",
    "    ax = axes[1]\n",
    "    ax.imshow(A, cmap='gray', interpolation='nearest')\n",
    "    ax.set_title('Original')\n",
    "    ax.set_xticks([]); ax.set_yticks([])\n",
    "\n",
    "    plt.tight_layout()\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d003b434",
   "metadata": {},
   "source": [
    "Eksperimentér med antallet af singulærværdier og brug funktionen `plot_approksimation(A,k)` for at svare på følgende:\n",
    "\n",
    "> Hvor mange singulærværdier skal bruges, før motivet kan genkendes?\n",
    "\n",
    "> Hvor mange skal bruges, før man kan se detaljer?\n",
    "\n",
    "> Hvor mange skal bruges, før approksimationen er lige så god som det originale billede med det blotte øje.\n",
    "\n",
    "> Hvad svarer det til i bevarede information $BI$ og lagringstal $LA(k,m,n)$?\n",
    "\n",
    "> Kan man genkende elementer i billedet i approksimationen, hvis kun den første singulær værdi bruges? Fx. himmel/land, baggrund/forgrund, og så videre."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fbbf5968",
   "metadata": {},
   "source": [
    "# Udfordrende: Farveapproksimation med SVD"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0cb04e32",
   "metadata": {},
   "source": [
    "I de tidligere øvelser har vi kun arbejdet med gråtoner eller 2D LEGO-matricer. Rigtige billeder består typisk af tre farvekanaler: Rød (R), Grøn (G) og Blå (B). Hver kanal kan behandles som en matrix, hvor vi kan udføre SVD og lave en $k$-rangs-approksimation. \n",
    "\n",
    "Ved at gøre dette for alle tre kanaler individuelt og sætte dem sammen igen, kan vi lave en komprimeret version af farvebilledet. Dette viser tydeligt, hvordan SVD kan bruges til billedkompression i farver. Vores procedurer `svd_approksimation()`, `bevarede_information()` og `plot_bevarede_information()` skal derfor udvides for at kunne håndtere 3 kanaler.\n",
    "\n",
    "> Opgaven er derfor:\n",
    "> 1. Opdel dit medbragte billede i R, G og B kanaler.  \n",
    "> 2. Udfør SVD på hver kanal.  \n",
    "> 3. Lav rang-$k$-approksimation for hver kanal.  \n",
    "> 4. Sæt kanalerne sammen igen for at genskabe billedet.  \n",
    "> 5. Visualiser resultatet."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c3e9bc9c",
   "metadata": {},
   "outputs": [],
   "source": [
    "def svd_rgb_approksimation(img, k, verbose=False):\n",
    "    \"\"\"\n",
    "    Returnerer en k-rangs SVD-approksimation af et RGB-billede.\n",
    "    \n",
    "    Parametre:\n",
    "    - img: et RGB-billede som numpy array (højde x bredde x 3)\n",
    "    - k: antal singulærværdier at medtage pr. kanal\n",
    "    \n",
    "    Returnerer:\n",
    "    - img_recon: det approksimerede billede\n",
    "    \"\"\"\n",
    "    img = np.asarray(img)\n",
    "\n",
    "    # Konverter billedet til float med samme størrelse\n",
    "    imgf = img.astype(float)\n",
    "    \n",
    "    # Lav en kopi af billedet, som vi kan fylde med approksimationen - brug np.zeros_like\n",
    "    img_recon = 'INDSÆT KODE HER'\n",
    "    \n",
    "    # Gå gennem de tre farvekanaler (R, G, B)\n",
    "    for i in range(3):\n",
    "        # Beregn k-rangs approksimation for denne kanal - brug svd_approksimation(channel, k)\n",
    "        channel = imgf[:, :, i]\n",
    "        Xk = 'INDSÆT KODE HER'\n",
    "        img_recon[:, :, i] = Xk\n",
    "\n",
    "    # Sørg for, at pixelværdierne ligger inden for 0–1 - brug np.clip\n",
    "    img_recon = 'INDSÆT KODE HER'\n",
    "\n",
    "    return img_recon\n",
    "\n",
    "\n",
    "A = load_image_matrix(\"INDSÆT KODE HER\", max_dim=512, to_gray=False) \n",
    "img_recon = svd_rgb_approksimation(A, k=100, verbose=True)\n",
    "\n",
    "# Visualiser resultatet\n",
    "plt.figure(figsize=(6,5))\n",
    "plt.imshow(img_recon)\n",
    "plt.title(\"RGB-rang-k approksimation\")\n",
    "plt.axis('off')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "48fe0d94",
   "metadata": {},
   "source": [
    "Vi ønsker at undersøge, hvordan den bevarede information udvikler sig for hver af de tre farvekanaler i et RGB-billede. Dette giver indsigt i, hvilke kanaler der bærer mest information, og hvor meget $k$-rangs approksimation kan komprimere billedet uden stort tab af kvalitet. \n",
    "\n",
    "> Udfør for hver kanal (Rød, Grøn, Blå) SVD.   \n",
    "> Beregn den tilhørende bevarede information for hver kanal. \\\n",
    "> Plot resultaterne sammen og vurdér, hvor mange singulærværdier, der skal bruges, for at opnå $90 \\%$ af den bevarede information i hver kanal."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b3b3278a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Plot kumulativ varians forklaret for RGB-billedet med kanal-specifik farve\n",
    "kanaler = ['Rød', 'Grøn', 'Blå']\n",
    "farver = ['red', 'green', 'blue']\n",
    "plt.figure(figsize=(8,5))\n",
    "\n",
    "for i, (kanal, farve) in enumerate(zip(kanaler, farver)):\n",
    "    # Udfør SVD på hver kanal\n",
    "    'INDSÆT KODE HER'\n",
    "    \n",
    "    # Beregn tilhørende kumulative information\n",
    "    'INDSÆT KODE HER'\n",
    "    \n",
    "    # Plot kumulativ information\n",
    "    'INDSÆT KODE HER'\n",
    "\n",
    "\n",
    "plt.xlabel('Antal singulærværdier medtaget')\n",
    "plt.ylabel('Bevarede information')\n",
    "plt.title('Bevarede information pr. farvekanal')\n",
    "plt.axhline(0.9, color='black', linestyle=':', label='90% linje')\n",
    "plt.legend()\n",
    "plt.grid(True, linestyle='--', alpha=0.5)\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "formats": "ipynb,md:myst",
   "text_representation": {
    "extension": ".md",
    "format_name": "myst",
    "format_version": 0.13,
    "jupytext_version": "1.16.0"
   }
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "source_map": [
   13,
   19,
   33,
   60,
   64,
   68,
   74,
   86,
   92,
   96,
   102,
   106,
   110,
   116,
   120,
   129,
   133,
   138,
   142,
   154,
   159,
   169,
   173,
   210,
   216,
   218,
   223,
   240,
   244,
   247,
   251,
   253,
   257,
   261,
   266,
   270,
   280,
   317,
   321,
   335,
   341,
   343,
   347,
   361,
   365,
   377,
   381,
   389,
   399,
   403,
   405,
   409,
   416,
   420,
   422,
   426,
   444,
   448,
   450,
   454,
   463,
   467,
   471,
   491,
   507,
   535,
   569,
   573,
   577,
   579,
   583,
   592,
   596,
   602,
   650,
   654,
   656,
   660,
   664,
   666,
   673,
   677,
   681,
   690,
   694,
   698,
   703,
   733,
   737,
   743,
   747,
   751,
   755,
   757,
   761,
   765,
   768,
   772,
   774,
   778,
   782,
   784,
   788,
   792,
   796,
   803,
   825,
   839,
   843,
   856,
   898,
   906
  ]
 },
 "nbformat": 4,
 "nbformat_minor": 5
}