Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Reading NumPy array conventions for images

~10 min

Every Python example in this course reads an image as a NumPy array. Its shape and indexing get used wrong at least once if the convention was never stated plainly. This short section states the convention once, so every later chapter's Python lab can lean on it without re-explaining.

NumPy stores an image exactly the way this course already agreed to think about one: as a matrix, row number first. So when code asks for image.shape, it is really asking "how many rows, then how many columns." And "rows" means height, "columns" means width.

The mismatch that trips people up is this. When a person describes a photo out loud, width comes first: "a 1920-by-1080 photo." But when the computer reports that same photo's shape, height comes first instead. Neither habit is wrong. They are just two equally standard habits that happen to run in opposite orders. The fix is simply knowing which one you are looking at: a spoken "width by height," versus a printed .shape that reads "height, width."

A grayscale image loaded into NumPy is a 2D array with .shape == (H, W), height first, width second. That is exactly the row-then-column order Section 2 already fixed for matrix indexing. image[y, x] reads the pixel at row y, column x. image.shape reports (H, W), not (W, H). A color image adds a third axis, .shape == (H, W, 3), with the channel axis last. image[y, x, c] reads channel c \in \{0, 1, 2\} (red, green, blue, in that order, by the near-universal convention this course follows throughout).

Two consequences surprise almost everyone once, so both are worth stating plainly. First, image.shape[0] is the height, not the width, the opposite of how a photo's dimensions are usually spoken aloud. A "1920\times 1080 image" means 1920 wide and 1080 tall, but its NumPy shape is (1080, 1920). Second, slicing image[100:200, 50:150] crops rows 100–199 and columns 50–149, a 100\times 100 region, using that same row-first order.

Shape, indexing, and (row, col) together

This is a 4x6 grayscale array. Its .shape is (4, 6) -- 4 rows, 6 columns. Click the cell at image[1, 4] (row 1, column 4) and check the value the readout reports.

Loading interactive…

Worked example

Cropping with the right slice order

  1. 1

    An image img has .shape == (480, 640) (height 480, width 640). Write the slice that crops out the top-left 100\times 100 pixel square. Rows come first. "Top-left" means the smallest row indices and the smallest column indices.

  2. 2
    \texttt{img[0:100, 0:100]}

    rows 0–99 (the top 100 rows), columns 0–99 (the leftmost 100 columns): a 100\times 100 crop, matching img[0:100, 0:100].shape == (100, 100).

Loading interactive…

Worked example

Reading a shape tuple out loud

  1. 1

    The widget above has .shape == (2, 3). State its height and width. The first entry is always height, the second is always width.

  2. 2

    Height =2, width =3 -- two rows, three columns, matching the picture.

Loading interactive…

Where this shows up

Document processing and OCR preprocessing

A document scanner that crops out a signature block or a form field before reading it uses exactly this slicing convention. Get the row-and-column order backwards and the crop silently grabs the wrong rectangle: right size, wrong spot. That bug is easy to miss by eye on one test document. It usually only shows up once the same steps run on a document shaped differently than the one first tested.

Check your understanding (counts toward your progress)

For a grayscale NumPy image array with .shape == (480, 640), what is the height?

Correct! 🎉 Not quite.

Right on try — credit. Right — the answer had already been shown, so no credit this time. Next try is worth Answer shown after three tries — this one no longer earns credit. You chose to see the answer — this one earns no credit.

640 is the width -- the second entry of the shape tuple, not the first.

The product of the two numbers is the pixel count, not the height.

The shape tuple directly reports height first, width second -- it's fully determinable.

Why

NumPy reports an image's shape as (height, width), so the first entry, 480, is the height.

In image[y, x, c], what does the index c select?

Correct! 🎉 Not quite.

Right on try — credit. Right — the answer had already been shown, so no credit this time. Next try is worth Answer shown after three tries — this one no longer earns credit. You chose to see the answer — this one earns no credit.

Column is selected by x, the second index -- not c.

Row is selected by y, the first index -- not c.

The index c selects one SPECIFIC channel (a single number, 0, 1, or 2), not a count of all channels.

Why

By this course's convention, the third array index selects which color channel (red, green, or blue) to read.

Write the NumPy slice that crops the top-left 100\times 100 pixel square out of an image img with .shape == (480, 640).

Practice: Shape and slicing (3 problems · doesn’t affect your progress)

A loaded image's .shape attribute prints (300, 400, 3). Read off its height, width, and channel count.

Write the NumPy slice that crops the bottom-right 50\times 50 square out of a 200\times 200 grayscale image.

A colleague says: 'I resized my 600\times 400 photo (width by height) and now img.shape shows (400, 600, 3) -- is that a bug?' Explain why it isn't.

Feeling good about this section?

Take the section quiz

Every answer gets a full explanation at the end.

Graph settings

Calculator

Sign in to save highlights

Insert an equation

  • No matches.