import numpy as npNumPy documentation¶
Note: Numpy has a very good and extensive documentation, which you can find at https://
Multi-dimensional images as arrays¶
Let us import and inspect an multidimensional image:
import skimage.io
from matplotlib import pyplot as plt
image_multi = skimage.io.imread('https://github.com/guiwitz/microfilm/raw/master/demodata/coli_nucl_ori_ter.tif')
image_multi.shapeNow this image has more than just rows and columns of pixels. In total, we have 4 dimensions, the last two corresponding to y and x coordinates. The two first correspond to different channels (3) and time-points (30). If we want to access and plot a partial 2D image, we have to use indexing. For example, let’s display the 2nd channel of the 6th time point:
im_singleplane = image_multi[1, 5].copy()
plt.imshow(im_singleplane, cmap='gray')Note that we additionally used the copy method. This ensures that whatever modification we make to the cropped version doesn’t affect the original image! We will do this often in the future, so it is good to remember.
Cropping¶
A common operation is to only consider part of an image for further analysis. We have learned in the previous chapters that we could use slicing to extract only part of an array which for an image is called cropping. For example, we might want to focus on the rows around 110 and columns around 50:
im_crop = im_singleplane[90:130, 30:70].copy()
plt.imshow(im_crop, cmap='gray')We can also check again the size of this cropped image. We expect it to be a square of 40x40 pixels (given the slicing numbers we used above):
im_crop.shapeOperations on images¶
All the operations we have seen for arrays in general are of course still valid for images. So we can do simple calculus or apply functions to images. Let’s first print the cropped image again and then see how the operations change the pixel values:
im_cropim_crop + 3im_crop * 3np.log10(im_crop)Projections (aggregating functions over dimensions)¶
When handling images, the min, max are very relevant as they implement the common projections that one often performs on multi-dimensional data. For example we can project all time-points on a single plane using mean intensity projection. Using the original multidimensional image again (remember that we did not change it since we always created copies to manipulate), we would write:
im_proj = np.mean(image_multi, axis=1)As we have aggregated over the time dimension, we now have a 3D image (2 spatial dimensions plus channels). The time-axis has been compressed to a single plane:
print("Shape of the original image:", image_multi.shape)
print("Shape after projection:", im_proj.shape)Finally, let us display the first channel of the projected image:
plt.imshow(im_proj[0], cmap='gray')Operations between images/arrays¶
As with any arrays, we can of course also perform operations between images. For example, we could create an image containing “noise” and add it to an image. Like this we can test the quality of a denoising algorithm.
First we create a noise image with the same shape as the original image:
im_noise = np.random.normal(0, 1, im_singleplane.shape)
plt.imshow(im_noise, cmap='gray')Then we simply add the noise image to the original image:
im_singleplane_noisy = im_singleplane + im_noise
plt.imshow(im_singleplane_noisy, cmap='gray')Image processing operations¶
In addition to the standard mathematical operations provided by Numpy, we can apply operations which are specifically designed for images. Such functions are typically provided by libraries like scikit-image.
A classical example is the blurring of an image using a Gaussian filter:
im_gauss = skimage.filters.gaussian(im_singleplane, sigma=2, preserve_range=True)
plt.imshow(im_gauss, cmap='gray');Masking/thresholding¶
Finally, we have seen that we can create boolean arrays by applying logical operations to arrays. In the world of images, this is typically used to thereshold an image, i.e. extracting only parts of the image which have an intensity higher that a given threshold.
For example on the blurred image above, we can extract all pixels which have an intensity higher than 103:
im_thresholded = im_gauss > 103
im_thresholdedplt.imshow(im_thresholded, cmap='gray')We see that we obtain a boolean array that we can still plot using imshow and that gives us a binary image with bright regions (above 103) shown as white (True) and darker ones as black (False).
With the right threshold value, we can extract the regions of interest in an image - like the cell bodies in this fluorescence image.
Rearranging and extending dimensions¶
Let us load another image:
image = skimage.io.imread("https://upload.wikimedia.org/wikipedia/commons/5/5b/Serengeti_Elefantenherde1.jpg?download")
print("Shape of the image:", image.shape)
print("Data type of the image:", image.dtype)
print("Value range of the image:", image.min(), "to", image.max())
plt.imshow(image)Playing with the colour channels¶
As you can see, in RGB images, the three channels are stored in the last dimension. This is different from the previous image, where the channels were stored in the first dimension. We can rearrange the colour channels by indexing:
image_recolor = image[:, :, [2, 1, 0]]
plt.imshow(image_recolor)Or we could remove the blue signal by setting it to zero:
image_no_blue = image.copy()
image_no_blue[:, :, 2] = 0
plt.imshow(image_no_blue)Or we could extract only the red channel, and then stack it again to an RGB image (think about why it is now only gray):
red_channel = image[:, :, 0]
rgb_only_red = np.stack((red_channel, red_channel, red_channel), axis=-1)
plt.imshow(rgb_only_red)Manipulating the spatial dimensions¶
Let’s flip the image along the horizontal axis. Instead of using negative steps in slicing, we now use the flip function from Numpy (sometimes there are multiple ways to one solution):
flipped = np.flip(image, axis=0)
plt.imshow(flipped)Next, let us concatenate the flipped image with the original one along the horizontal axis. We can use the concatenate function from Numpy for this:
combined = np.concatenate((image, flipped), axis=0)
print("Shape of the combined image:", combined.shape)
plt.imshow(combined)Finally, we can also rotate the original image by 90 degrees by moving the spatial axes around. This is done using the moveaxis function from Numpy:
rotated = np.moveaxis(image, 0, 1)
print("Shape after reshaping:", rotated.shape)
plt.imshow(rotated)Further¶
Image processing and computer vision is an entire field of research outside the scope of this introductory course. Common next steps are object detection, measurements, tracking etc. For an introduction to image processing you can check the course Image processing for beginners on GitHub.
Exercises¶
In these exercises, you will work on an image that shows a glacier, taken with a satellite, in the years 2021 and 2022. It is an RGB image, i.e. it has 3 channels (red, green and blue). The goal is to do a simple computational analysis of the image to answer the question whether the area of the glacier has changed.
More background information: https://
Load the following image into a numpy array, print its shape and display the image: https://
www .copernicus .eu /sites /default /files /inline -images /Picture6 _8 .jpg Remove the writing on the top by saving an image slice beginning from row 70 in a new variable. Display the slice to confirm that we have a good crop.
Next, we want to analyse the surface of the glacier. We consider this to be the white area in the image. All pixels that exceed a certain threshold shall be considered white. So create a mask in which all pixels contain
Truewhere the pixel value is greater than 200.Since we have an RGB image, there are 3 channels (red, green and blue), this also reflects in the shape of the mask created above. With
np.all()(another aggregating function) we can apply the mask to all 3 channels together to extract the white area. More precisely it means that we generate a 2D boolean image that is onlyTruewhere the values are above the threshold in all 3 channels. You can then show the result withplt.imshow().Hint: use
white = np.all(mask, axis=-1)(Optional) In many scientific contexts, it is crucial whether ice areas have a certain minimum size. To extract these, we can remove small, unconnected areas. We can use the function
skimage.morphology.opening()for this purpose (for details, see below). Again display the result.Hint: use
skimage.morphology.opening(white)Now split the extracted boolean snow image into a part showing 2021 and one for 2022, respectively (just create 2 crops along the middle and save them in variables).
Calculate the area of the glacier in 2021 and 2022 by counting the number of pixels that are
Truein the respective images. You can simply usenp.sum()for this. Print it and make a conclusion about the change in the glacier area.
Details on skimage.morphology.opening(): This function first removes a few pixels from the edge of all areas and then adds the same number of pixels to the result. The effect is that small areas are deleted because after the pixels have been removed there is no area left to which pixels could be added again. The result is a mask that only contains areas with a certain minimum size. Even more information you can find in the documentation
### YOUR CODE HERE