{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "383463d7-a2a3-279b-b684-42f2c6dde093"
      },
      "source": [
        "# Preprocess Images\n",
        "Normalize the luminance values and resize the images to a standard shape. This is done because the training and test images come in a variety of shapes, sizes, and lighting."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "4592095c-7d3b-babe-346a-e4a5bf078344"
      },
      "outputs": [],
      "source": [
        "%matplotlib inline\n",
        "\n",
        "import glob\n",
        "import os\n",
        "import re\n",
        "\n",
        "import matplotlib.image as mpimg\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import pandas as pd\n",
        "import PIL\n",
        "from PIL import Image"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "02f26153-daf3-e7b6-5246-9aa20e6e5a39"
      },
      "source": [
        "## Sort numerically\n",
        "This is not really necessary but when you're exploring it's nice to have the images sorted numerically so that `cat.100.jpg` comes before `cat.1000.jpg`, etc."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "cd3bfe01-1916-212c-3a75-ad3000182eea"
      },
      "outputs": [],
      "source": [
        "def natural_key(string_):\n",
        "    \"\"\"\n",
        "    Define sort key that is integer-aware\n",
        "    \"\"\"\n",
        "    return [int(s) if s.isdigit() else s for s in re.split(r'(\\d+)', string_)]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "864efd39-ae2e-e0c5-fa4c-e9bb370a9a4f"
      },
      "source": [
        "## Normalize the image luminance\n",
        "it is common in image analysis to normalize the luminance (brightness) values to have mean 0 and standard deviation 1. We do that here and apply a slight contrast stretch, which also ensures the brightness values stay within the bounds of the image encoding.\n",
        "\n",
        "The normalization is applied to the luminance, not to the RGB channels individually. We first convert to `YCbCr` space, operate on the `Y` channel, and then convert back to `RGB`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "e19df3a3-fa7c-af03-ff41-bfc05373a400"
      },
      "outputs": [],
      "source": [
        "def norm_image(img):\n",
        "    \"\"\"\n",
        "    Normalize PIL image\n",
        "    \n",
        "    Normalizes luminance to (mean,std)=(0,1), and applies a [1%, 99%] contrast stretch\n",
        "    \"\"\"\n",
        "    img_y, img_b, img_r = img.convert('YCbCr').split()\n",
        "    \n",
        "    img_y_np = np.asarray(img_y).astype(float)\n",
        "\n",
        "    img_y_np /= 255\n",
        "    img_y_np -= img_y_np.mean()\n",
        "    img_y_np /= img_y_np.std()\n",
        "    scale = np.max([np.abs(np.percentile(img_y_np, 1.0)),\n",
        "                    np.abs(np.percentile(img_y_np, 99.0))])\n",
        "    img_y_np = img_y_np / scale\n",
        "    img_y_np = np.clip(img_y_np, -1.0, 1.0)\n",
        "    img_y_np = (img_y_np + 1.0) / 2.0\n",
        "    \n",
        "    img_y_np = (img_y_np * 255 + 0.5).astype(np.uint8)\n",
        "\n",
        "    img_y = Image.fromarray(img_y_np)\n",
        "\n",
        "    img_ybr = Image.merge('YCbCr', (img_y, img_b, img_r))\n",
        "    \n",
        "    img_nrm = img_ybr.convert('RGB')\n",
        "    \n",
        "    return img_nrm"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "fea6ac25-99d3-0494-0e05-ff4686ad44ba"
      },
      "source": [
        "## Resize the image\n",
        "We resize the images to be square with a default side length of 224 to be compatible with models trained on ImageNet. The aspect ratio is preserved and gray bars are added as necessary to make the image square."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "43e2795d-7474-975c-5770-c5a7e9896742"
      },
      "outputs": [],
      "source": [
        "def resize_image(img, size):\n",
        "    \"\"\"\n",
        "    Resize PIL image\n",
        "    \n",
        "    Resizes image to be square with sidelength size. Pads with black if needed.\n",
        "    \"\"\"\n",
        "    # Resize\n",
        "    n_x, n_y = img.size\n",
        "    if n_y > n_x:\n",
        "        n_y_new = size\n",
        "        n_x_new = int(size * n_x / n_y + 0.5)\n",
        "    else:\n",
        "        n_x_new = size\n",
        "        n_y_new = int(size * n_y / n_x + 0.5)\n",
        "\n",
        "    img_res = img.resize((n_x_new, n_y_new), resample=PIL.Image.BICUBIC)\n",
        "\n",
        "    # Pad the borders to create a square image\n",
        "    img_pad = Image.new('RGB', (size, size), (128, 128, 128))\n",
        "    ulc = ((size - n_x_new) // 2, (size - n_y_new) // 2)\n",
        "    img_pad.paste(img_res, ulc)\n",
        "\n",
        "    return img_pad"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "3e0687d2-172a-9724-fca2-35c4ffd85072"
      },
      "source": [
        "## Accumulate the image names"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "2d8ba464-d1c7-b100-b5df-e7a28cbf55de"
      },
      "outputs": [],
      "source": [
        "TRAIN_DIR = '../input/train/'\n",
        "TEST_DIR = '../input/test/'\n",
        "\n",
        "train_cats = sorted(glob.glob(os.path.join(TRAIN_DIR, 'cat*.jpg')), key=natural_key)\n",
        "train_dogs = sorted(glob.glob(os.path.join(TRAIN_DIR, 'dog*.jpg')), key=natural_key)\n",
        "train_all = train_cats + train_dogs\n",
        "\n",
        "test_all = sorted(glob.glob(os.path.join(TEST_DIR, '*.jpg')), key=natural_key)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "ee0a2406-7277-4480-451e-2796a32d7b95"
      },
      "source": [
        "## Preprocess a single image\n",
        "This cell will preprocess a specified or random image and display the before and after versions."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "55ebe7f7-cc5c-754b-85f3-91c710974082"
      },
      "outputs": [],
      "source": [
        "SIZE = 224  # for ImageNet models compatibility\n",
        "\n",
        "# Read the image\n",
        "choose_random_image = True\n",
        "if choose_random_image:\n",
        "    idx = np.random.randint(0, len(train_all))\n",
        "else:\n",
        "    idx = 24486\n",
        "print(idx)\n",
        "path = train_all[idx]\n",
        "img = Image.open(path)\n",
        "\n",
        "# Normalize it\n",
        "img_nrm = norm_image(img)\n",
        "\n",
        "\n",
        "# Resize it\n",
        "img_res = resize_image(img_nrm, SIZE)\n",
        "\n",
        "# Show it\n",
        "plt.figure(figsize=(8,4))\n",
        "plt.subplot(131)\n",
        "plt.title('Original')\n",
        "plt.imshow(img)\n",
        "\n",
        "plt.subplot(132)\n",
        "plt.title('Normalized')\n",
        "plt.imshow(img_nrm)\n",
        "\n",
        "plt.subplot(133)\n",
        "plt.title('Resized')\n",
        "plt.imshow(img_res)\n",
        "\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "1ae21816-3017-fa7f-58b4-8b4e3f0e5ee4",
        "collapsed": true
      },
      "outputs": [],
      "source": [
        ""
      ]
    }
  ],
  "metadata": {
    "_change_revision": 0,
    "_is_fork": false,
    "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.6.0"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}