Extensions/Lukutar Nodes
ComfyUI Extension

Lukutar Nodes

ComfyUI custom node pack: color matching, SEGS overlays, prompt annotation, workflow variables, group switching, seed history.

By Mistress-Lukutar·Created 2 months ago·Updated 12 days ago· 2
Mistress-Lukutar/ComfyUI-LukutarNodes
Nodes14
On cloudLocal install
CategoryLukutar/Prompt, Lukutar/Image
Stars2
Updated12 days ago
Readme

ComfyUI-LukutarNodes

A collection of custom nodes for ComfyUI by Mistress-Lukutar.

The pack is organized as a home for standalone image-processing nodes: each node lives in its own module under nodes/, backed by a pure numpy/OpenCV engine under core/ (no ComfyUI imports, unit-testable anywhere) and thin tensor-conversion helpers under utils/.

Nodes

  • Color Match (Frequency Split) — recolor a processed image from a reference (Lukutar/Image).
  • SEGS BBox Overlay — draw Impact Pack SEGS detections on an image (Lukutar/Image).
  • SEGS Set Crop Size — refit Impact Pack SEGS crop regions to an absolute target size, e.g. 512×512 (Lukutar/Image).
  • Prompt Annotate — inline |label: text| region markup in one prompt (Lukutar/Prompt).
  • Annotations to Wildcard (LAB) — annotations → Impact Pack [LAB] wildcard text (Lukutar/Prompt).
  • Annotation Segment — extract one label's prompt text (Lukutar/Prompt).
  • Annotation Labels — the label set as one comma-separated string (Lukutar/Prompt).
  • Annotation Segment Edit — pass-through per-label edits on ANNOTATIONS: prepend/append/remove text, new/delete segments (Lukutar/Prompt).
  • Set Variable / Get Variable — named workflow variables: publish a value once, read it anywhere without dragging wires (Lukutar/Variables).
  • Set Bundle / Get Bundle — a named bundle of several typed values (model + vae + clip + …): grow slots by wiring, read the whole bundle anywhere (Lukutar/Variables).
  • Group Switch — one boolean row per canvas group: switch whole groups on/off (bypass or mute) from a single panel node (Lukutar/Groups).
  • Seed History — a seed source with the built-in seed/control_after_generate widgets plus a rolling log of used seeds rendered as copyable text (Lukutar/Seeds).
  • Paste (Clipspace, Keep Mask) — context-menu item for image upload nodes: paste a clipspace image while keeping the node's painted mask (frontend extension, no node).

Color Match (Frequency Split)

Restores the color distribution of a reference image onto a processed image (for example an SD upscale) while keeping the processed image's detail.

The node splits the input into frequency layers with a Gaussian blur (sigma = cutoff frequency), transfers the color statistics of the reference onto the low-frequency layer, and recombines it with the untouched high-frequency detail of the input.

Typical use case. SD iterative upscaling drifts colors through repeated VAE encode/decode cycles. Feed the pre-drift image as reference and the upscaled result as image to bring the colors back without losing the upscaled detail.

Color transfer methods:

  • reinhard — transfers per-channel mean/std in LAB space from the reference low layer onto the input low layer. The default; preserves the input's tonal structure while adopting the reference's color envelope.
  • replace — swaps the input's low-frequency layer for the reference's verbatim. Stronger effect; useful when the drift is severe.

Auto-tune. With auto_tune enabled the node grid-searches sigma from sigma_min to sigma_max in sigma_step increments, scores each candidate with a fixed evaluation blur (eval_sigma) and reuses the winning sigma for the whole batch. The envelope metric compares blurred layers only (color/tonal accuracy, detail ignored); full compares raw images (color + structure). The winning sigma is returned as the sigma output so it can be wired into other nodes.

Inputs

| Name | Type | Default | Description | |------|------|---------|-------------| | image | IMAGE | — | Processed image to recolor (e.g. the upscale). | | reference | IMAGE | — | Image with the target color distribution (the original). | | auto_tune | BOOLEAN | manual | Grid-search sigma on the first frame and reuse the best value for the whole batch. | | sigma | FLOAT | 30.0 | Gaussian blur radius (frequency cutoff). Ignored when auto_tune is on. | | method | COMBO | reinhard | reinhard: transfer LAB mean/std. replace: use the reference low layer verbatim. | | sigma_min | FLOAT | 5.0 | Auto-tune grid start. | | sigma_max | FLOAT | 60.0 | Auto-tune grid end. | | sigma_step | FLOAT | 5.0 | Auto-tune grid step. | | eval_sigma | FLOAT | 20.0 | Fixed blur radius for auto-tune scoring; prevents degenerate minima at high sigma. | | metric | COMBO | envelope | envelope: color only. full: color + structure. |

Outputs

| Name | Type | Description | |------|------|-------------| | image | IMAGE | Recolored batch, same resolution as image. | | sigma | FLOAT | Sigma actually used (best grid hit when auto-tuning). |

Batch behaviour

  • reference with a single frame is broadcast to every frame of image.
  • Matching batch sizes are processed pairwise.
  • Mismatched larger batches raise an error on the node.
  • Auto-tune searches on the first frame pair only, then applies the winning sigma to the whole batch (consistent processing for video).

Progress reporting

The node drives the ComfyUI progress bar: per frame in manual mode, per sigma candidate plus per frame while auto-tuning. Every auto-tune candidate and its score are also echoed to the ComfyUI console log.

SEGS BBox Overlay

Draws Impact Pack SEGS detections on an image the way YOLO demos do: an outlined bounding box per segment with a filled caption plate (class name, optionally with the confidence percentage) and an optional semi-transparent tint of the segment masks.

Feed it the output of SEGM Detector (SEGS) (e.g. the bbox detector used by ADetailer) plus the image the detector ran on. The segs input is passed through unchanged, so the node can be inserted between the detector and a Detailer pipeline to preview exactly what will be repainted.

Colors. In auto mode each class gets a stable color from a built-in vivid palette (same class = same color, regardless of detection order). In single mode every detection is drawn with one user color (color_r/color_g/color_b, only used by that mode).

Size mismatch. When the image resolution differs from the one recorded in SEGS (e.g. the detection ran on an upscaled copy but you preview the original), all coordinates and masks are rescaled proportionally — automatically.

Captions use OpenCV's built-in Hershey font, so labels are limited to ASCII characters (detector class names like face or hand are fine).

Inputs

| Name | Type | Default | Description | |------|------|---------|-------------| | image | IMAGE | — | Image to draw on, e.g. the one the detector ran on. | | segs | SEGS | — | Segments from an Impact Pack detector (SEGM Detector (SEGS)). | | label_format | COMBO | label+confidence | label: class name only. label+confidence: append the score, e.g. face 91%. | | draw_masks | BOOLEAN | masks | Tint the segment masks (boxes only to disable). | | mask_alpha | FLOAT | 0.45 | Mask tint strength, 0–1. | | thickness | INT | 0 | Box border width in pixels; 0 = auto from the image height. | | font_scale | FLOAT | 1.0 | Multiplier on the auto caption text size. | | color_mode | COMBO | auto | auto: stable color per class from the palette. single: one user color. | | color_r / color_g / color_b | INT | 0 / 255 / 0 | Single color mode's RGB channels. |

Outputs

| Name | Type | Description | |------|------|-------------| | image | IMAGE | Annotated batch, same resolution as image. | | segs | SEGS | The input SEGS passed through unchanged. |

Batch behaviour

  • SEGS describe a single image, so the same detections are drawn on every frame of the batch (useful for previewing a video pass).

SEGS Set Crop Size

Refits every Impact Pack SEGS segment's crop region to an absolute target size, e.g. 512×512, instead of the detector's relative crop_factor.

Why: crop_factor scales with the detection, so the same factor yields 128×128 crops for small segments and 1024×1024 for large ones, and the actual crop sizes are often sampler-unfriendly (552×239). This node pins the crop size so every segment is processed at a resolution you choose.

