Image Morphology
Erode, dilate, and the shape operators you'll actually use
- image
- IMAGE
Morphology is one of those things that sounds academic and turns out to be three lines of intent: grow the bright stuff, shrink the bright stuff, or subtract one from the other to get the edges. It's a 1960s image-processing primitive, it's fully deterministic, and in ComfyUI it's most of the way to being a mask tool without being one.
Image Morphology is a batch-friendly, GPU version of it. If you've used core Apply Morphology, this is the same operation with the same pixels - the difference is that it's separable and chunked, so a kernel of 101 or a 200-frame video batch doesn't blow up your VRAM or fall over mid-run.
The operations, and what they're for
All seven are built from two guards: dilate takes the brightest pixel under the kernel (bright areas grow), erode takes the darkest (bright areas shrink). Everything else is a composition of those two.
erode- shrink bright areas. Thins white regions, separates shapes that are touching.dilate- grow bright areas. Thickens white regions, bridges small gaps.open- erode then dilate. Kills bright specks smaller than the kernel while leaving big shapes roughly the size they were. The "despeckle" of the family.close- dilate then erode. Fills dark pinholes and pockmarks inside bright areas.gradient- dilate minus erode. Leaves just the boundaries: a clean, parameter-controlled edge map, no smoothing, no threshold guesswork.top_hat- the input minus its own opening. Keeps bright detail smaller than the kernel and throws away everything else. This is the detail extractor, and it's the one people don't know they want.bottom_hat- the closing minus the input. The same idea for dark detail: thin dark lines, cracks, scratches.
kernel_size is the side of a square kernel in pixels, default 3, and it goes up to 999. Small kernels touch individual pixels; 15 or 25 reaches across small shapes. It's an integer widget, so even values are legal, and the padding accounts for them correctly - but odd sizes centre cleanly and are easier to reason about.
How it works under the hood
Dilation is a max filter, erosion is a min filter, and both are separable: a 2D max over a 21×21 window is the same as a 1D max along every row of the padding, then a 1D max down every column. Two passes of cost proportional to the kernel side instead of kernel-side-squared. That's why a 999-pixel kernel returns in milliseconds here and why the naive version backfires on big kernels or long batches. The node runs the whole thing in chunks of sixteen frames at a time on ComfyUI's torch device and hands the results back on the intermediate device, so a 200-frame batch costs memory proportional to sixteen frames rather than all of them.
Two mechanics worth knowing because they show up in the output:
- Alpha is just another channel. The node works on every channel separately, so on an RGBA image
erodealso erodes the alpha - which softens the silhouette's edge. Sometimes that's what you want. If it isn't, split the alpha first or work on RGB and re-attach. - The overall image gets brighter or darker and that's correct. Erosion on greyscale is a min operation; across a whole frame it darkens. Dilation brightens. If you're using these to clean up a mask, remember you're also moving the average exposure of whatever you pass through.
Wiring it in
image in, IMAGE out at the same size it went in. That's the entire node - no mask ports, so if what you have is a MASK, convert it to an image first (the pack has mask-side erode/dilate nodes for that half of the job; this one is an image node). Drop it anywhere in a post-processing chain: after a blur, after a quantize, before a save.
Where it earns its place in a real workflow, honestly:
- Edge maps for a second pass.
gradientat kernel 3 is a crisp, controllable line mask for a depth-ish or lineart-ish conditioning. Because it's a difference of two min/max operations rather than a convolution, it doesn't chirp or ring the way an edge kernel can. - Mask cleanup.
openon a mask-shaped image removes stray single-pixel noise from a thresholding step;closerepairs the pinholes a bad segmentation left behind. Both are instant, which matters when you're doing this inside a graph and not in an image editor. - Detail passes.
top_hatgives you the texture layer of an image - grain, pore-level detail, fabric weave - which is what you want to screen back over a softened base. - Posterisation prep. Quantize a frame and you get flat bands with ragged boundaries; a
closeat kernel 3 tidies those stair-steps before a nearest-neighbour upscale.
Installing it
ComfyUI Manager → search WAS Node Suite v3 → Install → restart. Manually:
cd ComfyUI/custom_nodes
git clone https://github.com/WASasquatch/was-node-suite-comfyui.git
ComfyUI 0.14.0+ and Python 3.10+. Nothing else installs: v3's default requirements are empty, pip is never invoked, no weights are fetched. It used to be the opposite - v2 pulled in roughly twenty packages and ran its filters through OpenCV, which is where the "WAS Suite broke my install" folklore and the opencv downgrade rituals came from. In v3 those operations run on torch on ComfyUI's device.
The first start after install takes a second longer while config.yaml, the state database and the wildcard/LUT folders are written under <ComfyUI user dir>/was-node-suite/, and the pack compiles to bytecode. Pulls recompile once; that's the only cost of updating.
Nothing here really fails
There's no model, no file path and no randomness here, so there's little to break. The one surprise: a big kernel doesn't do a bit more, it does a lot more. top_hat at kernel 999 on a photograph hands back a nearly black frame, because almost nothing in it is small enough to survive the opening. Start at 3 and step up by 4. If a run is slower than you expect, that's the batch length - the chunking bounds memory, not time.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | The frames to reshape. Every channel is worked on separately. | |
| operation | COMBO | `erode` shrinks bright areas, `dilate` grows them, `open` removes bright specks, `close` fills dark gaps, `gradient` keeps edges, `top_hat` keeps bright detail smaller than the kernel, `bottom_hat` keeps dark detail smaller than it. | |
| kernel_size | INT | 33–999 | Side of the square kernel in pixels. `3` touches single pixels, `15` reaches across small shapes. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | The frames after the operation, the size they went in at. |