cv2.finiteMask
NaN Hunting With cv2.finiteMask
- src
- mask
- nparray
cv2.finiteMask is a small node that solves a very specific, very annoying problem: finding out which pixels of a float array are actual numbers. The pack's own description says it best - the result is 255 where every channel is finite and 0 wherever a NaN or Inf appears, and it's "the standard guard in front of a cv2 op that would propagate them".
Why you care: NaN is contagious. One division by zero in a disparity refinement, one uninitialised cell in an optical-flow field, one bad correspondence in a triangulated point cloud, and every average, min-max normalisation and filter downstream is poisoned. In a preview that shows up as a blank or all-black image with no explanation. In a 3D workflow it shows up as an entire mesh flying off to infinity.
Inputs and outputs
src is a float or double array of 1 to 4 channels. That's the documented requirement, and it matters: this is a data operation on a float array, not an image operation. In this pack the image-ish socket will also accept a ComfyUI IMAGE or MASK (converted to uint8, frame 0) - but uint8 has no NaN to find, so feeding a picture here is a no-op at best and a silent type lie at worst. Feed it NPARRAY output from something that produces floats: a disparity or depth map, a flow field, gradient and score arrays, an interpolated sparse map, or arithmetic on any of those.
There's also an optional mask input, and the pack's tooltip is honest about what it is: "output matrix of the same size as input of type CV_8UC1". It's cv2's output buffer, the C++ idiom where you can pre-allocate the destination. Leave it unconnected and cv2 allocates the mask for you.
The single output nparray is that mask: a CV_8U array, 255 where the source is finite, 0 where it isn't. Note the values - it's 255, not 1, so if you're counting valid pixels with a mean or a sum, divide by 255 rather than by the pixel count.
What you actually do with it
See the damage. Preview CV Array already excludes non-finite pixels from its value range and paints them in a marker colour the active colormap can't produce - pure red for the grey ramp and viridis - so that's the fastest visual check. finiteMask is the version you can wire: it turns "something is NaN, somewhere" into a mask you can count, threshold, or draw on.
Gate the fix. The natural partner in this pack is cv2_patchNaNs (its val parameter is the value written in place of every NaN found in the array). finiteMask tells you where; patchNaNs does the mopping up. Or keep the mask as validity information and exclude the bad pixels downstream with CV Filter Points By Mask.
The honest limit
It tells you whether a value is finite, not why. If your disparity map is 30% NaN, the mask confirms what you suspected and leaves the diagnosis to you: a matcher that found nothing in low-texture regions, a filter with a bad threshold, or arithmetic on two arrays whose shapes never actually matched. There's a reason depth-shaped work in ComfyUI is full of validity masks - estimators that produce depth natively hand one back with the map, and the classic stereo route has to reconstruct that validity by hand. This node is the reconstruction.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"
ComfyUI Manager: search "ComfyUI CV", install, restart. Needs Python ≥ 3.12 and a recent ComfyUI on the V3 node API, and the contrib wheel pinned above - finiteMask is a newer OpenCV entry point, so an older cv2 in the same environment is a real possibility to check before filing a bug.
Common issues
cv2 error about the input type. Your array isn't float/double, or it has more than 4 channels. Check with Inspect CV Data; CV Cast Array converts, and it can convert with scaling, which is what you want going up to float and not what you want going down to uint8.
Mask comes back all-zero for everything. The array really is non-finite throughout - usually the result of arithmetic on mismatched shapes, or a division by an all-zero array. Look one step upstream rather than at this node.
Nothing appears to change downstream. Remember the mask doesn't modify your data; it describes it. If you wanted the NaNs gone, that's cv2_patchNaNs. If you wanted them masked out, that's a mask-consuming node.
Contrib submodules evaporated. A non-contrib OpenCV wheel installed over the contrib build empties the contrib submodules because all the wheels share one site-packages/cv2. The pack's tools/repair_opencv_contrib.py --check diagnoses it; --apply repairs.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| src | NPARRAY,IMAGE,MASK | Input matrix, should contain float or double elements of 1 to 4 channels Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size. | |
| maskopt | NPARRAY,IMAGE,MASK | Output matrix of the same size as input of type CV_8UC1 Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| nparray | NPARRAY | — |