Files
m1_4kandClaude Sonnet 4.6 f0d2ae802a Refactor: IMAGE tensor I/O — connect directly to video nodes
- Input/output are now standard ComfyUI IMAGE tensors [B, H, W, C]
  float32 in [0,1], compatible with any Load/Save Video node
- Remove file-path and folder_paths dependencies from the node
- Add interpolate_batch_2x() to phase_interpolation.py for
  frame-list based processing
- Output frame count: B*2-1 (original frames interleaved with
  synthesized midpoints)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 00:30:02 +09:00

98 lines
3.2 KiB
Python

"""
ComfyUI custom node: Phase-Based Frame Interpolation (2x)
Input : IMAGE tensor [B, H, W, C] float32 [0, 1] — batch of video frames
Output : IMAGE tensor [B*2-1, H, W, C] float32 [0, 1] — 2x interpolated frames
Connects directly to any Load Video / Save Video node that uses the
standard ComfyUI IMAGE batch format.
"""
import numpy as np
import torch
from .phase_interpolation import interpolate_batch_2x
class PhaseBasedFrameInterpolation:
"""
2x Frame Interpolation via Phase-Based Fourier Method.
Takes a batch of video frames as a standard ComfyUI IMAGE tensor,
inserts one phase-interpolated frame between every consecutive pair,
and returns the doubled frame batch as an IMAGE tensor.
Connect: Load Video → [this node] → Save Video / Preview
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"images": (
"IMAGE",
{
"tooltip": "Batch of video frames [B, H, W, C] from a Load Video node.",
},
),
"pyramid_levels": (
"INT",
{
"default": 6,
"min": 3,
"max": 10,
"step": 1,
"tooltip": (
"Number of complex pyramid levels. "
"More levels = finer phase unwrapping but slower."
),
},
),
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("images",)
FUNCTION = "interpolate"
CATEGORY = "video/interpolation"
DESCRIPTION = (
"2x frame interpolation using Fourier-transform phase differences. "
"Accepts an IMAGE batch from a Load Video node, inserts one synthesized "
"frame between every consecutive pair, and returns the 2x frame batch. "
"Connect the output to a Save Video or Preview node."
)
def interpolate(self, images: torch.Tensor, pyramid_levels: int):
# images: [B, H, W, C] float32 in [0, 1]
B, H, W, C = images.shape
if B < 2:
raise ValueError("Need at least 2 frames for interpolation.")
# Convert tensor to list of uint8 RGB numpy arrays
np_frames = (images.cpu().numpy() * 255.0).clip(0, 255).astype(np.uint8)
frame_list = [np_frames[i] for i in range(B)]
print(f"[PhaseInterp] Input: {B} frames Output: {B * 2 - 1} frames")
# Run phase-based 2x interpolation
out_list = interpolate_batch_2x(frame_list, num_levels=pyramid_levels)
# Convert back to [B_out, H, W, C] float32 tensor in [0, 1]
out_np = np.stack(out_list, axis=0).astype(np.float32) / 255.0
out_tensor = torch.from_numpy(out_np)
return (out_tensor,)
# ---------------------------------------------------------------------------
# Node registration
# ---------------------------------------------------------------------------
NODE_CLASS_MAPPINGS = {
"PhaseBasedFrameInterpolation": PhaseBasedFrameInterpolation,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"PhaseBasedFrameInterpolation": "Phase-Based Frame Interpolation (2x)",
}