CV Sample Array At Points
Look up this map where the features are
- nparray
- points
- valid_in
- values
- valid
- count
Every geometric pipeline in this pack eventually hits the same step: you have a dense per-pixel map and a small set of interesting pixel positions, and you want the map's values at those positions. That's this node. It sounds trivial and it's the hinge five other nodes turn on.
What it takes
nparray is the HxW or HxWxC array to sample - an XYZ map, a depth map, an optical-flow field, a confidence map, a colour image - any dtype, and it doesn't care what's in it.
points is Nx2 or Nx1x2 pixel coordinates, and sub-pixel positions are allowed. Typically these come straight from CV Match Features (points_a / points_b), but any point source works: keypoints, cluster centroids, annotation anchors. An empty or None point set gives you an empty result rather than an error, which matters because "no features found" is a normal outcome, not a failure.
interpolation is the choice that decides whether you get honest data:
nearesttakes the exact pixel value. It never invents data, which is the right answer for XYZ maps, label maps and disparity maps where a neighbour might be invalid or from a different surface.bilinearweights a sub-pixel neighbourhood. Smoother, and the right call for genuinely smooth fields - but bad across depth edges, where it'll cheerfully average the near wall and the far wall into a point halfway between them.
The validity machinery, which is the real design
valid is an Nx1 uint8 mask, 255 where the sample counted. Points outside the array get clamped to the edge and flagged 0, so an out-of-frame keypoint never raises. validity decides what "counts": in bounds and finite also clears the flag where the sampled value is NaN or ±Inf - and that's aimed squarely at cv2.reprojectImageTo3D, which marks missing disparity exactly that way. In a stereo pipeline this is the difference between "this match has no depth" and a 3D point at infinity silently entering your cloud.
valid_in takes a mask from an earlier sampler and ANDs it in. That's how you chain several validity tests - inside-the-frame here, finite-depth there, inside-a-foreground-mask somewhere else - and it's specifically implemented to survive an empty point set, which cv2.bitwise_and does not (cv2 returns None for empty input). Small thing; it's the difference between a batch job finishing and a batch job dying on one blank frame.
count is the number of valid samples, as an INT you can wire into a branch.
Output values is NxC (C = 1 for a 2D input), same dtype as the input for nearest, float for bilinear.
The canonical chain
This is the node that turns feature matches into 3D. Match features between two stereo pairs' left views, sample each pair's reprojected XYZ map at those points, and now you have two point clouds with known correspondences - which is exactly what CV Register Point Clouds (3D) wants. Same node, same trick, for reading depth at the match positions instead of full 3D, or for checking whether an annotation anchor's depth says it's in front of the object.
The pack's workflows/exercise_stereo_multiview.json builds the whole stereo-to-cloud pipeline; workflows/exercise_stereo_pointcloud.json covers the single-pair version; and workflows/42_rapid_model_tracking.json uses it to sample a rendered depth map at projected points. Sample the depth map a node like CV Rasterize Mesh produces and inf means "the mesh isn't here" - which is precisely the answer you want when the question is occlusion.
Install
Part of ComfyUI CV (bmad4ever/comfyui_cv), GPL-3.0 fork of opencv-comfyui:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"
# restart ComfyUI
Manager users: search the pack title. Python ≥ 3.12 and a V3-node-API ComfyUI. The neighbour nodes that make this usable - CV Filter Points By Mask, CV Filter Points 3D By Mask - come from the same pack, and they're how you turn the valid mask into a actually-filtered point set rather than a flag you keep ignoring.
Two practical notes
Sample before you filter, or filter both. The classic silent bug: sample a map at 800 matched points, filter out the 300 invalid ones, then hand the 500 valid values to a node that's still holding the original 800-point label array. The pack is consistent about valid for exactly this reason; use it.
nearest on an [H,W,3] colour image is a colour pickup, and it's fine. People assume they need bilinear for "nicer" colours; for a match point landing on a feature you do not, because averaging neighbours there blends the background into your sample. Nearest is the default for a reason.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| nparray | NPARRAY | HxW or HxWxC array to sample (XYZ map, depth, flow, confidence, colour image...). Any dtype. | |
| points | NPARRAY | Sample positions in pixels, Nx2 or Nx1x2 (x, y) - e.g. 'CV Match Features' points_a/points_b. None/empty gives an empty result, not an error. | |
| interpolation | COMBO | nearest | nearest: exact pixel value, never invents data - the right choice for XYZ / label / disparity maps whose neighbours may be invalid. bilinear: sub-pixel weighted average, smoother but mixes in neighbours (good for smooth fields, bad across depth edges). |
| validityopt | COMBO | in bounds and finite | What 'valid' means. 'in bounds and finite' also clears the flag where the sampled value is NaN or Inf (cv2.reprojectImageTo3D marks missing disparity that way); 'in bounds only' flags every sample that landed inside the array, whatever its value. |
| valid_inopt | NPARRAY | Optional mask from an earlier sampler (one entry per point): its zeros are carried into this node's 'valid' output. Chain samplers through it to AND several validity tests together - it stays correct for an empty point set, which cv2.bitwise_and does not (cv2 returns None for empty input). |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| values | NPARRAY | NxC array of sampled values (C = 1 for a 2D input); same dtype as the input for 'nearest', float for 'bilinear'. |
| valid | NPARRAY | Nx1 uint8 mask, 255 where the point was inside the array (and finite, if require_finite). |
| count | INT | Number of valid samples. |