{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Affine transformation\n",
    "**Students:** Sam van Kampen, Robin Wacanno\n",
    "\n",
    "**Student numbers:** 11874716, 11741163"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The affine transformation matrix $A$ is of the form\n",
    "\n",
    "$$\n",
    "A = \\begin{pmatrix}a & b & c\\\\d & e & f\\\\0 & 0 & 1\\end{pmatrix}\n",
    "$$\n",
    "\n",
    "and maps input points $x_i, y_i$ to output points $x_{i}', y_{i}'$ with\n",
    "\n",
    "$$\n",
    "\\begin{pmatrix}x_i'\\\\y_i'\\\\1\\end{pmatrix} =\n",
    "\\begin{pmatrix}a & b & c\\\\d & e & f\\\\0 & 0 & 1\\end{pmatrix}\n",
    "\\begin{pmatrix}x_i\\\\y_i\\\\1\\end{pmatrix}\n",
    "$$\n",
    "\n",
    "working this out and rewriting so that the unknown vector is on the right hand side, which makes the system easily solvable as a system of linear equations (given enough points):\n",
    "\n",
    "$$\n",
    "\\begin{split}\\begin{pmatrix}x_i'\\\\ y_i'\\end{pmatrix} =\n",
    "\\begin{pmatrix}x_i& y_i& 1& 0& 0& 0\\\\0& 0& 0& x_i& y_i& 1 \\end{pmatrix}\n",
    "\\begin{pmatrix}a\\\\b\\\\c\\\\d\\\\e\\\\f \\end{pmatrix}\\end{split}\n",
    "$$\n",
    "\n",
    "This system does not have enough point correspondences to be invertible, so we need to go from one point to three (non-collinear) points, yielding:\n",
    "\n",
    "\\begin{split}\\begin{pmatrix}x_1'\\\\ y_1'\\\\x_2'\\\\ y_2'\\\\x_3'\\\\ y_3'\\end{pmatrix} =\n",
    "\\begin{pmatrix}\n",
    "x_1& y_1& 1& 0& 0& 0\\\\0& 0& 0& x_1& y_1& 1\\\\\n",
    "x_2& y_2& 1& 0& 0& 0\\\\0& 0& 0& x_2& y_2& 1\\\\\n",
    "x_3& y_3& 1& 0& 0& 0\\\\0& 0& 0& x_3& y_3& 1 \\end{pmatrix}\n",
    "\\begin{pmatrix}a\\\\b\\\\c\\\\d\\\\e\\\\f \\end{pmatrix}\\end{split}\n",
    "\n",
    "This system can be solved easily using numpy etc."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Populating the interactive namespace from numpy and matplotlib\n"
     ]
    }
   ],
   "source": [
    "%pylab inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "def affineTransform(f, x1, y1, x2, y2, x3, y3, width, height, crop=True):\n",
    "    M = np.array([[x, y, 1, *[0]*6, x, y, 1] for x, y in ((x1, y1), (x2, y2), (x3, y3))]).reshape(6, 6)\n",
    "    new_points = np.array([0, 0, width - 1, 0, 0, height - 1])\n",
    "    \n",
    "    M_inverse = np.linalg.inv(M)\n",
    "    \n",
    "    affine_transform_parameters = M_inverse @ new_points\n",
    "    \n",
    "    transform_matrix = np.array([*affine_transform_parameters, 0, 0, 1]).reshape(3, 3)\n",
    "    \n",
    "    # skimage.transform.warp does a backward transform, so we need the inverse transformation matrix.\n",
    "    inv_transform_matrix = np.linalg.inv(transform_matrix)\n",
    "    \n",
    "    # Warp, then crop to (height, width)\n",
    "    warped_img = skimage.transform.warp(f, inv_transform_matrix)\n",
    "    \n",
    "    return warped_img[:height,:width] if crop else warped_img   "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To test the transform code, we load some test images and warp them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x7f66aa9184e0>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 7664 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import skimage\n",
    "\n",
    "f = skimage.io.imread('data/sheared.png')\n",
    "# Corner points corresponding to new corners:\n",
    "f_points = [6, 87, 490, 87, 150, 401]\n",
    "\n",
    "imshow(f)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x7f66aa8b64a8>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 6188 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "imshow(affineTransform(f, *f_points, 486, 314))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Just to show I'm not cheating and cropping to a rectangular area containing all blue instead of properly transforming:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x7f66aa819898>"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 7500 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "imshow(affineTransform(f, *f_points, 486, 314, crop=False))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's also do a rotation around the center, for good measure. Instead of using my own image I'll just rotate Lena."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x7f66aa7f37b8>"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 155924 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# They removed Lena from the default test images!\n",
    "# (I understand the reasons, but I just think it's a nice tradition to use her image)\n",
    "f2 = skimage.io.imread('data/lena.png')\n",
    "\n",
    "imshow(f2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's rotate Lena 45 degrees, clockwise. To do this, the measurements we give have to be the inverse."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "f2_rotated = affineTransform(f2, -106, 256, 256, -106, 256, 618, 512, 512)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x7f66a8f5d0b8>"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 128624 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "imshow(f2_rotated)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It seems to work fine."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "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.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
