Index Ai Academy
Phase 0 · ToolsWorking with data

Project: images are arrays

About 45 minLooking ahead: some prerequisites aren't done yet

Why this matters

Every image a vision model sees is an array like the ones you'll build here, and every image tool you've used (filters, crops, thumbnails) is array math underneath. This project makes NumPy concrete: you'll see slicing, broadcasting and masks as pictures. It also gives you a handful of functions you'll actually use, from thumbnails to watermarks to color palettes.

  • Treat a picture as a (height, width, 3) array of numbers from 0 to 255
  • Draw shapes with boolean masks, then flip, crop and turn an image gray with slicing and math
  • Brighten an image without numbers wrapping around past 255
  • Use Pillow to add text and save a file, and find an image's most common colors

The brief

The practice shop wants product shots processed before they go on the website: turned gray for a print catalog, brightened, cropped square for thumbnails, stamped with a watermark, and summed up as a color palette. You'll draw a test image in code, then write one function for each job.

A picture is a grid of numbers

A color image is a 3-D array with the shape (height, width, 3). Each pixel holds three numbers: how much red, green and blue light it gives off, from 0 (none) to 255 (full). The type is uint8, an "unsigned 8-bit integer", which holds exactly the whole numbers 0 to 255.

import matplotlib.pyplot as plt
import numpy as np

tiny = np.array([
    [[255, 0, 0], [0, 255, 0], [0, 0, 255]],
    [[255, 255, 255], [128, 128, 128], [0, 0, 0]],
], dtype=np.uint8)

print(tiny.shape, tiny.dtype)
print(tiny[0, 2])          # row 0, column 2: the blue pixel

fig, ax = plt.subplots(figsize=(3, 2))
ax.imshow(tiny)
ax.axis("off")

Row 0 is the top of the picture. Red, green and blue at full strength make white; all three at 0 make black; equal amounts in between make gray. imshow draws an array as an image, and axis("off") hides the tick marks, which mean nothing for a photo.

Drawing a scene with masks

To draw a shape, make a mask: a True/False array with one value per pixel, True inside the shape. Assigning to img[mask] colors every True pixel at once, with no loop over pixels at all. The trick for building masks is a column of row numbers and a row of column numbers. Broadcasting compares them into a full grid:

import matplotlib.pyplot as plt
import numpy as np

def make_scene(h=120, w=160):
    img = np.zeros((h, w, 3), dtype=np.uint8)
    rows = np.arange(h)[:, None]            # shape (h, 1): 0, 1, 2 ... down the side
    cols = np.arange(w)[None, :]            # shape (1, w): 0, 1, 2 ... along the top
    img[:, :] = (150, 200, 235)             # sky everywhere
    img[h * 2 // 3:, :] = (70, 130, 60)     # grass on the bottom third
    sun = (rows - 25) ** 2 + (cols - 130) ** 2 <= 12 ** 2
    img[sun] = (250, 215, 80)
    tent = (rows >= 30) & (rows <= 100) & (abs(cols - 70) <= (rows - 30) * 0.8)
    img[tent] = (230, 120, 40)
    return img

scene = make_scene()
print(scene.shape)
fig, ax = plt.subplots(figsize=(4, 3))
ax.imshow(scene)
ax.axis("off")

The sun is every pixel within 12 of a center point, the formula for a circle. The tent is a triangle: the further down a row is, the wider the band of columns it covers. Both masks have the shape (120, 160), one value per pixel, so img[sun] picks exactly the pixels to paint.