Each element of an array can be located by its position in each dimension. Numpy offers multiple ways to access single elements or groups of elements in very efficient ways. We will illustrate these concepts both with small simple matrices as well as a regular image, in order to illustrate them.
import numpy as np
import matplotlib.pyplot as plt
plt.gray();
import skimage
We first load an image included in the scikit-image package:
image = skimage.data.chelsea()
plt.imshow(image);
We can check the dimensions of the image and see that it is an RGB image with 3 channels:
image.shape
We create a small 2D array to use as an example:
normal_array = np.random.normal(10, 2, (3,4))
normal_array
It is very easy to access an array's values. One can just pass an index for each dimensions. For example to recover the value on the last row and second column of the normal_array
array we just write (remember counting starts at 0):
single_value = normal_array[2,1]
single_value
What is returned in that case is a single number that we can re-use:
single_value += 10
single_value
And that change doesn't affect the original value in the array:
normal_array
However we can also directly change the value in an array:
normal_array[2,1] = 23
normal_array
One can also select multiple elements in each dimension (e.g. multiple rows and columns in 2D) by using the start:end:step
syntax. By default, if omitted, start=0
, end=last element
and step=1
. For example to select the first and second rows of the first column, we can write:
normal_array[0:2,0]
Note that the end
element is not included. One can use the same notation for all dimensions:
normal_array[0:2,2:4]
normal_array[1:,2:4]
If we only specify :
, it means we want to recover all elements in that dimension:
normal_array[:,2:4]
Also in general, if you only specify the value for a single axis, this will take the first element of the first dimension:
normal_array
normal_array[1]
Finally note that if you want to recover only one element along a dimension (single row, column etc), you can do that in two ways:
normal_array[0,:]
This returns a one-dimensional array containing a single row from the original array:
normal_array[0,:].shape
Instead, if you specify actual boundaries that still return only a single row:
normal_array[0:1,:]
normal_array[0:1,:].shape
you recover a tow dimensional array where one of the dimensions has a size of 1.
We can for example only select half the rows of the image but all columns and channels:
image.shape
sub_image = image[0:150,:,:]
plt.imshow(sub_image);
Or we can take every fith column and row from a single channel, which returns a pixelated version of the original image:
plt.imshow(image[::5,::5,0]);
As often with Python when you create a new variable using a sub-array, that variable is not independent from the original variable:
sub_array = normal_array[:,2:4]
sub_array
normal_array
If for example we modify normal_array
, this is going to be reflected in sub_array
too:
normal_array[0,2] = 100
normal_array
sub_array
The converse is also true:
sub_array[0,1] = 50
sub_array
normal_array
If you want your sub-array to be an independent copy of the original, you have to use the .copy()
method:
sub_array_copy = normal_array[1:3,:].copy()
sub_array_copy
sub_array_copy[0,0] = 500
sub_array_copy
normal_array
In the above case, we are limited to select rectangular sub-regions of the array. But sometimes we want to recover a series of specific elements for example the elements (row=0, column=3) and (row=2, column=2). To achieve that we can simply index the array with a list containing row indices and another with columns indices:
row_indices = [0,2]
col_indices = [3,2]
normal_array[row_indices, col_indices]
normal_array
selected_elements = normal_array[row_indices, col_indices]
selected_elements
The last way of extracting elements from an array is to use a boolean array of same shape. For example let's create a boolean array by comparing our original matrix to a threshold:
bool_array = normal_array > 40
bool_array
We see that we only have two elements which are above the threshold. Now we can use this logical array to index the original array. Imagine that the logical array is a mask with holes only in True
positions and that we superpose it to the original array. Then we just take all the values visible in the holes:
normal_array[bool_array]
Coming back to our real image, we can for example first create an image that contains a single channel and then find bright regions in it:
single_channel = image[:,:,0]
mask = single_channel > 150
plt.imshow(mask);
And now we can recover all the pixels that are "selected" by this mask:
single_channel[mask]
Often it is necessary to reshape arrays, i.e. keep elements unchanged but change their position. There are multiple functions that allow one to do this. The main one is of course reshape
.
reshape
¶Given an array of $MxN$ elements, one can reshape it with a shape $OxP$ as long as $M*N = O*P$.
reshaped = np.reshape(normal_array,(2,6))
reshaped
reshaped.shape
300*451/150
With the image as example, we can reshape the array from $300x451x3$ to $150x902x3$:
plt.imshow(np.reshape(image, (150,902,3)))
It's also possible to simply flatten an array i.e. remove all dimensions to create a 1D array. This can be useful for example to create a histogram of a high-dimensional array.
flattened = np.ravel(normal_array)
flattened
flattened.shape
Another common way that leads to reshaping is projection. Let's consider again our normal_array
:
normal_array
We can project all values along the first or second axis, to recover for each row/column the largest value:
proj0 = np.max(normal_array, axis = 0)
proj0
proj0.shape
We see that our projected array has lost a dimension, the one along which we performed the projection. With the image, we could project all channels along the third dimension:
plt.imshow(image.max(axis=2));
We can also simply exchange the position of dimensions. This can be achieved in different ways. For example we can np.roll
dimensions, i.e. circularly shift dimensions. This conserves the relative oder of all axes:
array3D = np.ones((4, 10, 20))
array3D.shape
array_rolled = np.rollaxis(array3D, axis=1, start=0)
array_rolled.shape
Alternatively you can swap two axes. This doesn't preserver their relative positions:
array_swapped = np.swapaxes(array3D, 0,2)
array_swapped.shape
With the image, we can for example swap the two first axes:
plt.imshow(np.swapaxes(image, 0, 1));
Finally, we can also change the position of elements without changing the shape of the array. For example if we have an array with two columns, we can swap them:
array2D = np.random.normal(0,1,(4,2))
array2D
np.fliplr(array2D)
Similarly, if we have two rows:
array2D = np.random.normal(0,1,(2,4))
array2D
np.flipud(array2D)
For more complex cases you can also use the more general np.flip()
function.
With the image, flipping a dimension just mirrors the picture. To do that we select a single channel:
plt.imshow(np.flipud(image[:,:,0]));