Task 5 of 6

Opening and Closing: Order Is the Answer

Erosion on its own is a blunt instrument: it kills the specks and takes a rind off everything else. Dilation on its own is the same mistake in reverse. Run them back to back and the size change cancels while the repair survives — and which repair you get depends entirely on which one goes first.

Opening is erode then dilate. The erosion wipes anything thinner than the structuring element, the dilation grows the survivors back to size: small bright specks are gone for good and everything else ends up roughly where it started. Closing is dilate then erode: the dilation swallows small dark holes, the erosion pulls the outlines back in, so pinholes fill and the specks stay exactly where they were.

They are not inverses and they are not interchangeable. Opening removes; closing fills. Ask for one and write the other and you get precisely the opposite of what you wanted — which is the single most reliable way to lose an afternoon to morphology.

Both kernels are given below, so this task is about the plumbing: chain them, and chain them twice. Two erosions followed by two dilations is an opening with a radius-2 element — it clears out the 3×3 clumps that a single pass is too gentle to touch.

run them the other way round and you repair the other defect
Goal: build an opening, a closing and a two-pass opening from the given kernels, and report what each one changed with the exact labels the starter uses.

Requirements

Hint 1 — chaining kernels

A kernel's result is an ordinary 2D array, so it goes straight back into another kernel: await dilate(await erode(mask)) is the whole opening. Every pass is a separate launch, which is exactly how a real pipeline does it (and Pipelines & Textures shows how to keep the intermediate on the GPU).

Hint 2 — which is which

Read the name outwards. An opening opens gaps up: it must start by shrinking, so erosion goes first. A closing closes gaps: it starts by growing. If your "opening" is filling holes instead of clearing specks, you have written a closing.

Hint 3 — the difference kernel
const removed = gpu.createKernel(function (before, after) {
  if (before[this.thread.y][this.thread.x] > after[this.thread.y][this.thread.x]) return 1;
  return 0;
}, { output: [128, 128] });

Feed it (mask, opened) to see what the opening threw away, and (closed, mask) to see what the closing filled in.

Same idea elsewhere

Opening and closing are the standard pre-processing pair in OpenCV (MORPH_OPEN, MORPH_CLOSE) and in every medical- and satellite-imaging toolchain. On a GPU each is a fixed chain of launches with no readback in between — the ping-pong between two buffers that WebGPU and CUDA pipelines are built around.

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.