Module 6 · Lesson 5
Capstone: Image Manipulation with NumPy
A grayscale image is nothing more than a 2-D array of brightness values
(0-255). Every “image processing” operation below is really just
array indexing, broadcasting, and boolean masking — the exact tools from
Modules 1-3, no special image library required.
Below we synthesize a small 40×40 image containing a bright square and a bright circle on a dark background, purely from array operations (no file is loaded, so this runs identically anywhere, including Colab).
rng = np.random.default_rng(seed=7)
size = 40
img = rng.integers(0, 40, size=(size, size)).astype(np.uint8) # dim background noise
# bright square: rows/cols 5..15
img[5:15, 5:15] = 220
# bright circle of radius 10 centered at (28, 28)
yy, xx = np.indices((size, size))
circle_mask = (yy - 28) ** 2 + (xx - 28) ** 2 <= 10 ** 2
img[circle_mask] = 200
plt.imshow(img, cmap="gray", vmin=0, vmax=255)
plt.title("Synthetic grayscale image")
plt.colorbar()
plt.show()
Output:
Every slice you write, like img[5:15, 5:15] above, is really Python
building a slice(5, 15) object per axis and handing a tuple of them to
img.__getitem__. Because that’s just an object, you can construct it
yourself, store it in a variable, and use it later — row_slice = slice(5, 15)
then img[row_slice] is identical to img[5:15]. For multiple axes, a
tuple of slices works the same way: (slice(5, 15), slice(0, 10)) is the
same index as img[5:15, 0:10]. The exercise below asks you to accept a
crop_slice built this way, so the caller can crop to whatever region it
wants.
# `a:b` syntax and an explicit slice object are the same thing.
row_slice = slice(5, 15)
print(np.array_equal(img[5:15, 5:15], img[row_slice, row_slice]))
# A tuple of slices works for multi-dimensional indexing too.
region = (slice(5, 15), slice(0, 10))
print(np.array_equal(img[region], img[5:15, 0:10]))
Output:
True
True
Threshold, crop, and measure brightness
Write analyze_image(img, threshold, crop_slice) that performs three
chained steps on a 2-D grayscale array img:
- Build a boolean
"mask"the same shape asimgwhere pixels are strictly greater thanthreshold. - Build a
"crop": the sub-arrayimg[crop_slice], wherecrop_sliceis a tuple of twosliceobjects (one per axis) — e.g.(slice(0, 10), slice(0, 10)). - Compute
"bright_fraction": the fraction (a Pythonfloatbetween 0 and 1) of pixels within that crop that are strictly greater thanthreshold.
Return a dict with exactly the keys "mask", "crop", and
"bright_fraction".
💡 Hint
mask = img > threshold. crop = img[crop_slice]. For the fraction, reuse the mask but computed on the crop (or slice the mask itself with crop_slice) and take .mean() of the boolean array — the mean of a boolean array IS the fraction of True values.
✅ Solution
def analyze_image(img, threshold, crop_slice):
mask = img > threshold
crop = img[crop_slice]
bright_fraction = float((crop > threshold).mean())
return {"mask": mask, "crop": crop, "bright_fraction": bright_fraction}