{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "To participate, you'll need to git clone (or download the .zip from GitHub):\n", "\n", "

https://github.com/scikit-image/skimage-tutorials

\n", "
\n", "\n", "

You can do that in git using:

\n", "\n", "
git clone --depth=1 https://github.com/scikit-image/skimage-tutorials
\n", "\n", "
\n", "If you have already cloned the material, please issue `git pull` now and reload the notebook to ensure that you have the latest updates.\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "remove-input", "remove-output" ] }, "outputs": [], "source": [ "%matplotlib inline\n", "%config InlineBackend.figure_format = 'retina'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Images are numpy arrays" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Images are represented in ``scikit-image`` using standard ``numpy`` arrays. This allows maximum inter-operability with other libraries in the scientific Python ecosystem, such as ``matplotlib`` and ``scipy``.\n", "\n", "Let's see how to build a grayscale image as a 2D array:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "from matplotlib import pyplot as plt\n", "\n", "random_image = np.random.random([500, 500])\n", "\n", "plt.imshow(random_image, cmap='gray')\n", "plt.colorbar();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The same holds for \"real-world\" images:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from skimage import data\n", "\n", "coins = data.coins()\n", "\n", "print('Type:', type(coins))\n", "print('dtype:', coins.dtype)\n", "print('shape:', coins.shape)\n", "\n", "plt.imshow(coins, cmap='gray');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A color image is a 3D array, where the last dimension has size 3 and represents the red, green, and blue channels:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cat = data.chelsea()\n", "print(\"Shape:\", cat.shape)\n", "print(\"Values min/max:\", cat.min(), cat.max())\n", "\n", "plt.imshow(cat);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "These are *just NumPy arrays*. E.g., we can make a red square by using standard array slicing and manipulation:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cat[10:110, 10:110, :] = [255, 0, 0] # [red, green, blue]\n", "plt.imshow(cat);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Images can also include transparent regions by adding a 4th dimension, called an *alpha layer*." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Other shapes, and their meanings\n", "\n", "|Image type|Coordinates|\n", "|:---|:---|\n", "|2D grayscale|(row, column)|\n", "|2D multichannel|(row, column, channel)|\n", "|3D grayscale (or volumetric) |(plane, row, column)|\n", "|3D multichannel|(plane, row, column, channel)|" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Displaying images using matplotlib" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from skimage import data\n", "\n", "img0 = data.chelsea()\n", "img1 = data.rocket()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "f, (ax0, ax1) = plt.subplots(1, 2, figsize=(20, 10))\n", "\n", "ax0.imshow(img0)\n", "ax0.set_title('Cat', fontsize=18)\n", "ax0.axis('off')\n", "\n", "ax1.imshow(img1)\n", "ax1.set_title('Rocket', fontsize=18)\n", "ax1.set_xlabel(r'Launching position $\\alpha=320$')\n", "\n", "ax1.vlines([202, 300], 0, img1.shape[0], colors='magenta', linewidth=3, label='Side tower position')\n", "ax1.plot([168, 190, 200], [400, 200, 300], color='white', linestyle='--', label='Side angle')\n", "\n", "ax1.legend();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For more on plotting, see the [Matplotlib documentation](https://matplotlib.org/gallery/index.html#images-contours-and-fields) and [pyplot API](https://matplotlib.org/api/pyplot_summary.html)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Data types and image values\n", "\n", "In literature, one finds different conventions for representing image values:\n", "\n", "```\n", " 0 - 255 where 0 is black, 255 is white\n", " 0 - 1 where 0 is black, 1 is white\n", "```\n", "\n", "``scikit-image`` supports both conventions--the choice is determined by the\n", "data-type of the array.\n", "\n", "E.g., here, I generate two valid images:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "linear0 = np.linspace(0, 1, 2500).reshape((50, 50))\n", "linear1 = np.linspace(0, 255, 2500).reshape((50, 50)).astype(np.uint8)\n", "\n", "print(\"Linear0:\", linear0.dtype, linear0.min(), linear0.max())\n", "print(\"Linear1:\", linear1.dtype, linear1.min(), linear1.max())\n", "\n", "fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(15, 15))\n", "ax0.imshow(linear0, cmap='gray')\n", "ax1.imshow(linear1, cmap='gray');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The library is designed in such a way that any data-type is allowed as input,\n", "as long as the range is correct (0-1 for floating point images, 0-255 for unsigned bytes,\n", "0-65535 for unsigned 16-bit integers)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can convert images between different representations by using ``img_as_float``, ``img_as_ubyte``, etc.:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from skimage import img_as_float, img_as_ubyte\n", "\n", "image = data.chelsea()\n", "\n", "image_ubyte = img_as_ubyte(image)\n", "image_float = img_as_float(image)\n", "\n", "print(\"type, min, max:\", image_ubyte.dtype, image_ubyte.min(), image_ubyte.max())\n", "print(\"type, min, max:\", image_float.dtype, image_float.min(), image_float.max())\n", "print()\n", "print(\"231/255 =\", 231/255.)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Your code would then typically look like this:\n", "\n", "```python\n", "def my_function(any_image):\n", " float_image = img_as_float(any_image)\n", " # Proceed, knowing image is in [0, 1]\n", "```\n", "\n", "We recommend using the floating point representation, given that\n", "``scikit-image`` mostly uses that format internally." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Image I/O\n", "\n", "Mostly, we won't be using input images from the scikit-image example data sets. Those images are typically stored in JPEG or PNG format. Since scikit-image operates on NumPy arrays, *any* image reader library that provides arrays will do. Options include imageio, matplotlib, pillow, etc.\n", "\n", "scikit-image conveniently wraps many of these in the `io` submodule, and will use whichever of the libraries mentioned above are installed:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from skimage import io\n", "\n", "image = io.imread('../images/balloon.jpg')\n", "\n", "print(type(image))\n", "print(image.dtype)\n", "print(image.shape)\n", "print(image.min(), image.max())\n", "\n", "plt.imshow(image);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We also have the ability to load multiple images, or multi-layer TIFF images:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ic = io.ImageCollection('../images/*.png:../images/*.jpg')\n", "\n", "print('Type:', type(ic))\n", "\n", "ic.files" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "\n", "f, axes = plt.subplots(nrows=3, ncols=len(ic) // 3 + 1, figsize=(20, 5))\n", "\n", "# subplots returns the figure and an array of axes\n", "# we use `axes.ravel()` to turn these into a list\n", "axes = axes.ravel()\n", "\n", "for ax in axes:\n", " ax.axis('off')\n", "\n", "for i, image in enumerate(ic):\n", " axes[i].imshow(image, cmap='gray')\n", " axes[i].set_title(os.path.basename(ic.files[i]))\n", " \n", "plt.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Aside: `enumerate`\n", "\n", "`enumerate` gives us each element in a container, along with its position." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "animals = ['cat', 'dog', 'leopard']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for i, animal in enumerate(animals):\n", " print('The animal in position {} is {}'.format(i, animal))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise: draw the letter H\n", "\n", "Define a function that takes as input an RGB image and a pair of coordinates (row, column), and returns a copy with a green letter H overlaid at those coordinates. The coordinates point to the top-left corner of the H.\n", "\n", "The arms and strut of the H should have a width of 3 pixels, and the H itself should have a height of 24 pixels and width of 20 pixels.\n", "\n", "Start with the following template:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "hide-output" ] }, "outputs": [], "source": [ "def draw_H(image, coords, color=(0, 255, 0)):\n", " out = image.copy()\n", " \n", " ...\n", " \n", " return out " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Test your function like so:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "remove-output" ] }, "outputs": [], "source": [ "cat = data.chelsea()\n", "cat_H = draw_H(cat, (50, -50))\n", "plt.imshow(cat_H);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise: visualizing RGB channels\n", "\n", "Display the different color channels of the image along (each as a gray-scale image). Start with the following template:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "raises-exception", "remove-output" ] }, "outputs": [], "source": [ "# --- read in the image ---\n", "\n", "image = plt.imread('../images/Bells-Beach.jpg')\n", "\n", "# --- assign each color channel to a different variable ---\n", "\n", "r = ... # FIXME: grab channel from image...\n", "g = ... # FIXME\n", "b = ... # FIXME\n", "\n", "# --- display the image and r, g, b channels ---\n", "\n", "f, axes = plt.subplots(1, 4, figsize=(16, 5))\n", "\n", "for ax in axes:\n", " ax.axis('off')\n", "\n", "(ax_r, ax_g, ax_b, ax_color) = axes\n", " \n", "ax_r.imshow(r, cmap='gray')\n", "ax_r.set_title('red channel')\n", "\n", "ax_g.imshow(g, cmap='gray')\n", "ax_g.set_title('green channel')\n", "\n", "ax_b.imshow(b, cmap='gray')\n", "ax_b.set_title('blue channel')\n", "\n", "# --- Here, we stack the R, G, and B layers again\n", "# to form a color image ---\n", "ax_color.imshow(np.stack([r, g, b], axis=2))\n", "ax_color.set_title('all channels');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, take a look at the following R, G, and B channels. How would their combination look? (Write some code to confirm your intuition.)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from skimage import draw\n", "\n", "red = np.zeros((300, 300))\n", "green = np.zeros((300, 300))\n", "blue = np.zeros((300, 300))\n", "\n", "r, c = draw.disk(center=(100, 100), radius=100)\n", "red[r, c] = 1\n", "\n", "r, c = draw.disk(center=(100, 200), radius=100)\n", "green[r, c] = 1\n", "\n", "r, c = draw.disk(center=(200, 150), radius=100)\n", "blue[r, c] = 1\n", "\n", "f, axes = plt.subplots(1, 3)\n", "for (ax, channel) in zip(axes, [red, green, blue]):\n", " ax.imshow(channel, cmap='gray')\n", " ax.axis('off')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise: Convert to grayscale (\"black and white\")\n", "\n", "The *relative luminance* of an image is the intensity of light coming from each point. Different colors contribute differently to the luminance: it's very hard to have a bright, pure blue, for example. So, starting from an RGB image, the luminance is given by:\n", "\n", "$$\n", "Y = 0.2126R + 0.7152G + 0.0722B\n", "$$\n", "\n", "Use Python 3.5's matrix multiplication, `@`, to convert an RGB image to a grayscale luminance image according to the formula above.\n", "\n", "Compare your results to that obtained with `skimage.color.rgb2gray`.\n", "\n", "Change the coefficients to 1/3 (i.e., take the mean of the red, green, and blue channels, to see how that approach compares with `rgb2gray`)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "raises-exception", "remove-output" ] }, "outputs": [], "source": [ "from skimage import color, img_as_float\n", "\n", "image = img_as_float(io.imread('../images/balloon.jpg'))\n", "\n", "gray = color.rgb2gray(image)\n", "my_gray = ... # FIXME\n", "\n", "# --- display the results ---\n", "\n", "f, (ax0, ax1) = plt.subplots(1, 2, figsize=(10, 6))\n", "\n", "ax0.imshow(gray, cmap='gray')\n", "ax0.set_title('skimage.color.rgb2gray')\n", "\n", "ax1.imshow(my_gray, cmap='gray')\n", "ax1.set_title('my rgb2gray')" ] } ], "metadata": { "celltoolbar": "Tags", "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.8.2" } }, "nbformat": 4, "nbformat_minor": 1 }