NumPy integration#
The Toolbox is built on top of NumPy, and all Image instances are designed to
be fully compatible with NumPy operations.
Accessing the pixels as a NumPy array#
The image pixels are stored in a 2D or 3D NumPy array within the Image object. The dimensions of this array are:
2D for a single-channel (greyscale) image,
3D for a multi-channel (color) image.
You can access this array by the array attribute:
arr = img.array
It is important to note that img.size is different to arr.shape. For an image
that is W pixels wide and H pixels high, with C channels:
the
shapeis the dimensions of the underlying array (H,W) or (H,W,C).the
sizeis the number of pixels (W, H) in the image. The number of channels (C) is not included in the size but is found bynplaneswhich will be 1 for a single-channel image even though the underlying array has no third dimension.
Warning
The array attribute returns a view of the underlying pixel data.
Modifying this array will modify the image and if the array dimensions were changed
it may invalidate some of the image image metadata.
We can slice the image using the same syntax as a NumPy array:
img[10:20, 30:40]
which is the region of pixels in columns [10,20) and rows [30,40) represented by a Image object.
Using NumPy this would be:
arr[30:40, 10:20]
and the result would be a NumPy array.
Important
The Toolbox is very consistent about always putting the horizontal coordinate or dimension, before the vertical one. This is the opposite of the usual convention for NumPy arrays, but it is more consistent with the way we think about images and how they are displayed.
The Toolbox doesn’t directly support accessing individual pixels by indexing, but we can access the underlying array and index into it:
img.array[v, u]
is the pixel value at column u and row v. The real power of the Toolbox comes from applying fast vectorized functions to whole images.
Using the NumPy ufunc protocol#
The Toolbox implements the NumPy ufunc protocol, which allows it to seamlessly integrate
with NumPy’s universal functions (ufuncs). When we apply a ufunc to an Image, the Toolbox will automatically
convert the image to its underlying NumPy array, apply the ufunc, and then convert the
result back into an Image if the output is an array.
Consider the following example:
img.mean()
which returns the mean value of the image. This is a method of the Image class that
computes the mean pixel value using NumPy internally. An alternative way to compute the mean using NumPy explicitly would be:
np.mean(img.array)
However, we can also do the operation using NumPy’s ufuncs:
np.mean(img)
which will invoke the ufunc protocol:
converting Image to ndarray on the way in, applying the ufunc, and converting any 2D or 3D NumPy arrays in the result back to Image.
While a number of common operations such as mean, median, min, max, std, var, etc. are implemented
as methods of the Image class, the ufunc protocol allows us to use any NumPy ufunc
directly on the image, for example:
np.ceil(img) # computes the ceiling of each pixel value
np.arctan2(img1, img2) # computes arctan(img1/img2) elementwise
np.hypot(img1, img2) # computes sqrt(img1**2 + img2**2) elementwise
Note
Only the
"__call__"ufunc mode is supported. Reduction-style ufunc methods such asreduceoraccumulateare delegated back to NumPy.The NumPy
out=argument is intentionally not supported. Images are treated as immutable values, so in-place ufunc writes are rejected.
Example:
>>> from machinevisionToolbox import Image
!! ModuleNotFoundError: No module named 'machinevisionToolbox' [ERR unknown:95:unknown (source/numpy.rst)]
>>> import numpy as np
>>> img = Image([[1.2, 2.8], [3.1, 4.9]], dtype='float32')
!! NameError: name 'Image' is not defined [ERR unknown:95:unknown (source/numpy.rst)]
>>> np.ceil(img).array
!! NameError: name 'img' is not defined [ERR unknown:95:unknown (source/numpy.rst)]
>>> a = Image([[0.0, 1.0], [2.0, 3.0]], dtype='float32')
!! NameError: name 'Image' is not defined [ERR unknown:95:unknown (source/numpy.rst)]
>>> b = Image([[1.0, 1.0], [1.0, 1.0]], dtype='float32')
!! NameError: name 'Image' is not defined [ERR unknown:95:unknown (source/numpy.rst)]
>>> np.arctan2(a, b).array
!! NameError: name 'a' is not defined [ERR unknown:95:unknown (source/numpy.rst)]
To clarify, the mode of operation, consider the following two different ways of adding two images.
1>>> im1 = Image([[1, 2], [3, 4]])
2>>> im2 = Image([[5, 6], [7, 8]])
3>>> im3 = np.add(im1, im2)
4>>> im3 = im1 + im2
Line 3 uses NumPy ufunc dispatch, so it comes to this method which unwraps
both Image instances to ndarrays, calls NumPy’s add ufunc, then wraps the ndarray result
back into an Image.
Line 4 uses Python operator dispatch which are handled by the Toolbox. These
perform scalar and plane broadcasting, but they do not support arbitrary ufuncs
such as np.ceil or np.arctan2. If we want to use those ufuncs, we
must call them directly on the images, which will invoke this method.