{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Image Matrix\n", "\n", "The `image_matrix()` function arranges a set of images in a grid.\n", "\n", "Dimensions of the grid are determined by the shape of the input Numpy 2D array.\n", "\n", "Each element of the input 2D array is an 2D or 3D Numpy array itself specifying either a grayscale image (2D array) \n", "or a color RGB(A) image (3D array).\n", "\n", "For more information on image arrays please see the documentation of `geom_imshow()` function.\n", "\n", "The `image_matrix()` function uses `geom_imshow()` under the hood, so you may want to check these demos as well:\n", "\n", " - [image_101.ipynb](https://nbviewer.org/github/JetBrains/lets-plot-docs/blob/master/source/examples/cookbook/image_101.ipynb)\n", " - [image_fisher_boat.ipynb](https://nbviewer.org/github/JetBrains/lets-plot-docs/blob/master/source/examples/cookbook/image_fisher_boat.ipynb)" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2024-04-17T07:34:43.165534Z", "iopub.status.busy": "2024-04-17T07:34:43.165457Z", "iopub.status.idle": "2024-04-17T07:34:43.492812Z", "shell.execute_reply": "2024-04-17T07:34:43.492352Z" } }, "outputs": [ { "data": { "text/html": [ "\n", "
\n", " \n", " " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import numpy as np\n", "from lets_plot import *\n", "from lets_plot.bistro.im import image_matrix\n", "\n", "LetsPlot.setup_html()" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2024-04-17T07:34:43.505548Z", "iopub.status.busy": "2024-04-17T07:34:43.505415Z", "iopub.status.idle": "2024-04-17T07:34:43.852959Z", "shell.execute_reply": "2024-04-17T07:34:43.852531Z" } }, "outputs": [ { "data": { "text/plain": [ "(225, 225, 3)" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Load image file.\n", "from PIL import Image\n", "import requests\n", "from io import BytesIO\n", "\n", "response = requests.get('https://github.com/JetBrains/lets-plot-docs/raw/master/source/examples/cookbook/images/fisher_boat.png')\n", "image = Image.open(BytesIO(response.content))\n", "img = np.asarray(image)\n", "img.shape\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create 2 x 3 grid of images\n", "\n", "Use the same image to fill the array elements." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2024-04-17T07:34:43.854290Z", "iopub.status.busy": "2024-04-17T07:34:43.854201Z", "iopub.status.idle": "2024-04-17T07:34:43.856058Z", "shell.execute_reply": "2024-04-17T07:34:43.855776Z" } }, "outputs": [], "source": [ "M = 2 # rows\n", "N = 3 # columns\n", "X = np.empty([M, N], dtype=object)\n", "X.fill(img)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Display images in 2 x 3 grid" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2024-04-17T07:34:43.857056Z", "iopub.status.busy": "2024-04-17T07:34:43.856935Z", "iopub.status.idle": "2024-04-17T07:34:43.947754Z", "shell.execute_reply": "2024-04-17T07:34:43.947541Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "image_matrix(X)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Images in the grid can be of different sizes" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2024-04-17T07:34:43.950637Z", "iopub.status.busy": "2024-04-17T07:34:43.950555Z", "iopub.status.idle": "2024-04-17T07:34:43.997133Z", "shell.execute_reply": "2024-04-17T07:34:43.996865Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Lets vary size of images in the matrix\n", "X1 = np.empty([M, N], dtype=object)\n", "for row in range(M):\n", " for col in range(N):\n", " v = (row + col + 1) * 10\n", " X1[row][col] = img[v:-v,v:-v,:]\n", "\n", "image_matrix(X1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Normalization of grayscale image\n", "\n", "By default, luminance values in grayscale image will be scaled to [0-255] range using a linear scaler." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2024-04-17T07:34:43.999305Z", "iopub.status.busy": "2024-04-17T07:34:43.999124Z", "iopub.status.idle": "2024-04-17T07:34:44.001025Z", "shell.execute_reply": "2024-04-17T07:34:44.000857Z" } }, "outputs": [ { "data": { "text/plain": [ "dtype('uint8')" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Generate a grayscale image from an RGB image \n", "# by selecting only one `R` channel:\n", "img_gs = img[:,:,0]\n", "\n", "img_gs.dtype" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For the demo purposes we will reduce the range of data in some of the images.\n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2024-04-17T07:34:44.002094Z", "iopub.status.busy": "2024-04-17T07:34:44.002003Z", "iopub.status.idle": "2024-04-17T07:34:44.003806Z", "shell.execute_reply": "2024-04-17T07:34:44.003631Z" } }, "outputs": [], "source": [ "def _degrade(grayscale_img:np.ndarray, coef:float):\n", " vmin = grayscale_img.min()\n", " vmax = grayscale_img.max()\n", " middle = vmin + (vmax - vmin) / 2\n", "\n", " rows, cols = grayscale_img.shape\n", " for row in range(rows):\n", " for col in range(cols):\n", " v = float(grayscale_img[row][col])\n", " v_new = middle + (v - middle) * coef\n", " grayscale_img[row][col] = int(v_new)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now lets fill an 2D array with images, applying the `_degrade()` function on each iteration. \n", "\n", "The last image added is the one \"degraded\" the most." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2024-04-17T07:34:44.004880Z", "iopub.status.busy": "2024-04-17T07:34:44.004746Z", "iopub.status.idle": "2024-04-17T07:34:44.082190Z", "shell.execute_reply": "2024-04-17T07:34:44.081929Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0, 0] data range: [38-216]\n", "[0, 1] data range: [64-189]\n", "[0, 2] data range: [82-170]\n", "[1, 0] data range: [95-156]\n", "[1, 1] data range: [104-146]\n", "[1, 2] data range: [110-139]\n" ] } ], "source": [ "X2 = np.empty([M, N], dtype=object)\n", "img_copy = img_gs.copy()\n", "for row in range(M):\n", " for col in range(N):\n", " X2[row][col] = img_copy\n", " img_copy = img_copy.copy()\n", " _degrade(img_copy, coef=.7)\n", " print(\"[{}, {}] data range: [{}-{}]\".format(row, col, img_copy.min(), img_copy.max()))\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Display images in grid with normalization (default)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2024-04-17T07:34:44.083286Z", "iopub.status.busy": "2024-04-17T07:34:44.083212Z", "iopub.status.idle": "2024-04-17T07:34:44.105990Z", "shell.execute_reply": "2024-04-17T07:34:44.105808Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "image_matrix(X2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### Display images in grid with NO normalization\n" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2024-04-17T07:34:44.107157Z", "iopub.status.busy": "2024-04-17T07:34:44.107061Z", "iopub.status.idle": "2024-04-17T07:34:44.130072Z", "shell.execute_reply": "2024-04-17T07:34:44.129885Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "image_matrix(X2, norm=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Scaling image size\n", "\n", "In case the image size is too small or too big to show, the displayed dimensions can be changed using the parameter `scale`.\n", "\n", "For example,`digits` dataset from `sklearn` package contains very small 8x8 pictures of digits. " ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "execution": { "iopub.execute_input": "2024-04-17T07:34:44.131322Z", "iopub.status.busy": "2024-04-17T07:34:44.131250Z", "iopub.status.idle": "2024-04-17T07:34:44.328395Z", "shell.execute_reply": "2024-04-17T07:34:44.328164Z" } }, "outputs": [], "source": [ "# Load `digits` form sklearn.\n", "from sklearn.datasets import load_digits\n", "digits_bunch = load_digits()\n", "digits_data = digits_bunch.data" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "execution": { "iopub.execute_input": "2024-04-17T07:34:44.330161Z", "iopub.status.busy": "2024-04-17T07:34:44.329870Z", "iopub.status.idle": "2024-04-17T07:34:44.331849Z", "shell.execute_reply": "2024-04-17T07:34:44.331647Z" } }, "outputs": [], "source": [ "# Create 4x4 ndarray containing the first 16 digits in from `digits` dataset. \n", "cols = 4\n", "rows = 4\n", "X4 = np.empty((rows, cols), dtype=object)\n", "for row in range(rows):\n", " for col in range(cols):\n", " i = row * cols + col;\n", " digit_data = digits_data[i]\n", " digit_img = digit_data.reshape(8, 8)\n", " X4[row][col] = digit_img" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "execution": { "iopub.execute_input": "2024-04-17T07:34:44.332840Z", "iopub.status.busy": "2024-04-17T07:34:44.332740Z", "iopub.status.idle": "2024-04-17T07:34:44.334679Z", "shell.execute_reply": "2024-04-17T07:34:44.334480Z" } }, "outputs": [ { "data": { "text/plain": [ "(8, 8)" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X4[0][0].shape" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "execution": { "iopub.execute_input": "2024-04-17T07:34:44.335554Z", "iopub.status.busy": "2024-04-17T07:34:44.335432Z", "iopub.status.idle": "2024-04-17T07:34:44.343490Z", "shell.execute_reply": "2024-04-17T07:34:44.343292Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# NOTE: minimal plot size in Lets-Plot is 50x50 px.\n", "# thus we actually don't see 8x8 images in their native dimensions here. \n", "image_matrix(X4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Scale Up\n", "\n", "Each digit image is 8x8 px. Multiply by 15 to see 120x120 px images." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "execution": { "iopub.execute_input": "2024-04-17T07:34:44.344353Z", "iopub.status.busy": "2024-04-17T07:34:44.344278Z", "iopub.status.idle": "2024-04-17T07:34:44.351335Z", "shell.execute_reply": "2024-04-17T07:34:44.351143Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "image_matrix(X4, scale=15)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Scale Down\n", "\n", "Use values < 1. to see smaller images." ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "execution": { "iopub.execute_input": "2024-04-17T07:34:44.352192Z", "iopub.status.busy": "2024-04-17T07:34:44.352117Z", "iopub.status.idle": "2024-04-17T07:34:44.408474Z", "shell.execute_reply": "2024-04-17T07:34:44.408168Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "image_matrix(X, scale=.3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The `cmap` parameter\n", "\n", "Use parameter `cmap` to display grayscale images in pseudo-colors." ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "execution": { "iopub.execute_input": "2024-04-17T07:34:44.411129Z", "iopub.status.busy": "2024-04-17T07:34:44.411035Z", "iopub.status.idle": "2024-04-17T07:34:44.443481Z", "shell.execute_reply": "2024-04-17T07:34:44.443291Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "image_matrix(X2, cmap=\"viridis\", norm=False)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.10.13" } }, "nbformat": 4, "nbformat_minor": 4 }