AP Loop Open
Your frame-by-frame feedback loop starts here — history, first frame, and a token that carries state
- 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
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 tohistory_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.
Inputs (8)
| Name | Type | Default | Description |
|---|---|---|---|
| source_images | IMAGE | — | |
| history_count | INT | 30–5 | — |
| return_first_when_no_previous_available | BOOLEAN | false | — |
| apply_custom_replacement | BOOLEAN | false | — |
| custom_frame_index_map | STRING | — | |
| source_masksopt | MASK | — | |
| custom_framesopt | IMAGE | — | |
| additional_dataopt | * | — |
Outputs (19)
| Name | Type | Description |
|---|---|---|
| loop_token | FLOW_CONTROL | — |
| original_current_image | IMAGE | — |
| original_current_mask | MASK | — |
| original_first_image | IMAGE | — |
| original_previous_image | IMAGE | — |
| original_previous_mask | MASK | — |
| processed_previous_image_1 | IMAGE | — |
| processed_previous_image_2 | IMAGE | — |
| processed_previous_image_3 | IMAGE | — |
| processed_previous_image_4 | IMAGE | — |
| processed_previous_image_5 | IMAGE | — |
| processed_previous_mask_1 | MASK | — |
| processed_previous_mask_2 | MASK | — |
| processed_previous_mask_3 | MASK | — |
| processed_previous_mask_4 | MASK | — |
| processed_previous_mask_5 | MASK | — |
| custom_current_frame | IMAGE | — |
| additional_data_current | * | — |
| iteration_index | INT | — |