Person Data Filter
Detail the person you meant, not all four of them
- person_data
- images
- sam3_model
- filtered
- remaining
- filtered_masks
- remaining_masks
- preview
- filtered_count
- remaining_count
- report
Automatic detailing has a structural blind spot, and it's the one the knowledge base keeps coming back to: a detector hands you regions, and regions have no idea who they belong to. A crowd of four people becomes four independent re-render jobs, each masked face gets the same prompt, and - the classic failure - they converge on the same person. Any pack that wants to say "this LoRA for her, that prompt for him" has to first solve identity, and in FVMtools the answer is a data type called PERSON_DATA: per-slot masks, keyed by person, carrying which reference slot each face matched.
PersonDataFilter is the node that then splits that set up. You give it the selector's PERSON_DATA; you get back two independent sets - the ones that match your criteria and the ones that don't - both re-indexed as fresh reference slots, so either can be wired straight into a Person Detailer.
Gate, rank, cut
Three stages, in that order, and knowing them tells you why you got what you got.
Gate is the content filter. Either gender (female / male / any) or, if sam3_prompt is non-empty, a free SAM3 text prompt - and the prompt always wins. Gender is resolved by grounding SAM3's woman / man against each person's body mask; anything SAM3 can't label falls back to InsightFace's gender/age classifier, and those fallback labels are marked with a * in the report so you know which answer came from a weaker signal. sam3_threshold is the grounding confidence; min_overlap is the fraction of a grounded mask that has to fall inside a person's body mask before that person counts as a hit - the knob for "don't match her to the woman behind her".
Rank sorts the survivors. sort_by is area (body mask pixel count), horizontal (centroid X), vertical (centroid Y), depth, or index. order flips it: ascending is smallest, leftmost, topmost, farthest, first. Depth needs a depth_map connected back on the selector - without one, every person carries a neutral depth and the ranking quietly degenerates to detection order.
Cut keeps either the first count (top_n, where 0 means all survivors) or exactly the count-th (nth, 1-based). Everything else becomes remaining.
The input that makes it worth wiring
source. Set to all_detected, it works over every person the selector found, including on a workflow where you never connected a single reference image. That's the interesting part: the selector no longer has to know who you care about in advance. Find all the people, gate and rank them here, then decide. The other option, matched_refs, only considers slots that matched a reference image, which is what you want when your identity assignment is already correct and you're just pruning it.
Also worth setting once: invert, which swaps the two outputs. "Everything except the woman on the left" is a legitimate thing to want.
Outputs
filtered and remaining are the two PERSON_DATA outputs. filtered_masks and remaining_masks are the same split as plain masks, for anything that takes masks rather than PERSON_DATA. filtered_count and remaining_count are integers - good for a conditional branch ("if nobody passed, skip the detail pass"). preview draws kept persons in green and dropped ones in red with labels, and report narrates which person got which label and why.
The usual wiring is two Person Detailers: one per split, each with its own LoRA and prompt, and the second one fed the first one's image - a detailer only repaints the regions it was handed, so the first pass survives.
Install
ComfyUI Manager, search comfyui-fvmtools, or:
cd ComfyUI/custom_nodes
git clone https://github.com/ping1979ping/comfyui-FVMtools
pip install insightface>=0.7.3 onnxruntime-gpu>=1.17.0 opencv-python>=4.8.0
Restart ComfyUI; the node is under Add Node → FVM Tools/Face. InsightFace's buffalo_l models auto-download on first use if onnxruntime is present - the 174 MB face recognizer plus four smaller files, into models/insightface/models/buffalo_l/. BiSeNet (parsing_bisenet.pth, 53 MB) has to be fetched by hand into models/gfpgan/. The free-text gate and SAM3-based gender need a sam3_model - a SAM3_MODEL_CONFIG, which is the pack's LoadSAM3Model output.
Common issues
The gate did nothing and the report says so. Three warnings cover it: no images connected (gate skipped, rank and cut still applied), a free-text prompt with no sam3_model (gate skipped), or gender-only with no sam3_model (InsightFace fallback only). All three are printed rather than raised, so check the report.
remaining is full of everyone. Your count is 0 with top_n, which keeps all survivors. That's the documented reading of 0 - it's "all that pass the gate", not "none".
Nothing passes the gender gate. Raise sam3_threshold down rather than up if masks are being missed, and lower min_overlap if the grounded region overlaps the body only partially - a cropped figure or a seated person will fail a 0.15 overlap more often than you'd expect.
Sorting by depth doesn't sort. No depth_map on the selector. Every person falls back to the same neutral depth value.
Inputs (14)
| Name | Type | Default | Description |
|---|---|---|---|
| person_data | PERSON_DATA | PERSON_DATA from Person Selector SAM3 / Multi / Data Refiner | |
| source | COMBO | all_detected | all_detected: every person the selector found (per_face_masks). Works even with no reference image connected. matched_refs: only the slots that matched a reference image. |
| gender | COMBO | any | Content gate. 'any' disables it. Ignored when sam3_prompt is filled. |
| sort_by | COMBO | area | Ranking criterion: area - body mask pixel count horizontal - body centroid X vertical - body centroid Y depth - needs depth_map on the selector index - original detection / slot order |
| order | COMBO | descending | ascending = smallest / leftmost / topmost / farthest first. descending = largest / rightmost / bottommost / nearest first. |
| mode | COMBO | top_n | top_n: keep the first `count` after sorting (0 = keep all). nth: keep exactly the `count`-th one (1-based). |
| count | INT | 10–32 | top_n: how many persons to keep (0 = all that pass the gate). nth: which one to keep, 1-based. |
| sam3_prompt | STRING | Free SAM3 text prompt, e.g. 'woman in red dress', 'child', 'person with backpack'. When non-empty it REPLACES the gender criterion. Empty = the gender widget applies. | |
| sam3_threshold | FLOAT | 0.300.05–1 | SAM3 grounding confidence threshold for the gate. |
| min_overlap | FLOAT | 0.150.01–1 | Minimum fraction of a grounded mask that must fall inside a person's body mask before that person counts as a hit. |
| imagesopt | IMAGE | The image(s) the person_data was built from. Needed for the gender / free-text gate and the preview. | |
| sam3_modelopt | SAM3_MODEL_CONFIG | SAM3 model from LoadSAM3Model. Needed for the free-text gate and SAM3-based gender. | |
| det_sizeopt | COMBO | 640 | Face detection resolution for the InsightFace gender fallback. |
| invertopt | BOOLEAN | false | Swap the two outputs — filtered becomes remaining and vice versa. |
Outputs (8)
| Name | Type | Description |
|---|---|---|
| filtered | PERSON_DATA | — |
| remaining | PERSON_DATA | — |
| filtered_masks | MASK | — |
| remaining_masks | MASK | — |
| preview | IMAGE | — |
| filtered_count | INT | — |
| remaining_count | INT | — |
| report | STRING | — |