The bbox, label, confidence and the mask content are untouched — only the crop rectangle (and the mask's alignment to it) changes, so the node can sit between a detector (e.g. SEGM Detector (SEGS)) and a Detailer pipeline. The added context area around the mask is zero-filled in the re-cut mask, exactly like Impact's own crop padding.

Modes.

  • exact — the crop region is exactly width×height, centered on the bbox. If the bbox itself is larger than the target, the region grows just enough to contain it (rounded up to a multiple of round_to) so a detection is never cut off; the size is then larger than requested and a warning is logged.
  • aspect — the bbox is scaled uniformly around its center so its longer side equals max(width, height), proportions kept and sizes rounded up to multiples of round_to (400×173 @ 512 → 512×224). Never scales below 1×, so the bbox always fits.

Clamping. Regions are centered on the bbox center and shifted to stay inside the image; a target larger than the image is clamped to the image size (with a warning).

Inputs

| Name | Type | Default | Description | |------|------|---------|-------------| | segs | SEGS | — | Segments from an Impact Pack detector (SEGM Detector (SEGS)). | | width | INT | 512 | Target crop width in pixels. | | height | INT | 512 | Target crop height in pixels. | | mode | COMBO | exact | exact: exactly width×height (grown only if the bbox is larger). aspect: uniform scale so the bbox's longer side equals max(width, height). | | round_to | INT | 8 | Round fitted sizes up to multiples of this (SD-friendly); applies to aspect-mode sizes and bbox-forced growth, the exact target is verbatim. |

Outputs

| Name | Type | Description | |------|------|-------------| | segs | SEGS | SEGS with refitted crop regions; bboxes and mask content unchanged. |

Prompt Annotate

Annotates one prompt with region labels for label-driven per-region workflows (e.g. classifier segments → Detailer inpainting). The prompt stays a single text that can still drive the base generation; only the added markup selects which parts belong to which region.

Markup. |label1,label2: text| tags mark text as belonging to label1 and label2:

masterpiece, |body:1girl, thin|, |face:blue eyes, smirk|, |body,hair:red hair|, |body:stands|, |background:outdoors, park|
  • Tags are flat, never nested — interleaved regions use repeated labels (body appears twice above); one tag may carry several labels.
  • Labels are free-form (letters, digits, _) and should match your classifier's label vocabulary; there is no fixed class list.
  • Text outside tags is the common part, implicitly labelled all.
  • The clean_prompt output strips all markup: masterpiece, 1girl, thin, blue eyes, smirk, red hair, stands, outdoors, park — feed it to the base generation.
  • Empty tags (|face:|) are dropped with their dangling separators.
  • | cannot appear in the prompt text itself; malformed tags fail the node with the offending position.

Web editor. With the web assets loaded (they ship with the pack), the node's prompt field itself becomes a rich input: the markup is highlighted live while typing — |label: parts dimmed, span text in a stable pastel color per label (dark-theme friendly). The field fills the node's widget area and follows node resizes; typing past the available height grows the node, but only up to a 400 px cap — past it the field stops growing and scrolls instead. The mouse wheel scrolls the field while it has somewhere to scroll; once the field is at its top/bottom edge (or has no overflow at all) the wheel zooms the canvas underneath. The built-in text widget is only hidden, so the value keeps serializing into the workflow normally. The Annotate... button opens a popup editor with the same live-highlighted input, larger, plus a palette of the labels already used in the text — click one to wrap the current selection. Standard ComfyUI text shortcuts work in both inputs: Ctrl+Enter queues the workflow (in the popup it saves the editor instead) and Ctrl+Up/Down adjusts attention weight around the caret. The node works without the web assets too — the markup is plain text and can be typed by hand.

Inputs

| Name | Type | Default | Description | |------|------|---------|-------------| | text | STRING | — | Prompt with \|label: text\| markup (multiline); labels are free-form, typed right into the markup. |

Outputs

| Name | Type | Description | |------|------|-------------| | annotations | ANNOTATIONS | Parsed spans; consumed by the nodes below. | | clean_prompt | STRING | The same prompt with all markup removed. |

Annotations to Wildcard (LAB)

Converts annotations into the label-mode wildcard consumed by Impact Pack Detailer (SEGS)-style wildcard inputs:

[LAB]
[ALL] masterpiece,
[body] 1girl, thin, red hair, stands
[face] blue eyes, smirk
[hair] red hair
[background] outdoors, park

The common part becomes the [ALL] line; multi-label spans are duplicated into each of their label lines. Impact Pack concatenates [ALL] and the matching label value with no separator, so the [ALL] line ends with a comma whenever label lines follow.

Inputs

| Name | Type | Description | |------|------|-------------| | annotations | ANNOTATIONS | Annotations from Prompt Annotate. |

Outputs

| Name | Type | Description | |------|------|-------------| | wildcard | STRING | [LAB]-format wildcard text for Detailer (SEGS). |

Annotation Segment

Extracts one label's prompt text, e.g. to drive a per-region inpaint directly. With include_common on, the unmarked common part is prepended (masterpiece, blue eyes, smirk for label=face above).

Inputs

| Name | Type | Default | Description | |------|------|---------|-------------| | annotations | ANNOTATIONS | — | Annotations from Prompt Annotate. | | label | STRING | face | Label to extract; unknown labels fail the node listing the available ones. | | include_common | BOOLEAN | common + label | Prepend the unmarked common part of the prompt. |

Outputs

| Name | Type | Description | |------|------|-------------| | text | STRING | Prompt text for the selected label. |

Annotation Labels

Renders the annotation's label set as one comma-separated string — body, face, hair, background for the example above — e.g. to feed a label picker or to log which regions a workflow covers. Labels keep first-appearance order and are deduplicated. The implicit all label of the unmarked common part is included only with include_common on; with no tagged regions and include_common off the output is empty.

Inputs

| Name | Type | Default | Description | |------|------|---------|-------------| | annotations | ANNOTATIONS | — | Annotations from Prompt Annotate. | | include_common | BOOLEAN | regions only | Include the implicit all label of the unmarked common part. |

Outputs

| Name | Type | Description | |------|------|-------------| | labels | STRING | Comma-separated label list, e.g. body, face, hair. |

Annotation Segment Edit

Pass-through editor for the annotations: takes ANNOTATIONS in and emits edited ANNOTATIONS out, so one shared Prompt Annotate can feed several workflow branches, each with its own tweaks (edits chain freely). The label field accepts several labels comma-separated (face, body) and every mode applies to all of them:

  • prepend — put the tags before each label's text (face + detailed eyes → detailed eyes, blue eyes, smirk);
  • append — put them after it (… → blue eyes, smirk, smile);
  • remove — delete the listed comma-separated tags from the labels' text, matched exactly after trimming (smirk → blue eyes);
  • new — append a fresh span |labels: text| to the end of the prompt with the typed labels and text (e.g. hands, weapon + delicate fingers → a new |hands,weapon: delicate fingers| tag); every listed label must not exist yet;
  • delete — remove the labels with their content entirely; the text field is unused (the web extension grays it out in this mode).

Notes on the semantics:

  • A label spread over several spans is edited at its first span (prepend) / last span (append); remove applies to all of them.
  • The all label edits the unmarked common part in place — the added text stays unmarked, no |all:| tag appears; deleting all removes the common text, keeping the separators between the surviving spans.
  • delete only strips the label from a multi-label span (|body,hair: red hair| − hair → |body: red hair|); a span left without any label disappears with its text.
  • A span shared by several labels holds one text, so prepend / append / remove through any of its labels change that shared text for all of them; a span emptied by remove disappears from the annotation (and its labels from Annotation Labels / the wildcard).
  • Removing tags that are not present changes nothing — the annotation passes through untouched. The same applies to blank text (except in delete): the edit becomes a no-op.
  • Unknown labels, unknown modes and blank label lists fail the node (unknown labels list the available ones; new fails on labels that already exist).

Inputs

| Name | Type | Default | Description | |------|------|---------|-------------| | annotations | ANNOTATIONS | — | Annotations from Prompt Annotate. | | label | STRING | face | Label(s) to edit, comma-separated for several; every mode applies to all of them. Unknown labels fail the node listing the available ones. | | mode | COMBO | prepend | prepend / append / remove / new / delete — see above. | | text | STRING | — | Tags to add, the comma-separated tags to remove, or the new span's text; blank text = no edit; unused in delete mode. |

Outputs

| Name | Type | Description | |------|------|-------------| | annotations | ANNOTATIONS | The edited annotations; same type as the input, feeds the other annotation nodes. |

Set Variable / Get Variable

Named workflow variables: publish a value under a name once and read it anywhere in the graph — no wire across groups and collapsed subgraphs (Lukutar/Variables). The type is arbitrary (IMAGE, MODEL, CONDITIONING, SEGS, strings…): the value is passed through untouched, so ComfyUI's own execution order and output caching apply, and the node titles show the detected type (Set · img_t2i [IMAGE]).

The web extension connects each Get to its Set with an invisible real link (it serializes into the prompt like a normal wire, it just never renders on the canvas). Because the connection is real, execution ordering, caching, exported API workflows and headless runs all work without any extra machinery. A manual wire into the Get's value input overrides the name; without the web assets the link can simply be wired by hand.

Set Variable — Inputs / Outputs

| Name | Type | Default | Description | |------|------|---------|-------------| | var_name | STRING | — | Variable name, e.g. img_t2i; Get Variable nodes with this name receive the value. | | value | * | — | The value to publish; any type. |

| Name | Type | Description | |------|------|-------------| | value | * | Pass-through of the input; may be wired normally. |

Get Variable — Inputs / Outputs

| Name | Type | Default | Description | |------|------|---------|-------------| | var_name | STRING | — | Variable name to read, e.g. img_t2i. | | value | * | — | Connected automatically (invisible); a manual wire overrides the name. |

| Name | Type | Description | |------|------|-------------| | value | * | The variable's value, with its original type. |

Rules

  • Names are global to the workflow. Several Set nodes may share one name on alternative branches as long as only one branch is active — the "generated or loaded" pattern: muting/bypassing a branch removes its nodes from the prompt, so exactly one Set survives. This is the key difference from KJNodes' Set/Get, which rejects duplicates on the canvas outright.
  • Two simultaneously active Sets with one name are ambiguous: the Get stays unlinked (⚠ on both nodes) and the queue fails with a descriptive error. Rename them or mute one branch.
  • Branches merged by a switch node should use a single Set after the switch instead.
  • Variables live within one queue run; values are not persisted between runs and cannot be "reassigned" sequentially (set → set → get reads the last) — that is a duplicate-name conflict.
  • Without the web extension, wire the Get's value input manually (Set output → Get input works fine headless).

Set Bundle / Get Bundle

A bundle is one named bag holding several heterogeneously typed values — e.g. a model, its VAE and its CLIP under a single name like sdxl — published once and readable anywhere (Lukutar/Variables). The single-value Set/Get Variable pair above stays the right tool for one value; the bundle pair is for moving a whole "kit" of nodes across the graph in one go.

Slots grow SEGS-Concat style (Impact Pack): a Set Bundle always shows one free + add input. Dropping a wire onto it fixes that slot's type to the source (MODEL, VAE, CLIP, IMAGE, anything — any custom type included) and auto-names it from the type (model, vae, clip, model_2…), then the next free input appears. Disconnecting an auto-named slot collapses it again. The node menu offers more:

  • Rename slot "…" — give the slot a meaningful name (e.g. sdxl_model); renamed slots survive disconnects, so a custom name is never lost by accident. Names must be unique, may not contain commas/semicolons/colons and may not be bundle_name, slots or slot_<n> (reserved).
  • Remove slot "…" — explicit removal.
  • Add slot ▸ — pre-create an unconnected slot of a chosen type (for wiring later; the type list there is fixed, wires accept any type).

Every named slot is mirrored to an output of the same name, so the Set node is a real bus: the wires source → Set → Get → consumer are all real links, and execution order, caching and API export stay ComfyUI's own — same principle as the variable pair. A Get Bundle resolves its bundle_name against the active Set Bundles and mirrors the Set's slots: one named input (the anchor of an invisible real link per slot) and one typed output per slot, appearing/vanishing as the Set's slots change. A manual wire into a Get input overrides the name. Titles show the state: Set Bundle · sdxl (3), Get Bundle · sdxl (3), plus ⚠ warnings (duplicate, no active Set, unconnected: vae).

Both nodes carry a slots widget — the spec (model:MODEL,vae:VAE,…) the extension keeps in sync. It is hidden from the UI (internal state; the sockets themselves are the interface) but serialized into the workflow and API export; the backends read it for loud per-slot errors at queue time (a declared but unwired slot fails with Set Bundle [sdxl]: slot(s) not connected: vae instead of a cryptic None downstream).

Set Bundle — Inputs / Outputs

| Name | Type | Default | Description | |------|------|---------|-------------| | bundle_name | STRING | — | Bundle name, e.g. sdxl; Get Bundle nodes with this name receive all slots. | | slots | STRING | — | Hidden spec, managed automatically; do not edit. | | (slots) | any | — | One input per slot, added by wiring or the node menu. |

| Name | Type | Description | |------|------|-------------| | (slots) | any | One output per slot, mirroring the inputs by name. |

Get Bundle — Inputs / Outputs

| Name | Type | Default | Description | |------|------|---------|-------------| | bundle_name | STRING | — | Bundle name to read, e.g. sdxl. | | slots | STRING | — | Hidden spec mirror; updated automatically from the Set side. | | (slots) | any | — | One input per slot, connected by invisible links; manual wires win. |

| Name | Type | Description | |------|------|-------------| | (slots) | any | One output per slot, with the slot's type. |

Rules

  • The same duplicate-name rules as the variable pair apply: several Set Bundles may share one name on alternative branches, but two simultaneously active Sets with one name leave Gets unlinked (⚠) and fail the queue. A Get follows the spec of the single active Set — branches with different slot specs reshape every linked Get on switch, dropping its wiring, so keep the specs identical across branches.
  • Slot values flow in spec order; renaming a slot renames it on every linked Get. Removing a slot removes the matching Get sockets; wires hanging on removed outputs are dropped (re-plug them).
  • A bundle holds at most 16 slots.
  • The bundle pair needs the web extension: every socket is grown and linked by it, so without the web assets the nodes cannot be wired from the UI (use the single-value Set/Get Variable pair instead). Hand-written API prompts work regardless — pass the slot links under their names and order them via the slots spec, or omit the spec and name the links slot_1..slot_16 (values then pass through in natural name order).

Group Switch

A panel node that mirrors the canvas groups: one boolean row per group (Lukutar/Groups). A checked row means the group is active; unchecking switches the group off with the chosen method. Rows appear, reorder and vanish automatically as groups are added, renamed, reordered or deleted on the canvas — the node has no inputs, and its single states output carries the current {group title: enabled} dictionary (order follows the prefix alphabet below).

Parameters

| Name | Type | Default | Description | |------|------|---------|-------------| | disable_method | bypass / disable | bypass | How an unchecked group is switched off. bypass sets the group's nodes to ComfyUI bypass mode (inputs wire through); disable sets them to never/mute (nodes leave the prompt). Same mechanism as the native group context menu. | | change_group_color | BOOLEAN | true | Recolor the frame of a switched-off group: purple for bypass, grey for disable; the original color is restored on re-enable. | | sort_order | STRING | — | Comma-separated title prefixes for the row order, e.g. inp,im,det. A group whose title starts with a token (case-insensitive) takes that token's position — letters or short combinations are enough, no full titles needed; several groups may share a token (they keep their relative order); unlisted groups follow at the end. | | group_states | STRING | {} | Current row states as JSON — managed automatically by the web extension and hidden on the node; edit it only for API workflows without the frontend. |

| Name | Type | Description | |------|------|-------------| | states | GROUP_STATES | Ordered {group title: enabled} dictionary; wire it anywhere * is accepted (e.g. Set Variable) or leave it unconnected. |

Behaviour

  • Toggling a row applies immediately: node modes are captured first and restored on re-enable, so nodes you had muted individually keep that state across a panel toggle. Groups switched off through the native group menu are shown as-is but are not re-applied by parameter changes.
  • The panel state survives reloads. The rows are derived from the live group node modes (which ComfyUI saves natively), and the declared parameters keep a stable serialization, so disable_method, change_group_color, sort_order and the states dictionary come back exactly as saved. The original frame color of a recolored group is remembered too, so a group re-enabled after a reload — even through the native group menu — gets its own color back.
  • The row values always reflect the live graph: toggling a group via the native group menu (or a second Group Switch node) updates the row on the next sync tick.
  • Only the root canvas is mirrored; groups inside subgraphs are not listed.
  • Duplicate group titles are disambiguated in the output keys as Title, Title (2), …
  • Without the web assets the node still loads and validates; drive it by setting the group_states JSON (and optionally sort_order) manually.

Seed History

A zero-input seed generator (Lukutar/Seeds): the same seed + control_after_generate widget pair as the built-in seed inputs, plus a history_size log. The seed wires into any sampler via its INT output.

Parameters

| Name | Type | Default | Description | |------|------|---------|-------------| | seed | INT | 0 | Starting seed value (0 … 2⁶⁴−1). | | history_size | INT | 5 | How many past seeds the rolling log keeps (1–1000). |

| Name | Type | Description | |------|------|-------------| | seed | INT | The seed for the current generation; connect it to any seed/noise_seed input. |

Behaviour

  • control_after_generate is added by the ComfyUI frontend to every seed widget and applied there (fixed / increment / decrement / randomize). It is deliberately not a backend input — the frontend never sends it, so API workflows submit only seed (and history_size).
  • The web extension keeps a rolling log of the seeds actually sent to the backend (newest first, older seeds pushed down, trimmed to history_size) and renders it below the parameters, one seed per line, using the same widget the core "Preview as Text" node displays its text with (plain text only — the Markdown toggle it ships with is removed). While the log is empty the panel shows a placeholder line, so the field is always visible. The log is copied with the copy button in the panel's top-right corner. Consecutive duplicates are collapsed, so re-queueing with a fixed seed does not fill the log with the same number.
  • The log is stored in a node property, so the history survives reloads. Logs saved by older builds (kept in a hidden widget value) are adopted on load.
  • If the frontend does not expose the "Preview as Text" helper, the extension falls back to a plain text widget; without the web assets the node still works as a plain seed source — the history is simply not displayed.

Paste (Clipspace, Keep Mask)

Not a node — a context-menu item the pack's web extension adds to image-upload nodes (Load Image, Load Image (impact), anything with an image_upload input).

ComfyUI stores a painted mask in the alpha channel of the very file the image widget points at; the stock Paste (Clipspace) swaps the file, and the mask dies with it. This item pastes the clipspace's selected image but re-bakes the current file's alpha (i.e. the mask) on top of it, uploading the combined PNG as a new input file — the same save flow the MaskEditor uses.

Behaviour

  • The item appears in the node's context menu whenever the clipspace holds an image. On the current ComfyUI frontend it sits in the Extensions section at the bottom of the menu (next to other packs' items); on the legacy frontend, directly in the node menu.
  • If the sizes differ, the mask is stretched to the new image's dimensions.
  • If the current file has no mask (no alpha / fully opaque), the item degrades to an ordinary clipspace paste — nothing is uploaded.
  • The pasted image's own alpha (if any) is discarded; only the node's existing mask is kept.
  • The combined PNG is encoded straight from the RGBA buffer, so RGB under semi-transparent mask areas is preserved bit-exactly (a plain canvas re-encode would black it out).
  • Requires no backend node — with the extension disabled everything keeps working, the stock paste behaviour is unchanged.

Installation

Via ComfyUI-Manager: Custom Nodes Manager → Install via git URL → https://github.com/Mistress-Lukutar/ComfyUI-LukutarNodes

Manual:

cd <ComfyUI>/custom_nodes
git clone https://github.com/Mistress-Lukutar/ComfyUI-LukutarNodes

Restart ComfyUI afterwards. requirements.txt is picked up automatically by ComfyUI-Manager (opencv-python; torch and numpy ship with ComfyUI).

Development

# Engine unit tests (any python with numpy, opencv-python and pytest)
pytest

# Node loading + end-to-end smoke test with ComfyUI's own python
"<ComfyUI>/python_embeded/python.exe" tests/smoke_test_comfyui_load.py

Layout:

core/    pure numpy/OpenCV engines, no ComfyUI imports
nodes/   ComfyUI node classes (INPUT_TYPES, tensor glue)
utils/   torch tensor conversion helpers
web/js/  frontend extension (prompt annotator popup editor)
tests/   pytest suite + ComfyUI loader smoke test

The image nodes appear in ComfyUI under the Lukutar/Image category, the prompt annotation nodes under Lukutar/Prompt.

License

MIT — see LICENSE.