Reputation: 161
I have 100 small images which I want to combine into one large (10x10) grid image for display with imshow. Each image (as a numpy array) is within the variable of a cell object. At the moment I'm using concatenate to first create vertical strips then using concatenate to connect all of those strips but it seems kinda clunky. Is there a better way to do this? I feel like I should be able to just create a numpy array the size of the final image (800 x 600) then plop each image in, but it seems to be beyond my ability to wrap my head around the syntax.
def stackImages(im1, im2, axisToStack):
newImage = np.concatenate((im1, im2), axis = axisToStack)
return newImage
def compileCells():
#Make a list of strips
strips = [(np.zeros((0,cellWidth,3), np.uint8)) for i in range(numberOfCells)]
for x in range(numberOfCells):
for i in range(numberOfCells):
strips[x] = stackImages(cellArray[i+(x*numberOfCells)].image, strips[x], 0)
display = strips[0]
for c in range(1,numberOfCells):
display = stackImages(strips[c], display, 1)
return display
Upvotes: 3
Views: 2738
Reputation: 880887
Copying arrays can be a real speed killer when working with NumPy. Every time
np.concatenate
is called, space for a new array is allocated, and all the old
data is copied into the new array. A way to make your code faster is to reduce
the amount of copying.
So as you suggested, the faster way is to allocate space for the final array,
display
from the very the beginning:
display = np.empty((cellHeight*nrows, cellWidth*ncols, 3), dtype=np.uint8)
and then copy the data from cellArray
into display
just once:
for i, j in IT.product(range(nrows), range(ncols)):
arr = cellArray[i*ncols+j].image
x, y = i*cellHeight, j*cellWidth
display[x:x+cellHeight, y:y+cellWidth, :] = arr
For example,
import numpy as np
import matplotlib.pyplot as plt
import itertools as IT
def compileCells(cellArray, nrows, ncols, cellHeight, cellWidth):
display = np.empty((cellHeight*nrows, cellWidth*ncols, 3), dtype=np.uint8)
for i, j in IT.product(range(nrows), range(ncols)):
# arr = cellArray[i*ncols+j].image # you may need this
arr = cellArray[i*ncols+j] # my simplified cellArray uses this
x, y = i*cellHeight, j*cellWidth
display[x:x+cellHeight, y:y+cellWidth, :] = arr
return display
cellHeight, cellWidth = 80, 60
nrows = ncols = numberOfCells = 10
cellArray = [np.full((cellHeight, cellWidth, 3), i)
for i in np.linspace(0, 255, nrows*ncols)]
display = compileCells(cellArray, nrows, ncols, cellHeight, cellWidth)
plt.imshow(display)
plt.show()
yields
Note, your code implies that cellArray
is a list of objects whose image
attributes are NumPy arrays. To make the example code above runnable and simple(r),
I've defined cellArray
above to be a list of NumPy arrays.
You may need to uncomment
# arr = cellArray[i*ncols+j].image
and comment out
arr = cellArray[i*ncols+j]
to fit your definition of cellArray
.
Let's compare the amount of copying done by the two approaches:
Using the original approach, if we say that an image array has size 1, then to build a strip
requires
allocating arrays of size 1, 2, ..., 10. So one strip requires allocating arrays
of total size 1+2+...+10 = 10(11)/2 = 55. To build display
requires allocating
arrays of total size 55(1+2+..+10) = 55*55 = 3025. Each allocation of space is
accompanied by a copy operation. The amount of copying grows quadratically with the number of cells in the final array.
In contrast, if we allocate space for the final display
just once, then we
only need to allocate of total size 10*10 = 100. Here, the amount of copying grows linearly with the number of cells.
Upvotes: 3