Add oversampled image output to all VFI Interpolate nodes
Second IMAGE output exposes the full power-of-2 oversampled frames before target FPS selection. Identical to the first output when target_fps=0. Document the new output in README. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,11 @@ Interpolates frames from an image batch.
|
|||||||
| **source_fps** | Input frame rate. Required when target_fps > 0 |
|
| **source_fps** | Input frame rate. Required when target_fps > 0 |
|
||||||
| **target_fps** | Target output FPS. When > 0, overrides multiplier — auto-computes the optimal power-of-2 oversample then selects frames at exact target timestamps. 0 = use multiplier |
|
| **target_fps** | Target output FPS. When > 0, overrides multiplier — auto-computes the optimal power-of-2 oversample then selects frames at exact target timestamps. 0 = use multiplier |
|
||||||
|
|
||||||
|
| Output | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| **images** | Interpolated frames at the target FPS (or at the multiplied rate when target_fps = 0) |
|
||||||
|
| **oversampled** | Full power-of-2 oversampled frames before target FPS selection. Same as `images` when target_fps = 0. Useful for inspecting the raw interpolation or feeding into another pipeline |
|
||||||
|
|
||||||
#### BIM-VFI Segment Interpolate
|
#### BIM-VFI Segment Interpolate
|
||||||
|
|
||||||
Same as Interpolate but processes a single segment of the input. Chain multiple instances with Save nodes between them to bound peak RAM. The model pass-through output forces sequential execution.
|
Same as Interpolate but processes a single segment of the input. Chain multiple instances with Save nodes between them to bound peak RAM. The model pass-through output forces sequential execution.
|
||||||
|
|||||||
60
nodes.py
60
nodes.py
@@ -202,8 +202,8 @@ class BIMVFIInterpolate:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RETURN_TYPES = ("IMAGE",)
|
RETURN_TYPES = ("IMAGE", "IMAGE")
|
||||||
RETURN_NAMES = ("images",)
|
RETURN_NAMES = ("images", "oversampled")
|
||||||
FUNCTION = "interpolate"
|
FUNCTION = "interpolate"
|
||||||
CATEGORY = "video/BIM-VFI"
|
CATEGORY = "video/BIM-VFI"
|
||||||
|
|
||||||
@@ -275,7 +275,7 @@ class BIMVFIInterpolate:
|
|||||||
keep_device, all_on_gpu, batch_size, chunk_size,
|
keep_device, all_on_gpu, batch_size, chunk_size,
|
||||||
source_fps=0.0, target_fps=0.0):
|
source_fps=0.0, target_fps=0.0):
|
||||||
if images.shape[0] < 2:
|
if images.shape[0] < 2:
|
||||||
return (images,)
|
return (images, images)
|
||||||
|
|
||||||
# Target FPS mode: auto-compute multiplier from fps ratio
|
# Target FPS mode: auto-compute multiplier from fps ratio
|
||||||
use_target_fps = target_fps > 0 and source_fps > 0
|
use_target_fps = target_fps > 0 and source_fps > 0
|
||||||
@@ -285,7 +285,7 @@ class BIMVFIInterpolate:
|
|||||||
# Downsampling or same fps — select from input directly
|
# Downsampling or same fps — select from input directly
|
||||||
all_frames = images.permute(0, 3, 1, 2)
|
all_frames = images.permute(0, 3, 1, 2)
|
||||||
result = _select_target_fps_frames(all_frames, source_fps, target_fps, mult, all_frames.shape[0])
|
result = _select_target_fps_frames(all_frames, source_fps, target_fps, mult, all_frames.shape[0])
|
||||||
return (result.cpu().permute(0, 2, 3, 1),)
|
return (result.cpu().permute(0, 2, 3, 1), images)
|
||||||
else:
|
else:
|
||||||
num_passes = {2: 1, 4: 2, 8: 3}[multiplier]
|
num_passes = {2: 1, 4: 2, 8: 3}[multiplier]
|
||||||
mult = multiplier
|
mult = multiplier
|
||||||
@@ -344,13 +344,16 @@ class BIMVFIInterpolate:
|
|||||||
|
|
||||||
result = torch.cat(result_chunks, dim=0)
|
result = torch.cat(result_chunks, dim=0)
|
||||||
|
|
||||||
|
# Convert oversampled to ComfyUI format for second output
|
||||||
|
oversampled = result.cpu().permute(0, 2, 3, 1)
|
||||||
|
|
||||||
# Target FPS: select frames from oversampled result
|
# Target FPS: select frames from oversampled result
|
||||||
if use_target_fps:
|
if use_target_fps:
|
||||||
result = _select_target_fps_frames(result, source_fps, target_fps, mult, total_input)
|
result = _select_target_fps_frames(result, source_fps, target_fps, mult, total_input)
|
||||||
|
|
||||||
# Convert back to ComfyUI [B, H, W, C], on CPU
|
# Convert back to ComfyUI [B, H, W, C], on CPU
|
||||||
result = result.cpu().permute(0, 2, 3, 1)
|
result = result.cpu().permute(0, 2, 3, 1)
|
||||||
return (result,)
|
return (result, oversampled)
|
||||||
|
|
||||||
|
|
||||||
class BIMVFISegmentInterpolate(BIMVFIInterpolate):
|
class BIMVFISegmentInterpolate(BIMVFIInterpolate):
|
||||||
@@ -461,7 +464,7 @@ class BIMVFISegmentInterpolate(BIMVFIInterpolate):
|
|||||||
|
|
||||||
# Standard multiplier mode
|
# Standard multiplier mode
|
||||||
is_continuation = segment_index > 0
|
is_continuation = segment_index > 0
|
||||||
(result,) = super().interpolate(
|
(result, _) = super().interpolate(
|
||||||
segment_images, model, multiplier, clear_cache_after_n_frames,
|
segment_images, model, multiplier, clear_cache_after_n_frames,
|
||||||
keep_device, all_on_gpu, batch_size, chunk_size,
|
keep_device, all_on_gpu, batch_size, chunk_size,
|
||||||
)
|
)
|
||||||
@@ -737,8 +740,8 @@ class EMAVFIInterpolate:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RETURN_TYPES = ("IMAGE",)
|
RETURN_TYPES = ("IMAGE", "IMAGE")
|
||||||
RETURN_NAMES = ("images",)
|
RETURN_NAMES = ("images", "oversampled")
|
||||||
FUNCTION = "interpolate"
|
FUNCTION = "interpolate"
|
||||||
CATEGORY = "video/EMA-VFI"
|
CATEGORY = "video/EMA-VFI"
|
||||||
|
|
||||||
@@ -803,7 +806,7 @@ class EMAVFIInterpolate:
|
|||||||
keep_device, all_on_gpu, batch_size, chunk_size,
|
keep_device, all_on_gpu, batch_size, chunk_size,
|
||||||
source_fps=0.0, target_fps=0.0):
|
source_fps=0.0, target_fps=0.0):
|
||||||
if images.shape[0] < 2:
|
if images.shape[0] < 2:
|
||||||
return (images,)
|
return (images, images)
|
||||||
|
|
||||||
# Target FPS mode: auto-compute multiplier from fps ratio
|
# Target FPS mode: auto-compute multiplier from fps ratio
|
||||||
use_target_fps = target_fps > 0 and source_fps > 0
|
use_target_fps = target_fps > 0 and source_fps > 0
|
||||||
@@ -812,7 +815,7 @@ class EMAVFIInterpolate:
|
|||||||
if num_passes == 0:
|
if num_passes == 0:
|
||||||
all_frames = images.permute(0, 3, 1, 2)
|
all_frames = images.permute(0, 3, 1, 2)
|
||||||
result = _select_target_fps_frames(all_frames, source_fps, target_fps, mult, all_frames.shape[0])
|
result = _select_target_fps_frames(all_frames, source_fps, target_fps, mult, all_frames.shape[0])
|
||||||
return (result.cpu().permute(0, 2, 3, 1),)
|
return (result.cpu().permute(0, 2, 3, 1), images)
|
||||||
else:
|
else:
|
||||||
num_passes = {2: 1, 4: 2, 8: 3}[multiplier]
|
num_passes = {2: 1, 4: 2, 8: 3}[multiplier]
|
||||||
mult = multiplier
|
mult = multiplier
|
||||||
@@ -871,13 +874,16 @@ class EMAVFIInterpolate:
|
|||||||
|
|
||||||
result = torch.cat(result_chunks, dim=0)
|
result = torch.cat(result_chunks, dim=0)
|
||||||
|
|
||||||
|
# Convert oversampled to ComfyUI format for second output
|
||||||
|
oversampled = result.cpu().permute(0, 2, 3, 1)
|
||||||
|
|
||||||
# Target FPS: select frames from oversampled result
|
# Target FPS: select frames from oversampled result
|
||||||
if use_target_fps:
|
if use_target_fps:
|
||||||
result = _select_target_fps_frames(result, source_fps, target_fps, mult, total_input)
|
result = _select_target_fps_frames(result, source_fps, target_fps, mult, total_input)
|
||||||
|
|
||||||
# Convert back to ComfyUI [B, H, W, C], on CPU
|
# Convert back to ComfyUI [B, H, W, C], on CPU
|
||||||
result = result.cpu().permute(0, 2, 3, 1)
|
result = result.cpu().permute(0, 2, 3, 1)
|
||||||
return (result,)
|
return (result, oversampled)
|
||||||
|
|
||||||
|
|
||||||
class EMAVFISegmentInterpolate(EMAVFIInterpolate):
|
class EMAVFISegmentInterpolate(EMAVFIInterpolate):
|
||||||
@@ -985,7 +991,7 @@ class EMAVFISegmentInterpolate(EMAVFIInterpolate):
|
|||||||
|
|
||||||
# Standard multiplier mode
|
# Standard multiplier mode
|
||||||
is_continuation = segment_index > 0
|
is_continuation = segment_index > 0
|
||||||
(result,) = super().interpolate(
|
(result, _) = super().interpolate(
|
||||||
segment_images, model, multiplier, clear_cache_after_n_frames,
|
segment_images, model, multiplier, clear_cache_after_n_frames,
|
||||||
keep_device, all_on_gpu, batch_size, chunk_size,
|
keep_device, all_on_gpu, batch_size, chunk_size,
|
||||||
)
|
)
|
||||||
@@ -1128,8 +1134,8 @@ class SGMVFIInterpolate:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RETURN_TYPES = ("IMAGE",)
|
RETURN_TYPES = ("IMAGE", "IMAGE")
|
||||||
RETURN_NAMES = ("images",)
|
RETURN_NAMES = ("images", "oversampled")
|
||||||
FUNCTION = "interpolate"
|
FUNCTION = "interpolate"
|
||||||
CATEGORY = "video/SGM-VFI"
|
CATEGORY = "video/SGM-VFI"
|
||||||
|
|
||||||
@@ -1194,7 +1200,7 @@ class SGMVFIInterpolate:
|
|||||||
keep_device, all_on_gpu, batch_size, chunk_size,
|
keep_device, all_on_gpu, batch_size, chunk_size,
|
||||||
source_fps=0.0, target_fps=0.0):
|
source_fps=0.0, target_fps=0.0):
|
||||||
if images.shape[0] < 2:
|
if images.shape[0] < 2:
|
||||||
return (images,)
|
return (images, images)
|
||||||
|
|
||||||
# Target FPS mode: auto-compute multiplier from fps ratio
|
# Target FPS mode: auto-compute multiplier from fps ratio
|
||||||
use_target_fps = target_fps > 0 and source_fps > 0
|
use_target_fps = target_fps > 0 and source_fps > 0
|
||||||
@@ -1203,7 +1209,7 @@ class SGMVFIInterpolate:
|
|||||||
if num_passes == 0:
|
if num_passes == 0:
|
||||||
all_frames = images.permute(0, 3, 1, 2)
|
all_frames = images.permute(0, 3, 1, 2)
|
||||||
result = _select_target_fps_frames(all_frames, source_fps, target_fps, mult, all_frames.shape[0])
|
result = _select_target_fps_frames(all_frames, source_fps, target_fps, mult, all_frames.shape[0])
|
||||||
return (result.cpu().permute(0, 2, 3, 1),)
|
return (result.cpu().permute(0, 2, 3, 1), images)
|
||||||
else:
|
else:
|
||||||
num_passes = {2: 1, 4: 2, 8: 3}[multiplier]
|
num_passes = {2: 1, 4: 2, 8: 3}[multiplier]
|
||||||
mult = multiplier
|
mult = multiplier
|
||||||
@@ -1262,13 +1268,16 @@ class SGMVFIInterpolate:
|
|||||||
|
|
||||||
result = torch.cat(result_chunks, dim=0)
|
result = torch.cat(result_chunks, dim=0)
|
||||||
|
|
||||||
|
# Convert oversampled to ComfyUI format for second output
|
||||||
|
oversampled = result.cpu().permute(0, 2, 3, 1)
|
||||||
|
|
||||||
# Target FPS: select frames from oversampled result
|
# Target FPS: select frames from oversampled result
|
||||||
if use_target_fps:
|
if use_target_fps:
|
||||||
result = _select_target_fps_frames(result, source_fps, target_fps, mult, total_input)
|
result = _select_target_fps_frames(result, source_fps, target_fps, mult, total_input)
|
||||||
|
|
||||||
# Convert back to ComfyUI [B, H, W, C], on CPU
|
# Convert back to ComfyUI [B, H, W, C], on CPU
|
||||||
result = result.cpu().permute(0, 2, 3, 1)
|
result = result.cpu().permute(0, 2, 3, 1)
|
||||||
return (result,)
|
return (result, oversampled)
|
||||||
|
|
||||||
|
|
||||||
class SGMVFISegmentInterpolate(SGMVFIInterpolate):
|
class SGMVFISegmentInterpolate(SGMVFIInterpolate):
|
||||||
@@ -1376,7 +1385,7 @@ class SGMVFISegmentInterpolate(SGMVFIInterpolate):
|
|||||||
|
|
||||||
# Standard multiplier mode
|
# Standard multiplier mode
|
||||||
is_continuation = segment_index > 0
|
is_continuation = segment_index > 0
|
||||||
(result,) = super().interpolate(
|
(result, _) = super().interpolate(
|
||||||
segment_images, model, multiplier, clear_cache_after_n_frames,
|
segment_images, model, multiplier, clear_cache_after_n_frames,
|
||||||
keep_device, all_on_gpu, batch_size, chunk_size,
|
keep_device, all_on_gpu, batch_size, chunk_size,
|
||||||
)
|
)
|
||||||
@@ -1536,8 +1545,8 @@ class GIMMVFIInterpolate:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RETURN_TYPES = ("IMAGE",)
|
RETURN_TYPES = ("IMAGE", "IMAGE")
|
||||||
RETURN_NAMES = ("images",)
|
RETURN_NAMES = ("images", "oversampled")
|
||||||
FUNCTION = "interpolate"
|
FUNCTION = "interpolate"
|
||||||
CATEGORY = "video/GIMM-VFI"
|
CATEGORY = "video/GIMM-VFI"
|
||||||
|
|
||||||
@@ -1647,7 +1656,7 @@ class GIMMVFIInterpolate:
|
|||||||
batch_size, chunk_size,
|
batch_size, chunk_size,
|
||||||
source_fps=0.0, target_fps=0.0):
|
source_fps=0.0, target_fps=0.0):
|
||||||
if images.shape[0] < 2:
|
if images.shape[0] < 2:
|
||||||
return (images,)
|
return (images, images)
|
||||||
|
|
||||||
# Target FPS mode: auto-compute multiplier from fps ratio
|
# Target FPS mode: auto-compute multiplier from fps ratio
|
||||||
use_target_fps = target_fps > 0 and source_fps > 0
|
use_target_fps = target_fps > 0 and source_fps > 0
|
||||||
@@ -1656,7 +1665,7 @@ class GIMMVFIInterpolate:
|
|||||||
if num_passes == 0:
|
if num_passes == 0:
|
||||||
all_frames = images.permute(0, 3, 1, 2)
|
all_frames = images.permute(0, 3, 1, 2)
|
||||||
result = _select_target_fps_frames(all_frames, source_fps, target_fps, mult, all_frames.shape[0])
|
result = _select_target_fps_frames(all_frames, source_fps, target_fps, mult, all_frames.shape[0])
|
||||||
return (result.cpu().permute(0, 2, 3, 1),)
|
return (result.cpu().permute(0, 2, 3, 1), images)
|
||||||
# Override multiplier for single_pass mode
|
# Override multiplier for single_pass mode
|
||||||
multiplier = mult
|
multiplier = mult
|
||||||
else:
|
else:
|
||||||
@@ -1732,13 +1741,16 @@ class GIMMVFIInterpolate:
|
|||||||
|
|
||||||
result = torch.cat(result_chunks, dim=0)
|
result = torch.cat(result_chunks, dim=0)
|
||||||
|
|
||||||
|
# Convert oversampled to ComfyUI format for second output
|
||||||
|
oversampled = result.cpu().permute(0, 2, 3, 1)
|
||||||
|
|
||||||
# Target FPS: select frames from oversampled result
|
# Target FPS: select frames from oversampled result
|
||||||
if use_target_fps:
|
if use_target_fps:
|
||||||
result = _select_target_fps_frames(result, source_fps, target_fps, mult, total_input)
|
result = _select_target_fps_frames(result, source_fps, target_fps, mult, total_input)
|
||||||
|
|
||||||
# Convert back to ComfyUI [B, H, W, C], on CPU
|
# Convert back to ComfyUI [B, H, W, C], on CPU
|
||||||
result = result.cpu().permute(0, 2, 3, 1)
|
result = result.cpu().permute(0, 2, 3, 1)
|
||||||
return (result,)
|
return (result, oversampled)
|
||||||
|
|
||||||
|
|
||||||
class GIMMVFISegmentInterpolate(GIMMVFIInterpolate):
|
class GIMMVFISegmentInterpolate(GIMMVFIInterpolate):
|
||||||
@@ -1856,7 +1868,7 @@ class GIMMVFISegmentInterpolate(GIMMVFIInterpolate):
|
|||||||
|
|
||||||
# Standard multiplier mode
|
# Standard multiplier mode
|
||||||
is_continuation = segment_index > 0
|
is_continuation = segment_index > 0
|
||||||
(result,) = super().interpolate(
|
(result, _) = super().interpolate(
|
||||||
segment_images, model, multiplier, single_pass,
|
segment_images, model, multiplier, single_pass,
|
||||||
clear_cache_after_n_frames, keep_device, all_on_gpu,
|
clear_cache_after_n_frames, keep_device, all_on_gpu,
|
||||||
batch_size, chunk_size,
|
batch_size, chunk_size,
|
||||||
|
|||||||
Reference in New Issue
Block a user