FindThreshold
Auto-Tune the Threshold That Gets You the Mask You Want
- src
- IMAGE
FindThreshold searches a range of threshold values until the thresholded image satisfies a condition you write, then applies that winning threshold at full resolution. It's a "find me the threshold that works" node - you describe the result you want, and it hunts for the setting that produces it.
Why you'd reach for it
Thresholding a grayscale image into black and white is the first step of half the CV tricks in ComfyUI: isolating a bright region, turning an edge map into a mask, prepping input for the pack's GrabCut nodes. The problem is that the "right" threshold varies per image, and hand-tuning it per frame is miserable. FindThreshold automates the hunt: instead of guessing that 128 is right, you say "I want a threshold where at least 50% of the pixels are black" and it goes and finds it.
Where this shines is anything that runs across many images - video frames, batches - where a fixed threshold that works on frame 1 silently breaks by frame 40. The condition gives you a target that stays true while the image changes.
How it works
For each candidate value in the range start_at..end_at, it applies OpenCV's threshold with your chosen thresh_type (BINARY, BINARY_INV, TRUNC, TOZERO, TOZERO_INV), evaluates your condition expression on the result, and stops at the first value that returns true. The search runs on a downscaled copy (downscale_factor, default 2) so it's fast; the winner is then re-applied at full resolution for the output. If nothing satisfies the condition, it falls back to end_at.
The condition box is evaluated with simpleeval (sandboxed, with a timeout). You get t (the thresholded image), plus cv, np, and m (math). The README's own examples:
cv.countNonZero(t) > 100 # more than 100 non-black pixels
(t.size - cv.countNonZero(t)) / t.size > .50 # more than 50% black pixels
The inputs that matter
- start_at / end_at - the search range (1–255). Note it searches in order, so if
end_at < start_atit runs the range in reverse. - thresh_type - the OpenCV threshold mode applied to each candidate.
- condition - your True/False expression; this is the actual specification of "the mask I want."
- downscale_factor - search-speed tradeoff; 1 searches at full res, higher is faster.
Output is a single IMAGE: the full-resolution threshold at the found value.
Install
This is one of the nodes that genuinely needs the pack's dependencies:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_bmad_nodes
pip install -r requirements.txt
then restart ComfyUI. Manager users search "comfyui_bmad_nodes". Without opencv-python the CV nodes fail to load.
Common issues
- "Nothing ever matches." The condition never comes true in the range, so it silently returns
end_at. Add aprint-style debug or loosen the condition. - Condition syntax errors. You're writing a Python expression with
tas the thresholded image. If the node throws, check you usedt(notimg) and thatcv/npare your only module prefixes. - Slow search. The downscale exists precisely because full-res thresholding of 255 candidates is slow; drop
downscale_factoronly when you're sure you need exact pixels.
It turns "which threshold?" into "this condition," and for batch work that's a genuinely better question to be asking.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| src | IMAGE | — | |
| start_at | INT | 11–255 | — |
| end_at | INT | 2551–255 | — |
| thresh_type | COMBO | BINARY | 5 options: BINARY, BINARY_INV, TRUNC, TOZERO, TOZERO_INV |
| downscale_factor | INT | 2 | — |
| condition | STRING | # Some expression that returns True or False | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | — |