ComfyUI Node

Filter Plurals

`arm up` or `arms up`, pick one before the model picks both

By alchemine·Created about a year ago·Updated 4 days ago· 2
Filter Plurals
    • processed_text
    • filtered_tags
    text

    Danbooru has both arm up and arms up. Both are real tags, both are trained, and they mean nearly the same thing - which is exactly why having both in one prompt is a problem. The model doesn't shrug and take one; it does its best to satisfy two pose tags at once, and you get the arm-that-isn't-quite-up-above-the-head look. Same story for hand on hip next to hands on hips.

    Filter Plurals keeps the first spelling and drops the later one. If you've got any prompt assembly in your graph - an LLM writing the tags, a {...|...} group, a tagger's output merged with your own - you'll hit this more often than you'd think, because nothing upstream knows the two spellings are the same idea.

    The mechanism is deliberately dumb

    For each tag, it strips a trailing s off any word of four letters or more, anywhere in the tag, and compares the resulting skeleton against ones it has already seen. First skeleton wins, and the tag that produced it stays.

    That crudeness is the point, and the failure modes it avoids are the interesting part. Words of three letters or fewer are left alone, so ass never collapses into as. And s isn't a plural marker anywhere except at the end of a word, so glasses becomes glasse while glass becomes glas - they don't collide. Ditto dress and dresses. The node is comparing two tags from the same prompt, and for that job, "does this look plural" beats a dictionary.

    It's also worth knowing what's out of scope: the same tag twice is not this node's business. arms up, arms up passes straight through. Outright duplicates are FilterSubtags' territory.

    Inputs, outputs

    text is an input socket, so you can't type into it directly - wire whatever produced the prompt into it, the pack's TextPrompt or any string source will do.

    processed_text is the prompt with the redundant spellings removed; that's the one you send to your positive CLIP Text Encode. filtered_tags is a comma-joined string of what got dropped. It's not decoration - plural filtering is a heuristic, and when a tag you wanted disappears, this output on a preview or note node tells you why in about two seconds.

    Weights and BREAK don't shield a tag. Both the weight notation and the emphasis brackets are stripped before comparison, so (arm up:1.2) still gets matched against arms up, and it keeps its weight when it survives. Groups separated by BREAK are treated as one picture, so the second spelling goes wherever you put it. Whitespace and line breaks in what's kept are preserved.

    Installing it

    ComfyUI Manager → search ComfyUI-Alchemine-Pack → install → restart. Manually:

    cd ComfyUI/custom_nodes
    git clone https://github.com/alchemine/comfyui-alchemine-pack
    cd comfyui-alchemine-pack && pip install -r requirements.txt
    

    One dependency (python-dotenv), no models, no config, and the nodes appear under AlcheminePack/Prompt. If you run the pack's combined ProcessTags node, this filter is already inside it and on by default - that's the checkbox that explains a prompt coming out shorter than you typed it.

    The traps

    Case is not normalised. Arm up and arms up don't collide, because the skeleton for the first is Arm up. Capitalise your tags consistently or this node will miss the exact pair it exists to catch - the comparison is one word of downstream string handling away from being fixed, and it isn't there.

    You can lose a tag you meant to keep. boots, boot are two different spellings of one thing for this node, so the later one goes. If you genuinely want both terms in the prompt for emphasis, put the plural elsewhere or don't run this node.

    The singular is spelled plainly. Word-splitting means only the trailing s per word comes off, so anything irregular - feet/foot, teeth/tooth, children/child - sails through untouched. Expect it to catch the common, boring case and nothing else.

    That's the whole node. It's the most narrowly scoped thing in the pack, and if you're building prompts programmatically, that's a feature.

    CategoryAlcheminePack/Prompt

    Inputs (1)

    NameTypeDefaultDescription
    textSTRING

    Outputs (2)

    NameTypeDescription
    processed_textSTRING
    filtered_tagsSTRING