NumPy as Grids: an Image is a Table of Numbers

Lesson 4 of 8 in the Pre-COSMOS series, 37 slides, on the one NumPy idea that powers camp vision: a 2D array is a grid, and a grayscale image is a grid of brightness numbers running from 0 for dark to 255 for bright. Using numpy as np, you build a grid with np.zeros((rows, cols), dtype=int), read its shape as (rows, cols), index it ROW-FIRST as img[row, col], and "draw" by setting a cell bright, as in img[1,2] = 9. You then find the brightest cell with img.max() and np.unravel_index(img.argmax(), img.shape). The two traps are the classic grid mistakes: reading [x, y] or img[col, row] instead of img[row, col], and the off-by-one IndexError from img[0,5] in a five-column grid, where the columns are numbered 0 to 4. There are five checks and a scaffolded your-turn Maze Rover Simulator build in which students draw a shape into a grid and report the brightest cell. NumPy is a library you install once with pip install numpy, not a piece of hardware. Every snippet was run on CPython 3.12 with numpy 2.3, and the outputs were copied verbatim.

Subject: Python · 63 slides · code lesson

Open the interactive version of this deck · Homework for this lesson

What this lesson covers

The lesson, slide by slide

1. NumPy as Grids: An Image is a Table of Numbers

Title

Pre-COSMOS · Lesson 4 of 8

A camera doesn't see a cat - it sees a grid of numbers. Today you build that grid, draw into it, and find its brightest spot, all in NumPy.

2. What you will be able to do

Objectives

Last time you met NumPy arrays as fast number-lists. Today they become grids - the shape every camp vision project starts from. By the end you can:

3. What survived from The Library Mindset: import, Docs & Small Modules?

Warm-up

Discussion prompt

Before we open NumPy as Grids: an Image is a Table of Numbers: without looking back, what was the main idea of The Library Mindset: import, Docs & Small Modules, and what could you do by the end of it that you could not do before?

Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.

Answer:

Pre-COSMOS Lesson 6 of 8 (38 slides): the meta-skill that nobody memorizes a library - you read an example or a doc and adapt it. A module is a toolbox you import; you reach its tools with the SAME dot you've used all camp (module.function).

4. NumPy is a library, not a robot part

Concept

Figure (svg): A terminal box showing the command pip install numpy, with an arrow to a small grid of cells, showing that installing the library gives you grids to work with.

One install gives you grids.

NumPy is a Python library - extra code you install once with pip install numpy. It is software, not a camera or a sensor.

We bring it in with one line and a short nickname, then use that nickname all day: import numpy as np.

5. Break it if you can: NumPy is a library, not a robot part

Counterexample

Discussion prompt

We bring it in with one line and a short nickname, then use that nickname all day: import numpy as np.

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

6. Today's roadmap

Concept

Four stops, each one a piece of the same grid:

Make a grid
np.zeros((rows, cols)).
Index it
Row first: img[row, col].
Draw
Set a cell bright: img[1,2]=9.
Find brightest
max() + unravel_index.

7. Which is which: Today's roadmap

Matching

Match the pairs

From Today's roadmap — match each one to what it actually does. The descriptions have been shuffled.

  • c1. Make a grid
  • c2. Index it
  • c3. Draw
  • c4. Find brightest
  • b1. np.zeros((rows, cols)).
  • b2. Row first: img[row, col].
  • b3. Set a cell bright: img[1,2]=9.
  • b4. max() + unravel_index.

Why: Make a grid, Index it, Draw, Find brightest are easy to tell apart while they are sitting next to their descriptions and much harder afterwards, which is what this checks.

8. A Grid is a Table of Numbers

Section

Section 1

9. What a 2D array is

Concept

A 2D array is just a table of numbers laid out in rows and columns - a grid. NumPy stores it as one tidy block so the whole grid is fast to work with.

grid (2D array) — A rectangle of numbers with rows (top to bottom) and columns (left to right). Every cell has an address: which row, which column.

10. By analogy: What a 2D array is

