Nodes/ꘇ Genereti/ꘇ perception.events
ComfyUI Node

ꘇ perception.events

Turn 'a person appears' into a trigger

By languel·Created about a month ago·Updated 3 days ago· 1
ꘇ perception.events
    • events
    • event data
    ◄streambody►
    ◄max_age_ms500.00►
    ◄rules{"version":1,"rules":[{"name":"person.present","steps":[{"feature":"pose.count","op":">","value":0,"hold_ms":300}],"within_ms":1500,"cooldown_ms":800}]}►

    Watching a number is easy. Turning a condition on a live stream into a reliable event - one that fires once when something starts, once when it stops, doesn't chatter while the condition holds, and doesn't re-trigger on every frame - is the part people rebuild badly in every project. perception.events is that, as a node: you write rules as JSON, it evaluates them against a MediaPipe stream, and it emits per-rule state and event records.

    It's the event engine of ꘇ Genereti's Perception category (languel/genereti, by Liuboto, MIT, super-alpha), and it's browser-side like the rest of that family. MediaPipe here is the WASM runtime in your tab, not a Python dependency - the KB's MediaPipe panel covers the library's role generally, but this pack uses it for something else entirely: interaction, not generation.

    The rule contract

    rules is a multiline STRING, and the default is a working example you can read:

    {"version":1,"rules":[{"name":"person.present","steps":[
      {"feature":"pose.count","op":">","value":0,"hold_ms":300}
    ],"within_ms":1500,"cooldown_ms":800}]}
    

    Each rule has a name, a list of steps, and two timing budgets. A step is feature, op (>, <, >=, <=), value, and hold_ms - how long the condition must stay true before it counts. Steps run in order on fresh source frames, so multi-step rules are sequences, not conditions: the first must hold, then the second, and the whole thing must finish inside within_ms. A false condition interrupts the current hold.

    Entry fires once. Then the first condition must go false again before the rule re-arms, and cooldown_ms enforces a gap. That's the anti-chatter design, and it's why this node won't spam your graph with the same event every frame.

    A step can also be a distance rather than a feature comparison: "distance":["hands.0.thumb_tip","hands.0.index_tip"] measures between two point prefixes in image-height units, accounting for non-square aspect. Missing points never satisfy a condition, which is the behaviour you want - an absent hand isn't a nose touch.

    You can also express a rule value as distance:pointA,pointB in the visual editor. Import/export is versioned JSON and the plain widget is the portable advanced editor, so a rule set travels with the workflow.

    There is deliberately no eval, no application command, no automatic queue, and no assistant action in a rule. It's a condition engine, not a scripting one.

    Inputs and outputs

    stream (default body) names the global stream to evaluate. max_age_ms (500) is the staleness limit - stale input or a source change resets progress without generating a synthetic exit event, which is the right call and worth knowing. rules is the JSON above.

    Outputs: events (GENERETI_CHOP) with per-rule active state, a cumulative entry count, and a validity channel. event data (DAT) holds the last event batch and its source association.

    That DAT is where the node's one real footgun lives, and the author names it: the held DAT batch is not a trigger. If you test "is there a row in the DAT," you'll re-fire on every frame while the batch sits there. Use the count channel, comparing it to the last value you saw, or subscribe to the browser event. The engine also emits a named event once on entry and once on exit, which is what Livecode's __.events.on('person.present', ...) uses, and which is by far the cleanest way to react.

    What you'd build with it

    Demo 03 in the pack's series uses a pointer.y > 0.5 half-plane rule with a 100 ms hold to arm a brush; demo 04 builds a nose-touch gesture from a distance rule between pose.nose and hands.left.index_tip, with the honest caveat that the thresholds need calibrating on real hardware. The pattern generalises: presence, contact, region entry, "held still for a second."

    Install

    Manager → search "Genereti" → install ꘇ Genereti → restart ComfyUI → refresh the browser page. Manual:

    git clone https://github.com/languel/genereti.git custom_nodes/genereti
    ./.venv/bin/python -m pip install -r custom_nodes/genereti/requirements.txt
    

    Pinned requirement: comfyui-frontend-package>=1.53.6. Perception assets are a separate on-request download into models/genereti/browser-perception. ComfyUI 0.38.2+, Python 3.11+, modern browser.

    Gotchas

    Queue raises, loudly. Event rules evaluate browser Live streams; the node tells you they never run camera or inference in Queue. Rule-based automation is a Live-mode feature.

    You have to start evaluation. The ▶ control starts rule evaluation - creating the node or loading the workflow doesn't. Same policy as everything else in the pack.

    Hold durations are in milliseconds, and they're not the same as timeout. hold_ms is how long a condition must persist; within_ms is how long the whole sequence has to complete. Mixing them up produces rules that either fire instantly or never.

    Distance rules need landmarks that exist. hands.0 is a per-frame detection index, not a stable track - the docs are explicit that hand indices aren't tracking IDs and that handedness scores don't establish body-side identity. Calibrate before you trust a gesture.

    CategoryGenereti / Perception

    Inputs (3)

    NameTypeDefaultDescription
    streamSTRINGbody—
    max_age_msFLOAT500.0050–10000—
    rulesSTRING{"version":1,"rules":[{"name":"person.present","steps":[{"feature":"pose.count","op":">","value":0,"hold_ms":300}],"within_ms":1500,"cooldown_ms":800}]}—

    Outputs (2)

    NameTypeDescription
    eventsGENERETI_CHOP—
    event dataGENERETI_DAT—