{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "f94476eb",
   "metadata": {},
   "source": [
    "## Question 1 (14 marks)\n",
    "\n",
    "Write two functions compute_gradient_magnitude(gr_im, kx, ky) and\n",
    "compute_gradient_direction(gr_im, kx, ky) to compute the magnitude and direction of\n",
    "gradient of the grey image gr_im with the horizontal kernel kx and vertical kernel ky."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "edf40439",
   "metadata": {},
   "outputs": [],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "cbfcddf7",
   "metadata": {},
   "outputs": [],
   "source": [
    "image_path = \"data/shapes.png\"\n",
    "gr_im = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "da7b5144",
   "metadata": {},
   "source": [
    "This is the default kernel for Sobel Filter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "7ba9e609",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Sobel Filter\n",
    "kx_conv = np.array([\n",
    "         [1, 0, -1], \n",
    "         [2, 0, -2], \n",
    "         [1, 0, -1]\n",
    "])\n",
    "\n",
    "ky_conv = np.array([\n",
    "         [1, 2, 1], \n",
    "         [0, 0, 0],\n",
    "         [-1, -2, -1]\n",
    "])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8f3279b4",
   "metadata": {},
   "source": [
    "**However, some students used the cross-correlation kernel, and thus produced different results.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "1316ddae",
   "metadata": {},
   "outputs": [],
   "source": [
    "kx_cross = np.array([\n",
    "    [-1, 0, 1], \n",
    "    [-2, 0, 2], \n",
    "    [-1, 0, 1]])\n",
    "\n",
    "ky_cross = np.array([\n",
    "    [-1, -2, -1], \n",
    "    [0, 0, 0],\n",
    "    [1, 2, 1]\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "6ccd97f3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# kx_cross = np.flip(np.flip(kx_conv, 0), 1)\n",
    "# ky_cross = np.flip(np.flip(ky_conv, 0), 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "cf6af87b",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x1f7c0c56340>"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 38304 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.imshow(gr_im)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "535502fe",
   "metadata": {},
   "source": [
    "## Method 1: cv2.Sobel (Convolution)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "36c99d87",
   "metadata": {},
   "outputs": [],
   "source": [
    "def m1_compute_gradient_magnitude(gr_im, kx, ky):\n",
    "\n",
    "    Gx = cv2.Sobel(gr_im, cv2.CV_64F, 1, 0, ksize=kx.shape[0])\n",
    "    Gy = cv2.Sobel(gr_im, cv2.CV_64F, 0, 1, ksize=ky.shape[0])\n",
    "    \n",
    "    # Compute the magnitude of gradients\n",
    "    magnitude = np.sqrt(Gx**2 + Gy**2)\n",
    "    \n",
    "    return magnitude\n",
    "\n",
    "def m1_compute_gradient_direction(gr_im, kx, ky):\n",
    "\n",
    "    Gx = cv2.Sobel(gr_im, cv2.CV_64F, 1, 0, ksize=kx.shape[0])\n",
    "    Gy = cv2.Sobel(gr_im, cv2.CV_64F, 0, 1, ksize=ky.shape[0])\n",
    "    \n",
    "    # Compute the direction of gradients\n",
    "    direction = np.arctan2(Gy, Gx)\n",
    "\n",
    "    return direction"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a351fc50",
   "metadata": {},
   "source": [
    "**Here, we use k_conv**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "9523e11f",
   "metadata": {},
   "outputs": [],
   "source": [
    "m1_magnitude = m1_compute_gradient_magnitude(gr_im, kx_conv, ky_conv)\n",
    "m1_direction = m1_compute_gradient_direction(gr_im, kx_conv, ky_conv)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "26e30167",
   "metadata": {},
   "source": [
    "## Method 2: cv2.filter2D (Cross-Correlation)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "3224665e",
   "metadata": {},
   "outputs": [],
   "source": [
    "def m2_compute_gradient_magnitude(gr_im, kx, ky):\n",
    "\n",
    "    Gx = cv2.filter2D(gr_im, cv2.CV_64F, kx)\n",
    "    Gy = cv2.filter2D(gr_im, cv2.CV_64F, ky)\n",
    "\n",
    "    # Compute the magnitude of gradients\n",
    "    magnitude = np.sqrt(Gx**2 + Gy**2)\n",
    "    \n",
    "    return magnitude\n",
    "\n",
    "\n",
    "def m2_compute_gradient_direction(gr_im, kx, ky):\n",
    "    Gx = cv2.filter2D(gr_im, cv2.CV_64F, kx)\n",
    "    Gy = cv2.filter2D(gr_im, cv2.CV_64F, ky)\n",
    "\n",
    "    # Compute the direction of gradients\n",
    "    direction = np.arctan2(Gy, Gx)\n",
    "\n",
    "    return direction"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ab719729",
   "metadata": {},
   "source": [
    "**Here, we use k_cross**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "561a90aa",
   "metadata": {},
   "outputs": [],
   "source": [
    "m2_magnitude = m2_compute_gradient_magnitude(gr_im, kx_cross, ky_cross)\n",
    "m2_direction = m2_compute_gradient_direction(gr_im, kx_cross, ky_cross)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a84d8da8",
   "metadata": {},
   "source": [
    "## Method 3: scipy.signal.convolve2d (Convolution)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "e5ef9996",
   "metadata": {},
   "outputs": [],
   "source": [
    "from scipy.signal import convolve2d"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "0351c477",
   "metadata": {},
   "outputs": [],
   "source": [
    "def m3_compute_gradient_magnitude(gr_im, kx, ky):\n",
    "\n",
    "    Gx = convolve2d(gr_im, kx, mode='same')\n",
    "    Gy = convolve2d(gr_im, ky, mode='same')\n",
    "    \n",
    "    # Compute the magnitude of gradients\n",
    "    magnitude = np.sqrt(Gx**2 + Gy**2).astype(np.float64)\n",
    "    \n",
    "    return magnitude\n",
    "\n",
    "def m3_compute_gradient_direction(gr_im, kx, ky):\n",
    "\n",
    "    Gx = convolve2d(gr_im, kx, mode='same')\n",
    "    Gy = convolve2d(gr_im, ky, mode='same')\n",
    "    \n",
    "    # Compute the direction of gradients\n",
    "    direction = np.arctan2(Gy, Gx).astype(np.float64)\n",
    "    \n",
    "    return direction"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2393ad09",
   "metadata": {},
   "source": [
    "**Here, we use k_conv**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "93365cc4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Compute the gradient magnitude and direction\n",
    "m3_magnitude = m3_compute_gradient_magnitude(gr_im, kx_conv, ky_conv)\n",
    "m3_direction = m3_compute_gradient_direction(gr_im, kx_conv, ky_conv)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17c87bab",
   "metadata": {},
   "source": [
    "## Method 4: scipy.ndimage.convolve (Convolution)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec74ef97",
   "metadata": {},
   "source": [
    "This is what ChatGPT returns. But some students forget to convert the data type to float, causing errors."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "d8f7c62a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from scipy import ndimage"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "88e3a971",
   "metadata": {},
   "outputs": [],
   "source": [
    "def m4_compute_gradient_magnitude(gr_im, kx, ky):\n",
    "\n",
    "    Gx = ndimage.convolve(gr_im.astype(float), kx)\n",
    "    Gy = ndimage.convolve(gr_im.astype(float), ky)\n",
    "\n",
    "    # Compute the magnitude of gradients\n",
    "    magnitude = np.sqrt(Gx**2 + Gy**2).astype(np.float64)\n",
    "\n",
    "    return magnitude\n",
    "\n",
    "def m4_compute_gradient_direction(gr_im, kx, ky):\n",
    "\n",
    "    Gx = ndimage.convolve(gr_im.astype(float), kx)\n",
    "    Gy = ndimage.convolve(gr_im.astype(float), ky)\n",
    "    \n",
    "    # Compute the direction of gradients\n",
    "    direction = np.arctan2(Gy, Gx).astype(np.float64)\n",
    "    \n",
    "    return direction"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ed9fad3f",
   "metadata": {},
   "source": [
    "**Here, we use k_conv**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "b15ee435",
   "metadata": {},
   "outputs": [],
   "source": [
    "m4_magnitude = m4_compute_gradient_magnitude(gr_im, kx_conv, ky_conv)\n",
    "m4_direction = m4_compute_gradient_direction(gr_im, kx_conv, ky_conv)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3be8af33",
   "metadata": {},
   "source": [
    "## Method 5: cv2.addWeighted / cv2.phase (Wrong Sum)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6a9b84f8",
   "metadata": {},
   "source": [
    "Some students followed the OpenCV documentation: https://docs.opencv.org/3.4/d2/d2c/tutorial_sobel_derivatives.html"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "48ffd1e3",
   "metadata": {},
   "source": [
    "They used $G = |G_x| + |G_y|$, rather than $G = \\sqrt{G_x^2 + G_y^2}$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "a2b9510e",
   "metadata": {},
   "outputs": [],
   "source": [
    "def m5_compute_gradient_magnitude(gr_im, kx, ky):\n",
    "    x,y = gr_im.shape\n",
    "    # calculate the derivatives in x and y directions\n",
    "    grad_x = cv2.filter2D(gr_im, cv2.CV_64F, kx)\n",
    "    grad_y = cv2.filter2D(gr_im, cv2.CV_64F, ky)\n",
    "\n",
    "    gradSum = cv2.addWeighted(grad_x, 0.5, grad_y, 0.5, 0)\n",
    "\n",
    "    return gradSum\n",
    "\n",
    "def m5_compute_gradient_direction(gr_im, kx, ky):\n",
    "    # calculate the derivatives in x and y directions\n",
    "    grad_x = cv2.filter2D(gr_im, cv2.CV_64F, kx)\n",
    "    grad_y = cv2.filter2D(gr_im, cv2.CV_64F, ky)\n",
    "\n",
    "    direction = cv2.phase(grad_x, grad_y, angleInDegrees=False)\n",
    "    return direction"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "9ce074c8",
   "metadata": {},
   "outputs": [],
   "source": [
    "m5_magnitude = m5_compute_gradient_magnitude(gr_im, kx_cross, ky_cross)\n",
    "m5_direction = m5_compute_gradient_magnitude(gr_im, kx_cross, ky_cross)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1cd12219",
   "metadata": {},
   "source": [
    "## Method 6: cv2.filter2D (Wrong, parameter -1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a01fc4bc",
   "metadata": {},
   "source": [
    "Some students added an extra parameter -1 in the wrong place."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67cf59d3",
   "metadata": {},
   "source": [
    "It's either -1 or cv2.CV_64F. Can't use both.\n",
    "```\n",
    "grad_x = cv2.filter2D(gr_im, -1, kx)\n",
    "grad_x = cv2.filter2D(gr_im, cv2.CV_64F, kx)\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "2003be8a",
   "metadata": {},
   "outputs": [],
   "source": [
    "def m6_compute_gradient_magnitude(gr_im, kx, ky):\n",
    "    grad_x = cv2.filter2D(gr_im, -1, cv2.CV_64F, kx)\n",
    "    grad_y = cv2.filter2D(gr_im, -1, cv2.CV_64F, ky)\n",
    "    \n",
    "    return np.sqrt(grad_x**2 + grad_y**2).astype(np.float64)\n",
    "\n",
    "def m6_compute_gradient_direction(gr_im, kx, ky):\n",
    "    grad_x = cv2.filter2D(gr_im, -1, cv2.CV_64F, kx)\n",
    "    grad_y = cv2.filter2D(gr_im, -1, cv2.CV_64F, ky)\n",
    "    \n",
    "    return np.arctan2(grad_y, grad_x).astype(np.float64)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "d647ef2b",
   "metadata": {},
   "outputs": [],
   "source": [
    "m6_magnitude = m6_compute_gradient_magnitude(gr_im, kx_cross, ky_cross)\n",
    "m6_direction = m6_compute_gradient_direction(gr_im, kx_cross, ky_cross)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "710f2e14",
   "metadata": {},
   "source": [
    "## Save outputs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "4e98ca9d",
   "metadata": {},
   "outputs": [],
   "source": [
    "np.save('data/question1_magnitude.npy', m1_magnitude)\n",
    "np.save('data/question1_direction.npy', m1_direction)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "600779b5",
   "metadata": {},
   "source": [
    "## Put students' implementations here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "a953ff6a",
   "metadata": {},
   "outputs": [],
   "source": [
    "def compute_gradient_magnitude(gr_im, kx, ky):\n",
    "    # Ensure the image is a float64 for computation\n",
    "    gr_im_float64 = gr_im.astype(np.float64)\n",
    "\n",
    "    # Compute gradients in x and y direction\n",
    "    grad_x = cv2.filter2D(gr_im_float64, -1, kx.astype(np.float64))\n",
    "    grad_y = cv2.filter2D(gr_im_float64, -1, ky.astype(np.float64))\n",
    "\n",
    "    # Compute gradient magnitude\n",
    "    magnitude = np.sqrt(grad_x**2 + grad_y**2)\n",
    "    \n",
    "    return magnitude\n",
    "\n",
    "def compute_gradient_direction(gr_im, kx, ky):\n",
    "    # Ensure the image is a float64 for computation\n",
    "    gr_im_float64 = gr_im.astype(np.float64)\n",
    "    \n",
    "    # Compute gradients in x and y direction\n",
    "    grad_x = cv2.filter2D(gr_im_float64, -1, kx.astype(np.float64))\n",
    "    grad_y = cv2.filter2D(gr_im_float64, -1, ky.astype(np.float64))\n",
    "\n",
    "    # Compute gradient direction\n",
    "    direction = np.arctan2(grad_y, grad_x)\n",
    "    \n",
    "    return direction"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a02a91e2",
   "metadata": {},
   "source": [
    "Different APIs use different kernels:\n",
    "- cv2.Sobel(gr_im, cv2.CV_64F, 1, 0, ksize=kx.shape[0]) ==> k_conv\n",
    "- cv2.filter2D(gr_im, cv2.CV_64F, kx) ==> k_cross\n",
    "- scipy.signal.convolve2d(gr_im, kx, mode='same') ==> k_conv\n",
    "- ndimage.convolve(gr_im.astype(float), kx) ==> k_conv"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "63663950",
   "metadata": {},
   "outputs": [],
   "source": [
    "# For convolution\n",
    "# magnitude = compute_gradient_magnitude(gr_im, kx_conv, ky_conv)\n",
    "# direction = compute_gradient_direction(gr_im, kx_conv, ky_conv)\n",
    "\n",
    "# For Cross-Correlation\n",
    "magnitude = compute_gradient_magnitude(gr_im, kx_cross, ky_cross)\n",
    "direction = compute_gradient_direction(gr_im, kx_cross, ky_cross)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "88424988",
   "metadata": {},
   "source": [
    "## Test (Should output ALL PASS)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3a0e2419",
   "metadata": {},
   "source": [
    "Restart and Run ALL for each submission"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "166652ce",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "PASS: Method 1\n",
      "PASS: Method 2\n",
      "PASS: Method 3\n",
      "PASS: Method 4\n",
      "ALL PASS\n"
     ]
    }
   ],
   "source": [
    "assert np.allclose(m1_magnitude, magnitude), np.allclose(m1_direction, direction)\n",
    "print (\"PASS: Method 1\")\n",
    "\n",
    "assert np.allclose(m2_magnitude, magnitude), np.allclose(m2_direction, direction)\n",
    "print (\"PASS: Method 2\")\n",
    "\n",
    "assert np.allclose(m3_magnitude, magnitude), np.allclose(m3_direction, direction)\n",
    "print (\"PASS: Method 3\")\n",
    "\n",
    "assert np.allclose(m4_magnitude, magnitude), np.allclose(m4_direction, direction)\n",
    "print (\"PASS: Method 4\")\n",
    "\n",
    "print (\"ALL PASS\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fb616c86",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "what",
   "language": "python",
   "name": "what"
  },
  "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.8.16"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
