Files
grmchn-ComfyUI-ProportionCh…/proportion_changer/interpolation_node.py
T
gear_machineandClaude 5777eeac20 Improve error handling and reduce verbose logging across KeyPoint processing nodes
- Enhanced error handling with specific exception types in detector and interpolation nodes
- Reduced verbose console output in KeyPoint Denoiser components for cleaner operation
- Improved coordinate validation and array bounds checking in detector nodes
- Translated comments to English in params node for consistency
- Maintained core functionality while improving user experience

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-28 06:58:02 +09:00

332 lines
15 KiB
Python

"""
ProportionChanger interpolation node class
Provides keypoint interpolation for creating smooth animations between poses
"""
import numpy as np
from typing import List, Dict, Any, Optional, Union
class ProportionChangerInterpolator:
"""
Interpolates between keypoint sequences to create smooth animation frames
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"pose_keypoint": ("POSE_KEYPOINT",), # Batch processing supported (singular notation but accepts lists)
"interpolation_frames": ("INT", {"default": 0, "min": 0, "max": 5}), # 0=no interpolation, 1=add 1 frame, 2=add 2 frames...
"method": (["linear", "momentum", "bezier", "ease_in_out"], {"default": "linear"}),
}
}
RETURN_TYPES = ("POSE_KEYPOINT",)
RETURN_NAMES = ("interpolated_sequence",)
FUNCTION = "process"
CATEGORY = "ProportionChanger"
def process(self, pose_keypoint, interpolation_frames: int = 0, method: str = "linear"):
"""
Process keypoint interpolation with new simplified specification
Args:
pose_keypoint: Input keypoint sequence (can be single frame or sequence)
interpolation_frames: Number of frames to interpolate between each pair (0-5)
method: Interpolation method to use
Returns:
interpolated_sequence: Processed sequence with interpolated frames
"""
# Handle single frame input - return as-is
if interpolation_frames == 0:
return (pose_keypoint,)
# Convert input to sequence format if needed
if isinstance(pose_keypoint, dict):
# Single frame input - cannot interpolate
return (pose_keypoint,)
elif isinstance(pose_keypoint, list) and len(pose_keypoint) < 2:
# Insufficient frames for interpolation
return (pose_keypoint,)
# Process sequence interpolation
try:
interpolated_result = self._process_sequence_interpolation(
pose_keypoint, interpolation_frames, method
)
return (interpolated_result,)
except (ValueError, TypeError, KeyError) as e:
print(f"Interpolation error: {e}")
return (pose_keypoint,) # Return original on error
except Exception as e:
print(f"Unexpected interpolation error: {e}")
return (pose_keypoint,) # Return original on error
def _process_sequence_interpolation(self, sequence: List[Dict], interpolation_frames: int, method: str) -> List[Dict]:
"""
Process sequence interpolation with batch processing
Args:
sequence: List of keypoint frames
interpolation_frames: Number of frames to interpolate between each pair
method: Interpolation method
Returns:
Interpolated sequence with additional frames
"""
if len(sequence) < 2:
return sequence
interpolated_sequence = []
# Process each consecutive frame pair
for i in range(len(sequence) - 1):
current_frame = sequence[i]
next_frame = sequence[i + 1]
# Add current frame
interpolated_sequence.append(current_frame)
# Generate interpolated frames between current and next
for j in range(interpolation_frames):
t = (j + 1) / (interpolation_frames + 1)
# Choose interpolation method
if method == "linear":
interp_frame = self._linear_interpolation(current_frame, next_frame, t)
elif method == "ease_in_out":
interp_frame = self._ease_in_out_interpolation(current_frame, next_frame, t)
elif method == "bezier":
interp_frame = self._bezier_interpolation(current_frame, next_frame, t)
elif method == "momentum":
# For momentum, use previous frame if available
prev_frame = sequence[i - 1] if i > 0 else None
next_next_frame = sequence[i + 2] if i + 2 < len(sequence) else None
interp_frame = self._momentum_interpolation(
prev_frame, current_frame, next_frame, next_next_frame, t
)
else:
# Fallback to linear
interp_frame = self._linear_interpolation(current_frame, next_frame, t)
interpolated_sequence.append(interp_frame)
# Add final frame
interpolated_sequence.append(sequence[-1])
return interpolated_sequence
def _linear_interpolation(self, frame_a: Dict, frame_b: Dict, t: float) -> Dict:
"""
Linear interpolation between two keypoint frames
"""
result_frame = {
'people': [],
'canvas_width': frame_a.get('canvas_width', 512),
'canvas_height': frame_a.get('canvas_height', 768)
}
# Interpolate each person
max_people = max(len(frame_a.get('people', [])), len(frame_b.get('people', [])))
for person_idx in range(max_people):
person_a = frame_a.get('people', [{}])[min(person_idx, len(frame_a.get('people', [])) - 1)] if frame_a.get('people') else {}
person_b = frame_b.get('people', [{}])[min(person_idx, len(frame_b.get('people', [])) - 1)] if frame_b.get('people') else {}
interpolated_person = {}
# Interpolate each keypoint type
for keypoint_type in ['pose_keypoints_2d', 'face_keypoints_2d', 'hand_left_keypoints_2d', 'hand_right_keypoints_2d']:
if keypoint_type in person_a or keypoint_type in person_b:
kps_a = person_a.get(keypoint_type, [])
kps_b = person_b.get(keypoint_type, [])
interpolated_person[keypoint_type] = self._interpolate_keypoint_array(kps_a, kps_b, t)
result_frame['people'].append(interpolated_person)
return result_frame
def _interpolate_keypoint_array(self, kps_a: List[float], kps_b: List[float], t: float) -> List[float]:
"""
Interpolate keypoint arrays with proper confidence handling
"""
if not kps_a and not kps_b:
return []
# Use non-empty array or pad shorter array
max_len = max(len(kps_a), len(kps_b))
kps_a_padded = kps_a + [0.0] * (max_len - len(kps_a))
kps_b_padded = kps_b + [0.0] * (max_len - len(kps_b))
interpolated = []
for i in range(0, max_len, 3): # Process x, y, confidence triplets
if i + 2 < max_len:
# Linear interpolation for x, y coordinates
x = kps_a_padded[i] + t * (kps_b_padded[i] - kps_a_padded[i])
y = kps_a_padded[i + 1] + t * (kps_b_padded[i + 1] - kps_a_padded[i + 1])
# Use minimum confidence (conservative approach)
conf = min(kps_a_padded[i + 2], kps_b_padded[i + 2])
interpolated.extend([x, y, conf])
return interpolated
def _ease_in_out_interpolation(self, frame_a: Dict, frame_b: Dict, t: float) -> Dict:
"""
Ease-in-out interpolation using cubic curves
"""
# Apply cubic ease-in-out to t
if t < 0.5:
eased_t = 4 * t * t * t
else:
eased_t = 1 - 4 * (1 - t) * (1 - t) * (1 - t)
return self._linear_interpolation(frame_a, frame_b, eased_t)
def _bezier_interpolation(self, frame_a: Dict, frame_b: Dict, t: float) -> Dict:
"""
Bezier curve interpolation with automatic control point generation
"""
# Generate automatic control point (simple upward curve)
result_frame = {
'people': [],
'canvas_width': frame_a.get('canvas_width', 512),
'canvas_height': frame_a.get('canvas_height', 768)
}
max_people = max(len(frame_a.get('people', [])), len(frame_b.get('people', [])))
for person_idx in range(max_people):
person_a = frame_a.get('people', [{}])[min(person_idx, len(frame_a.get('people', [])) - 1)] if frame_a.get('people') else {}
person_b = frame_b.get('people', [{}])[min(person_idx, len(frame_b.get('people', [])) - 1)] if frame_b.get('people') else {}
interpolated_person = {}
for keypoint_type in ['pose_keypoints_2d', 'face_keypoints_2d', 'hand_left_keypoints_2d', 'hand_right_keypoints_2d']:
if keypoint_type in person_a or keypoint_type in person_b:
kps_a = person_a.get(keypoint_type, [])
kps_b = person_b.get(keypoint_type, [])
interpolated_person[keypoint_type] = self._bezier_interpolate_keypoint_array(kps_a, kps_b, t)
result_frame['people'].append(interpolated_person)
return result_frame
def _bezier_interpolate_keypoint_array(self, kps_a: List[float], kps_b: List[float], t: float) -> List[float]:
"""
Bezier interpolation for keypoint arrays
"""
if not kps_a and not kps_b:
return []
max_len = max(len(kps_a), len(kps_b))
kps_a_padded = kps_a + [0.0] * (max_len - len(kps_a))
kps_b_padded = kps_b + [0.0] * (max_len - len(kps_b))
interpolated = []
for i in range(0, max_len, 3):
if i + 2 < max_len:
# Calculate control point (automatic curve generation)
x_start, y_start = kps_a_padded[i], kps_a_padded[i + 1]
x_end, y_end = kps_b_padded[i], kps_b_padded[i + 1]
# Control point: middle x, upward y offset
x_control = (x_start + x_end) / 2
y_offset = abs(x_end - x_start) * 0.25 # Proportional to distance
y_control = (y_start + y_end) / 2 - y_offset * 0.5 # Upward curve
# 3-point Bezier: B(t) = (1-t)²*P0 + 2*(1-t)*t*P1 + t²*P2
x = (1-t)**2 * x_start + 2*(1-t)*t * x_control + t**2 * x_end
y = (1-t)**2 * y_start + 2*(1-t)*t * y_control + t**2 * y_end
# Use minimum confidence
conf = min(kps_a_padded[i + 2], kps_b_padded[i + 2])
interpolated.extend([x, y, conf])
return interpolated
def _momentum_interpolation(self, prev_frame: Optional[Dict], current_frame: Dict,
next_frame: Dict, next_next_frame: Optional[Dict], t: float) -> Dict:
"""
Momentum-based interpolation considering velocity vectors
"""
# Fixed momentum weight for internal use
momentum_weight = 0.5
if not prev_frame:
# Fallback to bezier if no previous frame
return self._bezier_interpolation(current_frame, next_frame, t)
result_frame = {
'people': [],
'canvas_width': current_frame.get('canvas_width', 512),
'canvas_height': current_frame.get('canvas_height', 768)
}
max_people = max(len(current_frame.get('people', [])), len(next_frame.get('people', [])))
for person_idx in range(max_people):
person_prev = prev_frame.get('people', [{}])[min(person_idx, len(prev_frame.get('people', [])) - 1)] if prev_frame.get('people') else {}
person_curr = current_frame.get('people', [{}])[min(person_idx, len(current_frame.get('people', [])) - 1)] if current_frame.get('people') else {}
person_next = next_frame.get('people', [{}])[min(person_idx, len(next_frame.get('people', [])) - 1)] if next_frame.get('people') else {}
interpolated_person = {}
for keypoint_type in ['pose_keypoints_2d', 'face_keypoints_2d', 'hand_left_keypoints_2d', 'hand_right_keypoints_2d']:
if keypoint_type in person_curr or keypoint_type in person_next:
kps_prev = person_prev.get(keypoint_type, [])
kps_curr = person_curr.get(keypoint_type, [])
kps_next = person_next.get(keypoint_type, [])
interpolated_person[keypoint_type] = self._momentum_interpolate_keypoint_array(
kps_prev, kps_curr, kps_next, t, momentum_weight
)
result_frame['people'].append(interpolated_person)
return result_frame
def _momentum_interpolate_keypoint_array(self, kps_prev: List[float], kps_curr: List[float],
kps_next: List[float], t: float, momentum_weight: float = 0.5) -> List[float]:
"""
Momentum-based interpolation using velocity vectors
"""
if not kps_curr and not kps_next:
return []
max_len = max(len(kps_prev), len(kps_curr), len(kps_next))
kps_prev_padded = kps_prev + [0.0] * (max_len - len(kps_prev)) if kps_prev else [0.0] * max_len
kps_curr_padded = kps_curr + [0.0] * (max_len - len(kps_curr))
kps_next_padded = kps_next + [0.0] * (max_len - len(kps_next))
interpolated = []
for i in range(0, max_len, 3):
if i + 2 < max_len:
# Calculate velocity vectors
vel_in_x = kps_curr_padded[i] - kps_prev_padded[i]
vel_in_y = kps_curr_padded[i + 1] - kps_prev_padded[i + 1]
vel_out_x = kps_next_padded[i] - kps_curr_padded[i]
vel_out_y = kps_next_padded[i + 1] - kps_curr_padded[i + 1]
# Momentum vector calculation
momentum_x = momentum_weight * vel_in_x + (1 - momentum_weight) * vel_out_x
momentum_y = momentum_weight * vel_in_y + (1 - momentum_weight) * vel_out_y
# Bezier control point from momentum
control_x = kps_curr_padded[i] + momentum_x * 0.5
control_y = kps_curr_padded[i + 1] + momentum_y * 0.5
# 3-point Bezier with momentum-generated control point
x = (1-t)**2 * kps_curr_padded[i] + 2*(1-t)*t * control_x + t**2 * kps_next_padded[i]
y = (1-t)**2 * kps_curr_padded[i + 1] + 2*(1-t)*t * control_y + t**2 * kps_next_padded[i + 1]
# Use minimum confidence
conf = min(kps_curr_padded[i + 2], kps_next_padded[i + 2])
interpolated.extend([x, y, conf])
return interpolated