Analogy

Discussion prompt

Explain What a 2D array is by analogy to something with no Python in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.

Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.

Answer:

A 2D array is just a table of numbers laid out in rows and columns - a grid. NumPy stores it as one tidy block so the whole grid is fast to work with.

11. A grayscale image IS a grid

Intuition

Figure (svg): A small grid where each cell shows a brightness number, with a dark corner near 0 and a bright corner near 255, illustrating that an image is a grid of brightness values.

Each cell is one pixel's brightness.

A camera doesn't store a picture of a cat. It stores a grid of brightness numbers - one number per tiny square (a pixel).

In grayscale, 0 means dark (black) and 255 means bright (white). Everything in between is a shade of gray. The picture is the pattern of numbers.

12. Teach it back: A grayscale image IS a grid

Explain it

Discussion prompt

Explain A grayscale image IS a grid to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.

Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.

Answer:

A camera doesn't store a picture of a cat. It stores a grid of brightness numbers - one number per tiny square (a pixel).

13. Make a grid of zeros

Worked example

np.zeros((rows, cols), dtype=int) builds a grid filled with 0 - a totally dark image you can draw onto. Note the double parentheses: (4, 5) is one thing, the shape.

import numpy as np
img = np.zeros((4, 5), dtype=int)
print(img)

You get 4 rows and 5 columns, every cell a 0. dtype=int keeps them whole numbers (without it you'd get 0. floats).

linewhat it doesresult
import numpy as npload the library, nickname np(no output)
np.zeros((4, 5), dtype=int)build a 4-row, 5-col grid of 0the grid below
print(img)show the grid[[0 0 0 0 0] / [0 0 0 0 0] / [0 0 0 0 0] / [0 0 0 0 0]]

14. Fill in: what it does for Make a grid of zeros

Comparison

Comparison matrix

From Make a grid of zeros: refill the what it does column from what you know. The rest of the table is as it appeared.

linewhat it doesresult
import numpy as npload the library, nickname np(no output)
np.zeros((4, 5), dtype=int)build a 4-row, 5-col grid of 0the grid below
print(img)show the grid[[0 0 0 0 0] / [0 0 0 0 0] / [0 0 0 0 0] / [0 0 0 0 0]]

15. shape tells you the size

Concept

Every grid knows its own size. img.shape reports it as a pair (rows, cols) - rows first, always.

shape — A grid's size as (rows, cols). For our grid, shape is (4, 5): 4 rows, 5 columns. Rows come first in the pair.

16. Read the shape

Worked example

shape is data about the grid - read it with no parentheses, like an attribute.

import numpy as np
img = np.zeros((4, 5), dtype=int)
print(img.shape)

It prints (4, 5) - 4 rows, then 5 columns. Same order you typed into zeros.

expressionmeansvalue
img.shape(rows, cols)(4, 5)
img.shape[0]number of rows4
img.shape[1]number of columns5

17. What each one costs: Read the shape

Trade off

Comparison matrix

From Read the shape: every row here is a choice with a cost. Fill the means column, then say which row you would actually pick and what you give up for it.

expressionmeansvalue
img.shape(rows, cols)(4, 5)
img.shape[0]number of rows4
img.shape[1]number of columns5

18. Rebuild the recipe: How to set up any grid

Ranking

Put in order

These are the steps of How to set up any grid, scrambled. Put them back in order before the next slide shows you.

  1. Import the library: import numpy as np.
  2. Make the grid with a shape: np.zeros((rows, cols), dtype=int).
  3. Check the size with img.shape - it reads (rows, cols).
  4. Remember: rows first, then columns - in zeros, in shape, and (next) in indexing.

Why: This is the order the recipe itself gives. Recalling the sequence without the slide in front of you is the difference between recognising the method and being able to run it — most of what goes wrong in practice is a step done out of turn.

19. How to set up any grid

Pattern

Every camp vision project starts the exact same way:

  1. Import the library: import numpy as np.
  2. Make the grid with a shape: np.zeros((rows, cols), dtype=int).
  3. Check the size with img.shape - it reads (rows, cols).
  4. Remember: rows first, then columns - in zeros, in shape, and (next) in indexing.

20. Where does it stop working: How to set up any grid

Edge cases

Discussion prompt

How to set up any grid works on the cases you have just seen. Push it to the edge: what is the most degenerate input it still handles — empty, zero, one item, everything equal — and what is the first case where it stops being true? Name the case, not just "it breaks".

Hint: Try the smallest legal input, then the largest, then the one where two things collide. Methods are specified at their edges; the middle takes care of itself.

Answer:

Every camp vision project starts the exact same way:

21. Rule out three: Check: what does shape return?

Elimination

Eliminate the wrong options

img = np.zeros((4, 5), dtype=int) What does img.shape return?

3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.

  • A. (4, 5) - 4 rows, 5 columns
  • B. (5, 4) - 5 rows, 4 columns
  • C. 20 - the total number of cells
  • D. (0, 0) - the grid is all zeros

Survives elimination: A

Why: shape echoes the size you built, in (rows, cols) order: (4, 5) means 4 rows and 5 columns. Rows always come first in the pair.

22. Check: what does shape return?

Check

Picture the grid np.zeros((4, 5)) - 4 rows, 5 columns.

Check your understanding

img = np.zeros((4, 5), dtype=int)

What does img.shape return?

  • A. (4, 5) - 4 rows, 5 columns (correct)
  • B. (5, 4) - 5 rows, 4 columns
  • C. 20 - the total number of cells
  • D. (0, 0) - the grid is all zeros

Answer: A

Why: shape echoes the size you built, in (rows, cols) order: (4, 5) means 4 rows and 5 columns. Rows always come first in the pair.

Why B tempts people
Swaps the order. shape is (rows, cols), not (cols, rows) - you built 4 rows first, so 4 comes first.
Why C tempts people
That's the cell COUNT (img.size = 4*5 = 20), not the shape. shape gives the (rows, cols) layout instead.
Why D tempts people
shape reports the grid's dimensions, not the values inside it. The cells are 0, but the grid is still sized (4, 5).

23. Indexing: Row First

Section

Section 2

24. An address is (row, col)

Concept

To reach one cell you give its address: img[row, col]. The row comes first (which line down), then the column (which spot across).

Counting starts at 0. The top row is row 0; the leftmost column is column 0.

25. Row first: down, then across

Intuition

Figure (svg): A grid with rows labeled 0 to 3 down the left side and columns labeled 0 to 4 across the top, with the cell at row 1 column 2 highlighted to show img[1,2] goes down to row 1 then across to column 2.

Row 1 down, column 2 across.

Think of it like a street address: first you find the floor (the row), then the apartment along that floor (the column).

img[1, 2] means: go down to row 1, then across to column 2. That lands on one single cell.

26. Teach it back: Row first: down, then across

Explain it

Discussion prompt

Explain Row first: down, then across to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.

Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.

Answer:

Think of it like a street address: first you find the floor (the row), then the apartment along that floor (the column).

27. Set a cell: img[1, 2] = 9

Worked example

Drawing is just assigning to a cell. We set two cells bright and leave the rest dark.

import numpy as np
img = np.zeros((4, 5), dtype=int)
img[1, 2] = 9
img[2, 1] = 5
print(img)

The 9 lands at row 1, col 2; the 5 lands at row 2, col 1. Notice they are NOT in the same place - order matters.

after this linethe grid is
np.zeros((4, 5))[[0 0 0 0 0] / [0 0 0 0 0] / [0 0 0 0 0] / [0 0 0 0 0]]
img[1, 2] = 9[[0 0 0 0 0] / [0 0 9 0 0] / [0 0 0 0 0] / [0 0 0 0 0]]
img[2, 1] = 5[[0 0 0 0 0] / [0 0 9 0 0] / [0 5 0 0 0] / [0 0 0 0 0]]

28. Read a cell back

Worked example

Reading uses the same address. img[1, 2] gives back the 9 we stored; img[2, 1] gives the 5.

import numpy as np
img = np.zeros((4, 5), dtype=int)
img[1, 2] = 9
img[2, 1] = 5
print(img[1, 2])
print(img[2, 1])

Same address writes AND reads. The row index always comes first.

expressionrow, colvalue
img[1, 2]row 1, col 29
img[2, 1]row 2, col 15
img[0, 0]row 0, col 0 (still dark)0

29. Something is wrong here: swapping row and column

Anomaly

Predict first

A student writes this, and it looks reasonable:

You think in [x, y] (across, then down) like a math graph, so you write img[2, 1] to mean 'column 2, row 1'.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: You read the address as [col, row].

Always read the address as [row, col] - row first.

Why: You read the address as [col, row]. But NumPy reads it as [row, col] - so you actually land on row 2, col 1.

30. Trap: swapping row and column

Trap

The trap

You think in [x, y] (across, then down) like a math graph, so you write img[2, 1] to mean 'column 2, row 1'.

Write img[2, 1] hoping for the cell at row 1, col 2

Why: You read the address as [col, row]. But NumPy reads it as [row, col] - so you actually land on row 2, col 1.

You touch the wrong cell - the 5, not the 9

Why: img[2, 1] is row 2, col 1 (value 5). The cell you wanted, row 1 col 2 (value 9), needed img[1, 2].

The fix

Always read the address as [row, col] - row first.

Write img[1, 2] for row 1, column 2

Why: Down to row 1, then across to column 2. That is the cell holding 9.

Say it out loud: 'row one, column two'

Why: Naming row first every time kills the x/y habit. Grids are [row, col], not [x, y].

31. Break it on purpose: swapping row and column

Break the constraint

Discussion prompt

The rule this trap just fixed:

Down to row 1, then across to column 2. That is the cell holding 9.

Now break it on purpose. Build a case that violates it and follow the consequences until something visibly fails. Where does the failure first show up — and would you have noticed it if you had not been looking?

Hint: The dangerous rules are the ones whose violation still produces an answer. If yours fails loudly, try to find one that fails quietly.

Answer:

You read the address as [col, row]. But NumPy reads it as [row, col] - so you actually land on row 2, col 1.

32. Check: which cell?

Check

The grid has a 9 at row 1, col 2 and a 5 at row 2, col 1.

Check your understanding

img = np.zeros((4, 5), dtype=int)
img[1, 2] = 9
img[2, 1] = 5

What does img[1, 2] return?

  • A. 9 (correct)
  • B. 5
  • C. 0
  • D. (1, 2)

Answer: A

Why: img[1, 2] is row 1, column 2 - exactly where we stored the 9. Reading uses the same row-first address as writing.

Why B tempts people
5 lives at img[2, 1] (row 2, col 1). Reading [1, 2] as [col, row] is the swap trap - the address is [row, col].
Why C tempts people
0 would be an untouched cell. But row 1, col 2 is exactly the cell we set to 9, so it is not still dark.
Why D tempts people
img[1, 2] returns the VALUE at that address (9), not the address itself. The brackets look up the cell's contents.

33. Something is wrong here: going off the edge

Anomaly

Predict first

A student writes this, and it looks reasonable:

Your grid has 5 columns, so you reach for img[0, 5] expecting the 5th column.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: You counted columns as 1..5. But indexing starts at 0, so 5 columns are numbered 0, 1, 2, 3, 4 - there is no column 5.

The last valid index is count minus one.

Why: You counted columns as 1..5. But indexing starts at 0, so 5 columns are numbered 0, 1, 2, 3, 4 - there is no column 5.

34. Trap: going off the edge

Trap

The trap

Your grid has 5 columns, so you reach for img[0, 5] expecting the 5th column.

Write img[0, 5] on a 5-column grid

Why: You counted columns as 1..5. But indexing starts at 0, so 5 columns are numbered 0, 1, 2, 3, 4 - there is no column 5.

Crashes: IndexError: index 5 is out of bounds for axis 1 with size 5

Why: Axis 1 is the columns. 'size 5' means 5 columns (0..4); index 5 is one step past the last. Off-by-one.

The fix

The last valid index is count minus one.

Use img[0, 4] for the last column

Why: 5 columns -> valid indices 0, 1, 2, 3, 4. The rightmost is 4, not 5.

Check the range against img.shape

Why: shape is (4, 5): rows 0..3, columns 0..4. Stay inside count - 1 on each axis.

35. Which of these survive contact with NumPy as Grids: an Image is a Table of…?

Two truths and a lie

Sort into buckets

Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.

Holds up
We bring it in with one line and a short nickname, then use that nickname all day: import numpy as np.; Four stops, each one a piece of the same grid:; A 2D array is just a table of numbers laid out in rows and columns - a grid. NumPy stores it as one tidy block so the whole grid is fast to work with.
Breaks
You think in [x, y] (across, then down) like a math graph, so you write img[2, 1] to mean 'column 2, row 1'.; Your grid has 5 columns, so you reach for img[0, 5] expecting the 5th column.
sound
These are stated as this lesson states them — each one survives the edge cases NumPy as Grids: an Image is a Table of Numbers puts it through.
flawed
Each of these is lifted from a trap in this deck: reasonable-sounding, and wrong in a way that only shows up once you rely on it.

36. Answer it before you see the options: Check: the out-of-bounds error

Prediction

Predict first

img = np.zeros((4, 5), dtype=int) print(img[0, 5]) What happens?

Answer it in your own words, now, with nothing to choose from. The options are on the next slide — and picking the right one off a list is an easier skill than producing it.

Correct: IndexError: index 5 is out of bounds for axis 1 with size 5

Why: 5 columns are numbered 0..4, so column index 5 is one past the end. NumPy raises IndexError on axis 1 (the columns), which has size 5.

37. Check: the out-of-bounds error

Check

The grid is 4 rows by 5 columns.

Check your understanding

img = np.zeros((4, 5), dtype=int)
print(img[0, 5])

What happens?

  • A. IndexError: index 5 is out of bounds for axis 1 with size 5 (correct)
  • B. Prints 0 - that cell is dark
  • C. Prints the whole last column
  • D. Wraps around and prints img[0, 0]

Answer: A

Why: 5 columns are numbered 0..4, so column index 5 is one past the end. NumPy raises IndexError on axis 1 (the columns), which has size 5.

Why B tempts people
There is no cell at column 5 to be dark - the column does not exist. Valid columns are 0 through 4, so this raises an error.
Why C tempts people
Printing a column needs slice syntax like img[:, 4]. A single out-of-range index just errors, it does not grab a column.
Why D tempts people
NumPy does not wrap negative-or-over indices around like a clock. A too-big index is an IndexError, not img[0, 0].

38. Brightness: 0 Dark .. 255 Bright

Section

Section 3

39. The number IS the brightness

Concept

In a grayscale image, each cell's number is how bright that pixel is. 0 is fully dark (black); 255 is fully bright (white). Bigger number = brighter pixel.

pixel value — The brightness stored in one cell, 0..255. 0 = black, 255 = white, in-between = a gray. A bright spot is just a big number.

40. Term to definition: NumPy as Grids: an Image is a Table of Numbers

Matching

Match the pairs

Match each term to the definition this lesson gave it — not the one you would guess from the word.

  • t1. grid (2D array)
  • t2. shape
  • t3. pixel value
  • d1. A rectangle of numbers with rows (top to bottom) and columns (left to right). Every cell has an address: which row, which column.
  • d2. A grid's size as (rows, cols). For our grid, shape is (4, 5): 4 rows, 5 columns. Rows come first in the pair.
  • d3. The brightness stored in one cell, 0..255. 0 = black, 255 = white, in-between = a gray. A bright spot is just a big number.

Why: These are the working definitions of grid (2D array), shape, pixel value as NumPy as Grids: an Image is a Table of Numbers uses them. Pairing them correctly is the test of whether you could state each one with the slide switched off.

41. Drawing = turning cells bright

Intuition

Start with an all-0 grid (a black image). To draw a shape, you set the cells along it to a high number - they 'light up' against the dark background.

So a white dot on black is one cell set to 255 while its neighbors stay 0. Pictures are made by choosing which numbers go where.

42. By analogy: Drawing = turning cells bright

Analogy

Discussion prompt

Explain Drawing = turning cells bright by analogy to something with no Python in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.

Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.

Answer:

Start with an all-0 grid (a black image). To draw a shape, you set the cells along it to a high number - they 'light up' against the dark background.

43. Find the brightest value: max()

Worked example

img.max() scans the whole grid and returns the single largest number - the brightest pixel's value.

import numpy as np
img = np.zeros((4, 5), dtype=int)
img[1, 2] = 9
img[2, 1] = 5
print(img.max())

Of all the cells (mostly 0, plus a 9 and a 5), the biggest is 9.

cells presentthe largest isimg.max()
0 (many)not the max-
5 at (2, 1)not the max-
9 at (1, 2)the brightest9

44. Watch it run: Find the brightest value: max()

Pattern

Step through it

Step through Find the brightest value: max() one row at a time. What is driving the change, and what would the row after the last one be?

  1. Step 1: cells present is 0 (many)
  2. Step 2: cells present is 5 at (2, 1)
  3. Step 3: cells present is 9 at (1, 2)

45. max gives the value, not the place

Concept

img.max() tells you the brightest value (9) but not where it is. For the location we need one more tool.

img.argmax() gives the position - but as a single counted-from-the-top-left number (7 here). np.unravel_index turns that flat number into a (row, col) pair.

46. Break it if you can: max gives the value, not the place

Counterexample

Discussion prompt

img.max() tells you the brightest value (9) but not where it is. For the location we need one more tool.

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

Answer:

img.argmax() gives the position - but as a single counted-from-the-top-left number (7 here). np.unravel_index turns that flat number into a (row, col) pair.

47. Find WHERE: unravel_index

Worked example

np.unravel_index(img.argmax(), img.shape) returns the (row, col) of the brightest cell. Pass it the grid's shape so it knows the width.

import numpy as np
img = np.zeros((4, 5), dtype=int)
img[1, 2] = 9
img[2, 1] = 5
spot = np.unravel_index(img.argmax(), img.shape)
print(spot)

It prints (np.int64(1), np.int64(2)). Don't let np.int64 scare you - read it as plain (1, 2): row 1, column 2. (Use .item() if you want clean Python ints.)

stepvaluemeaning
img.argmax()7brightest cell, counted flat from top-left
np.unravel_index(7, (4, 5))(np.int64(1), np.int64(2))that flat spot as (row, col)
read as(1, 2)row 1, column 2 - matches where we put the 9

48. Fill in: meaning for Find WHERE: unravel_index

Comparison

Comparison matrix

From Find WHERE: unravel_index: refill the meaning column from what you know. The rest of the table is as it appeared.

stepvaluemeaning
img.argmax()7brightest cell, counted flat from top-left
np.unravel_index(7, (4, 5))(np.int64(1), np.int64(2))that flat spot as (row, col)
read as(1, 2)row 1, column 2 - matches where we put the 9

49. Answer it before you see the options: Check: find the brightest cell

Prediction

Predict first

To get BOTH the brightest value and its (row, col) location in a grid img, you use:

Answer it in your own words, now, with nothing to choose from. The options are on the next slide — and picking the right one off a list is an easier skill than producing it.

Correct: img.max() for the value, np.unravel_index(img.argmax(), img.shape) for the (row, col)

Why: img.max() gives the brightest value; img.argmax() gives a flat position, and np.unravel_index turns that plus img.shape into a (row, col) pair. Two tools, two jobs.

50. Check: find the brightest cell

Check

max gives the value; unravel_index(argmax, shape) gives the place.

Check your understanding

To get BOTH the brightest value and its (row, col) location in a grid img, you use:

  • A. img.max() for the value, np.unravel_index(img.argmax(), img.shape) for the (row, col) (correct)
  • B. img.max() - it returns the value and the location together
  • C. img.argmax() - it returns the (row, col) directly
  • D. img.shape - the brightest cell is always at the shape

Answer: A

Why: img.max() gives the brightest value; img.argmax() gives a flat position, and np.unravel_index turns that plus img.shape into a (row, col) pair. Two tools, two jobs.

Why B tempts people
max() returns only the value (a single number like 9), never the location. You still need argmax + unravel_index for where.
Why C tempts people
argmax() returns a FLAT index (like 7), not a (row, col). unravel_index is what converts that flat number using img.shape.
Why D tempts people
shape is the grid's size (rows, cols), not where the bright cell is. The brightest spot can be anywhere inside.

51. Rule out three: Check: dark or bright?

Elimination

Eliminate the wrong options

In a grayscale grid, one cell is 0 and another is 250. What do those mean?

3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.

  • A. 0 is dark (black); 250 is nearly fully bright (white)
  • B. 0 is bright; 250 is dark
  • C. 0 is an error; 250 is the only real pixel
  • D. Both are the same gray - the number is just a label

Survives elimination: A

Why: Brightness goes from 0 (fully dark/black) up to 255 (fully bright/white). 0 is dark and 250 is almost white - a bright pixel.

52. Check: dark or bright?

Check

Grayscale runs 0..255.

Check your understanding

In a grayscale grid, one cell is 0 and another is 250. What do those mean?

  • A. 0 is dark (black); 250 is nearly fully bright (white) (correct)
  • B. 0 is bright; 250 is dark
  • C. 0 is an error; 250 is the only real pixel
  • D. Both are the same gray - the number is just a label

Answer: A

Why: Brightness goes from 0 (fully dark/black) up to 255 (fully bright/white). 0 is dark and 250 is almost white - a bright pixel.

Why B tempts people
It is backwards. Larger numbers are BRIGHTER, so 250 is near-white and 0 is black, not the other way around.
Why C tempts people
0 is a perfectly valid pixel value meaning black. It is not an error - an all-zero grid is just a black image.
Why D tempts people
The number is the brightness itself, not an arbitrary label. 0 and 250 are very different shades: black vs near-white.

53. Your Turn: Maze Rover Simulator

Section

Section 4 · build it yourself

54. The build: draw a maze grid

Concept

Build a tiny Maze Rover map as a grid: dark floor (0) with a few bright cells marking the rover's trail. Then report the brightest cell. Type every line yourself, run after each line, and read the errors - don't erase them.

#do thistool you'll use
1make a 5x6 grid of zerosnp.zeros((rows, cols), dtype=int)
2draw a trail: set a few cells brightimg[row, col] = 255
3print the grid and its shapeprint(img), img.shape
4report the brightest cell + locationimg.max(), np.unravel_index(...)

55. Milestone 1 — make the map

Worked example

Your turn: make a grid with 5 rows and 6 columns, all zeros, and print its shape. Say out loud what shape you expect before you run it.

Hint: np.zeros takes the shape as (rows, cols) in double parentheses; read the size back with .shape.

import numpy as np
grid = np.zeros((5, 6), dtype=int)
print(grid.shape)
lineprints
np.zeros((5, 6), dtype=int)(a 5x6 grid of 0s)
print(grid.shape)(5, 6)

56. Milestone 2 — draw the trail

Worked example

Your turn: light up a few cells to 255 to draw the rover's path. Predict which cells will show 255 before you print.

Hint: each bright cell is an assignment grid[row, col] = 255 - row first, then column.

grid[1, 1] = 255
grid[1, 2] = 255
grid[2, 2] = 255
print(grid)
setlands at row, col
grid[1, 1] = 255row 1, col 1
grid[1, 2] = 255row 1, col 2
grid[2, 2] = 255row 2, col 2

57. Milestone 3 — find the brightest cell

Worked example

Your turn: report the brightest value and where it is. Predict the value first - what is the biggest number you put in?

Hint: grid.max() gives the value; np.unravel_index(grid.argmax(), grid.shape) gives the (row, col).

print(grid.max())
spot = np.unravel_index(grid.argmax(), grid.shape)
print(spot)
lineprintsread it as
print(grid.max())255brightest value
np.unravel_index(grid.argmax(), grid.shape)(np.int64(1), np.int64(1))(1, 1) - the FIRST 255 we set
print(spot)(np.int64(1), np.int64(1))row 1, column 1

58. What each one costs: Milestone 3 — find the brightest cell

Trade off

Comparison matrix

From Milestone 3 — find the brightest cell: every row here is a choice with a cost. Fill the prints column, then say which row you would actually pick and what you give up for it.

lineprintsread it as
print(grid.max())255brightest value
np.unravel_index(grid.argmax(), grid.shape)(np.int64(1), np.int64(1))(1, 1) - the FIRST 255 we set
print(spot)(np.int64(1), np.int64(1))row 1, column 1

59. Milestone 4 — full program

Worked example

Your turn: put it together - make the map, draw the trail, print it, and report the brightest cell. Predict the final two lines before running.

import numpy as np
grid = np.zeros((5, 6), dtype=int)
grid[1, 1] = 255
grid[1, 2] = 255
grid[2, 2] = 255
print(grid)
print("shape:", grid.shape)
print("brightest:", grid.max())
print("at:", np.unravel_index(grid.argmax(), grid.shape))
output lineprints
print(grid)5 rows; 255s at (1,1) (1,2) (2,2), rest 0
shape:shape: (5, 6)
brightest:brightest: 255
at:at: (np.int64(1), np.int64(1))

If yours prints brightest: 255 and at: (np.int64(1), np.int64(1)) - you just built and read an image as a grid of numbers.

60. Fill in: prints for Milestone 4 — full program

Comparison

Comparison matrix

From Milestone 4 — full program: refill the prints column from what you know. The rest of the table is as it appeared.

output lineprints
print(grid)5 rows; 255s at (1,1) (1,2) (2,2), rest 0
shape:shape: (5, 6)
brightest:brightest: 255
at:at: (np.int64(1), np.int64(1))

61. Show it off

Worked example

Explain your program out loud, line by line: which line makes the grid, which lines draw (set cells bright), and which line reads the size?

Point to the (np.int64(1), np.int64(1)) and say what it means: row 1, column 1 - the first cell you turned bright. Note it's the FIRST 255, since ties go to the earliest cell.

You can now beat both of today's traps: reading [row, col] (not [x, y]), and staying inside 0..count-1 on each axis.

62. Connect it up: NumPy as Grids: an Image is a Table of Numbers

Connect it up

Draw it

One page, no notation unless you need it: draw how these connect — A Grid is a Table of Numbers · Indexing: Row First · Brightness: 0 Dark .. 255 Bright · Your Turn: Maze Rover Simulator. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.

63. What you can do now

Recap

you want to...you write
make a dark gridnp.zeros((4, 5), dtype=int)
read the sizeimg.shape -> (4, 5)
reach one cellimg[row, col] (row first)
draw a bright cellimg[1, 2] = 255
find brightest + whereimg.max(), np.unravel_index(img.argmax(), img.shape)

Next time (Lesson 5): you'll work a whole row or column at once with slicing - img[0, :], img[:, 2] - to brighten lines, not just single cells.

Sources

  1. NumPy user guide - Indexing on ndarrays (basic indexing, row/col order)
  2. NumPy reference - numpy.unravel_index
  3. All snippets executed on CPython 3.12; output copied verbatim. Author verification run, 2026-06-24 (Pre-COSMOS Lesson 4 of 8). — numpy 2.3 installed; printed grids, shape, max, unravel_index coords, and the IndexError text copied verbatim into the tables.

Want this taught 1-on-1? Alexander tutors Python — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108