Task 6 of 6

Payoff: Clean the Mask, Count What Is Left

The whole module in one run. noisy is a mask straight off a threshold: sixteen solid rectangles and a confetti of stray one- and two-pixel specks. Open it once to clear the confetti, then count what survived.

Counting connected blobs sounds like it needs a real labelling algorithm — and in general it does. But every shape here is an axis-aligned rectangle, and a rectangle has exactly one top-left corner: a foreground pixel whose neighbour above and whose neighbour to the left are both background. So count corners and you have counted shapes, with a per-pixel predicate and a sum — the same map-then-reduce shape Reductions is built on.

Be straight about the caveat. A U-shaped blob has two top-left corners and this would count it twice. The trick is exact for this mask, not for all masks; real connected-component labelling is a different and much heavier algorithm.

One more border note. This mask has a clear frame — nothing touches the edge — so a clamped read of a missing neighbour lands on background either way and the count comes out exact. Had a shape run to the edge, the clamped read would have returned the shape itself and that corner would have gone uncounted: one more place where the border rule is a decision, not a detail.

Goal: open noisy once, write a corners kernel that marks each rectangle's top-left pixel, and log the blob count before and after the cleanup.

Requirements

Hint 1 — three conditions

A cell is a corner when all three hold: it is foreground, the cell above is not, and the cell to its left is not. Any one of them failing means 0 — which reads nicely as three early returns.

Hint 2 — clamping just the two you need

Only the low side can go out of bounds here, so two clamps are enough:

let up = this.thread.y - 1;
if (up < 0) up = 0;
let left = this.thread.x - 1;
if (left < 0) left = 0;
Hint 3 — the whole body
const y = this.thread.y;
const x = this.thread.x;
if (mask[y][x] < 0.5) return 0;
let up = y - 1;
if (up < 0) up = 0;
let left = x - 1;
if (left < 0) left = 0;
if (mask[up][x] > 0.5) return 0;
if (mask[y][left] > 0.5) return 0;
return 1;

Same idea elsewhere

Cleaning a mask and then reducing it to a handful of numbers is what a vision pipeline actually does — the mask is never the product. The corner predicate is a stencil in CUDA/ROCm terms and the sum is a standard reduction, so on any platform this is one filter pass feeding one reduction: precisely the two primitives this course keeps coming back to.

All tasks in Thresholding & Morphology

  1. One Number for the Whole Image
  2. Let the Histogram Pick the Number
  3. A Threshold Per Neighbourhood
  4. Erode and Dilate: the Sweep, With Min and Max
  5. Opening and Closing: Order Is the Answer
  6. Payoff: Clean the Mask, Count What Is Left

This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.