ComfyUI Node

AP Loop Open

Your frame-by-frame feedback loop starts here — history, first frame, and a token that carries state

By adampolczynski·Created 6 months ago·Updated 6 months ago· 1
AP Loop Open
  • source_images
  • source_masks
  • custom_frames
  • additional_data
  • loop_token
  • original_current_image
  • original_current_mask
  • original_first_image
  • original_previous_image
  • original_previous_mask
  • processed_previous_image_1
  • processed_previous_image_2
  • processed_previous_image_3
  • processed_previous_image_4
  • processed_previous_image_5
  • processed_previous_mask_1
  • processed_previous_mask_2
  • processed_previous_mask_3
  • processed_previous_mask_4
  • processed_previous_mask_5
  • custom_current_frame
  • additional_data_current
  • iteration_index
history_count3
return_first_when_no_previous_availablefalse
apply_custom_replacementfalse
custom_frame_index_map

ComfyUI is, at heart, a graph of pure functions: every node gets inputs, produces outputs, and forgets everything. That's a problem the moment you want temporal feedback - "here's what the previous frame looked like after I processed it, now improve this one" - because nothing in a pure graph remembers the previous frame. The loop system in this pack (AP Loop Open/AP Loop Close) is the answer, and this is the opening half: it slices a source batch into per-frame pieces, hands you the current frame plus up to five frames of processed history, and issues a token that carries the loop's state forward.

The author built this pack because existing loop/feedback nodes "lack a clean API, iteration counters, or require unnecessary inputs" - and it shows here. Loop Open's design goal is one node that opens a per-frame recursion and gives you everything you need to reason about "now" vs "then."

The mechanism

Under the hood it's a hidden-iteration recursion: Loop Open and Loop Close are linked through a loop_token (a FLOW_CONTROL object), and ComfyUI re-executes the loop body with an incremented internal iteration_index until every frame of source_images is consumed. Loop Open reads that internal index and the accumulated processed history from the engine, then slices:

  • original_current_image / original_current_mask - this iteration's raw frame and mask.
  • original_first_image - the very first frame, always handy as a reference anchor.
  • original_previous_image / original_previous_mask - the unprocessed source frame before this one.
  • processed_previous_image_1..5 + matching masks - the processed history: frames that already went through your loop body, up to history_count (max 5). This is what temporal-consistency work feeds on - connect these to a temporal blend or use them as sampler context.
  • iteration_index - the current 0-based loop position.

Two behaviors worth knowing: with return_first_when_no_previous_available=true, missing previous slots return the first frame instead of zeros (useful for the first iterations where no processed history exists yet). And the loop deliberately raises an error if it reaches a frame that should have processed history but none was connected - the error message is explicit that you must wire processed_image from Loop Close, which is a genuinely helpful guardrail compared to silently feeding garbage.

Custom frame replacement

You can override what the loop feeds as "current" using custom_frames plus custom_frame_index_map (a comma-separated list like 0,12,48 - frame indices to replace) and apply_custom_replacement=true. When active, the mapped replacement frames show up as custom_current_frame and can force current_image to use them. This is for pipelines where specific frames come from somewhere else (a cleaner render, an inpaint pass) rather than the source batch.

Inputs to actually set

  • source_images - the batch to iterate over.
  • history_count - how many processed frames of history to expose (0–5, default 3). More history = more temporal context, more wires.
  • source_masks (optional) - per-frame masks, carried through the loop.
  • additional_data (optional, * type) - a parallel payload iterated in lockstep at the same index (the README's example: run an IMAGE loop with a LATENT loop payload in parallel).

Installing it

Pack-wide install: ComfyUI Manager (search "AP_OpticalFlow"), or

cd ComfyUI/custom_nodes
git clone https://github.com/adampolczynski/ComfyUI_AP_OpticalFlow
python -m pip install -r custom_nodes/ComfyUI_AP_OpticalFlow/requirements.txt

Restart. Dependency: torchvision>=0.15.

The pairing rule

Loop Open is always half of a pair - the other half is APLoopClose, which takes your processed result and feeds it back. Wire loop_token from Open into Close, put your processing (warp, inpaint, blend, sample) in between, and Close's processed_image output is what comes back in as processed_previous_image_1 next iteration. If you see the "expected previous processed image" error, it means that return path is missing - the loop caught you, and the fix is wiring the Close node's output into the Open node's hidden history inputs.

CategoryAP_OpticalFlow

Inputs (8)

NameTypeDefaultDescription
source_imagesIMAGE
history_countINT30–5
return_first_when_no_previous_availableBOOLEANfalse
apply_custom_replacementBOOLEANfalse
custom_frame_index_mapSTRING
source_masksoptMASK
custom_framesoptIMAGE
additional_dataopt*

Outputs (19)

NameTypeDescription
loop_tokenFLOW_CONTROL
original_current_imageIMAGE
original_current_maskMASK
original_first_imageIMAGE
original_previous_imageIMAGE
original_previous_maskMASK
processed_previous_image_1IMAGE
processed_previous_image_2IMAGE
processed_previous_image_3IMAGE
processed_previous_image_4IMAGE
processed_previous_image_5IMAGE
processed_previous_mask_1MASK
processed_previous_mask_2MASK
processed_previous_mask_3MASK
processed_previous_mask_4MASK
processed_previous_mask_5MASK
custom_current_frameIMAGE
additional_data_current*
iteration_indexINT