Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

More idiomatic way to display images in a grid with numpy

Is there a more idiomatic way to display a grid of images as in the below example?

import numpy as np

def gallery(array, ncols=3):
    nrows = np.math.ceil(len(array)/float(ncols))
    cell_w = array.shape[2]
    cell_h = array.shape[1]
    channels = array.shape[3]
    result = np.zeros((cell_h*nrows, cell_w*ncols, channels), dtype=array.dtype)
    for i in range(0, nrows):
        for j in range(0, ncols):
            result[i*cell_h:(i+1)*cell_h, j*cell_w:(j+1)*cell_w, :] = array[i*ncols+j]
    return result

I tried using hstack and reshape etc, but could not get the right behaviour.

I am interested in using numpy to do this because there is a limit to how many images you can plot with matplotlib calls to subplot and imshow.

If you need sample data to test you can use your webcam like so:

import cv2
import matplotlib.pyplot as plt
_, img = cv2.VideoCapture(0).read()

plt.imshow(gallery(np.array([img]*6)))
like image 922
Frank Wilson Avatar asked Feb 04 '17 13:02

Frank Wilson


People also ask

How do I show an image in a grid in Python?

Matplotlib Image grid fig - It's the parent figure for displaying images. rect - This will set the axes position as (left, bottom, width, height) tuple or as a three-digit subplot position code, it's 111 in our case. nrows_ncols - number of rows and cols, the shape of the grid, it's 2x2 in our case here.

How do I display a NumPy array of an image?

Display Numpy array as Image We import numpy library to create Numpy array and PIL library to add support for display and saving. Create a sample Numpy array and convert the array to PIL format using fromarray() function of PIL. This will open a new terminal window where the image will be displayed.

What does Meshgrid do in NumPy?

The numpy. meshgrid function is used to create a rectangular grid out of two given one-dimensional arrays representing the Cartesian indexing or Matrix indexing.


3 Answers

import numpy as np
import matplotlib.pyplot as plt

def gallery(array, ncols=3):
    nindex, height, width, intensity = array.shape
    nrows = nindex//ncols
    assert nindex == nrows*ncols
    # want result.shape = (height*nrows, width*ncols, intensity)
    result = (array.reshape(nrows, ncols, height, width, intensity)
              .swapaxes(1,2)
              .reshape(height*nrows, width*ncols, intensity))
    return result

def make_array():
    from PIL import Image
    return np.array([np.asarray(Image.open('face.png').convert('RGB'))]*12)

array = make_array()
result = gallery(array)
plt.imshow(result)
plt.show()

yields enter image description here


We have an array of shape (nrows*ncols, height, weight, intensity). We want an array of shape (height*nrows, width*ncols, intensity).

So the idea here is to first use reshape to split apart the first axis into two axes, one of length nrows and one of length ncols:

array.reshape(nrows, ncols, height, width, intensity)

This allows us to use swapaxes(1,2) to reorder the axes so that the shape becomes (nrows, height, ncols, weight, intensity). Notice that this places nrows next to height and ncols next to width.

Since reshape does not change the raveled order of the data, reshape(height*nrows, width*ncols, intensity) now produces the desired array.

This is (in spirit) the same as the idea used in the unblockshaped function.

like image 93
unutbu Avatar answered Oct 22 '22 02:10

unutbu


Another way is to use view_as_blocks . Then you avoid to swap axes by hand :

from skimage.util import view_as_blocks
import numpy as np

def refactor(im_in,ncols=3):
    n,h,w,c = im_in.shape
    dn = (-n)%ncols # trailing images
    im_out = (np.empty((n+dn)*h*w*c,im_in.dtype)
           .reshape(-1,w*ncols,c))
    view=view_as_blocks(im_out,(h,w,c))
    for k,im in enumerate( list(im_in) + dn*[0] ):
        view[k//ncols,k%ncols,0] = im 
    return im_out
like image 31
B. M. Avatar answered Oct 22 '22 03:10

B. M.


This answer is based off @unutbu's, but this deals with HWC ordered tensors. Furthermore, it shows black tiles for any channels that do not factorize evenly into the given rows/columns.

def tile(arr, nrows, ncols):
    """
    Args:
        arr: HWC format array
        nrows: number of tiled rows
        ncols: number of tiled columns
    """
    h, w, c = arr.shape
    out_height = nrows * h
    out_width = ncols * w
    chw = np.moveaxis(arr, (0, 1, 2), (1, 2, 0))

    if c < nrows * ncols:
        chw = chw.reshape(-1).copy()
        chw.resize(nrows * ncols * h * w)

    return (chw
        .reshape(nrows, ncols, h, w)
        .swapaxes(1, 2)
        .reshape(out_height, out_width))

Here's a corresponding detiling function for the reverse direction:

def detile(arr, nrows, ncols, c, h, w):
    """
    Args:
        arr: tiled array
        nrows: number of tiled rows
        ncols: number of tiled columns
        c: channels (number of tiles to keep)
        h: height of tile
        w: width of tile
    """
    chw = (arr
        .reshape(nrows, h, ncols, w)
        .swapaxes(1, 2)
        .reshape(-1)[:c*h*w]
        .reshape(c, h, w))

    return np.moveaxis(chw, (0, 1, 2), (2, 0, 1)).reshape(h, w, c)
like image 1
Mateen Ulhaq Avatar answered Oct 22 '22 02:10

Mateen Ulhaq