# One Number for the Whole Image

*Task 1 of 6 · [Thresholding & Morphology](https://gpu.rocks/learn/thresholding-and-morphology-670eaafa.md) · GPU.js Learn*

Everything downstream — counting, measuring, tracking — wants a
**binary mask**: one bit per pixel, foreground or background. The cheapest
way to make one is a **threshold**. Pick a number; call every pixel
brighter than it foreground.

Per pixel, no neighbours, no order, nothing shared: the friendliest shape a kernel
can have, and exactly the pure map "Thinking in Parallel" calls the easy case. One
thread, one pixel, one comparison.

It is also where real images bite back. `photo` is lit unevenly — bright
at the top-left corner, fading away to the bottom-right — with small bright marks
scattered over the whole frame. Run the starter and read the ASCII dump it prints: one
corner comes back solid, the opposite corner comes back empty, and only a diagonal band
across the middle finds the marks at all.

**Array layout in gpu.js**
Image data comes in row-major: `image[y][x]` is the pixel in row *y*,
column *x*, and each pixel is an `[r, g, b, a]` array with channels from
0 to 1. Mind the inversion that catches everyone — sizes are given width-first
(`output: [width, height]`), but indexing runs row-first, so this thread's own
pixel is `image[this.thread.y][this.thread.x]`. Swap those two and you read the
transpose of your image. Three-dimensional data follows the same rule:
`output: [w, h, d]` is indexed `[z][y][x]`.

## Figures

- **one number cannot serve both ends of a lit scene; a number per neighbourhood can**

## Goal

**Goal:** return a 128×128 mask — `1` where this pixel's
*luminance* is above `this.constants.t`, `0` everywhere
else.

## Requirements

- Read this thread's pixel: `photo[this.thread.y][this.thread.x]`
- Threshold the **luminance** `0.299r + 0.587g + 0.114b`, not a single channel
- Return exactly `1` or `0` — brighter than `this.constants.t` is foreground

## Hint 1 — luminance first, comparison second

Two steps, both of which you have written before: reduce the pixel to one
number, then compare that number. The image is warm-toned, so red and luminance are
genuinely different pictures — thresholding `pixel[0]` gives a mask that
is wrong by a couple of lighting bands.

## Hint 2 — returning a bit

A kernel returns a number, so the "bit" is the number `1` or the
number `0`:

```js
if (lum > this.constants.t) return 1;
return 0;
```

## Hint 3 — the whole body

```js
const p = photo[this.thread.y][this.thread.x];
const lum = 0.299 * p[0] + 0.587 * p[1] + 0.114 * p[2];
if (lum > this.constants.t) return 1;
return 0;
```

## Same idea elsewhere

A threshold is one `step()` in GLSL/WGSL, one predicated store in
CUDA, and a single fused op in every imaging library — it is so cheap that camera ISPs
do it in silicon. Which is exactly why the interesting question is never how to compare,
but what to compare against.

## Starter code

```js
// One thread, one pixel, one comparison. No neighbours needed.
const gpu = new GPU({ mode });

const threshold = gpu.createKernel(function (photo) {
  // TODO: reduce this thread's pixel to its luminance
  // (0.299 R + 0.587 G + 0.114 B), then return 1 when that is above
  // this.constants.t and 0 when it is not.
  return 0;
}, {
  output: [128, 128],
  constants: { t: 0.49 },
});

const mask = await threshold(photo);

// A look at the result: every 4th pixel, '#' where the mask says foreground.
for (let y = 0; y < 128; y += 4) {
  let line = '';
  for (let x = 0; x < 128; x += 4) line += mask[y][x] > 0.5 ? '#' : '.';
  console.log(line);
}

// The same story in numbers: two opposite corners of the frame.
let lit = 0;
let dark = 0;
for (let y = 0; y < 32; y++) {
  for (let x = 0; x < 32; x++) {
    lit += mask[y][x];
    dark += mask[y + 96][x + 96];
  }
}
console.log('top-left 32x32 foreground:', lit, 'of 1024');
console.log('bottom-right 32x32 foreground:', dark, 'of 1024');
```

---

Interactive version: https://gpu.rocks/learn/thresholding-and-morphology-670eaafa/1

[Next task](https://gpu.rocks/learn/thresholding-and-morphology-670eaafa/2.md)
