164 lines
6.3 KiB
Markdown
164 lines
6.3 KiB
Markdown
|
|
# Documentation
|
|
- Class name: SaltKeyframeVisualizer
|
|
- Category: SALT/Scheduling/Util
|
|
- Output node: True
|
|
- Repo Ref: https://github.com/BennyKok/comfyui-deploy-diffusers-extras
|
|
|
|
SaltKeyframeVisualizer节点用于可视化随时间变化的关键帧数据,帮助洞察动画或效果的动态变化。它生成关键帧值随帧数变化的可视化表示,有助于评估和调整音视频项目中动画的时间和强度。
|
|
|
|
# Input types
|
|
## Required
|
|
- schedule_list
|
|
- 需要可视化的关键帧值列表,代表随时间变化的调度变化。对理解动画或效果的进展至关重要。
|
|
- Comfy dtype: LIST
|
|
- Python dtype: List[float]
|
|
## Optional
|
|
- start_frame
|
|
- 指定可视化的起始帧,允许集中分析动画的特定片段。
|
|
- Comfy dtype: INT
|
|
- Python dtype: int
|
|
- end_frame
|
|
- 定义可视化的结束帧,允许自定义可视化的范围。
|
|
- Comfy dtype: INT
|
|
- Python dtype: int
|
|
- simulate_stereo
|
|
- 指示是否通过镜像关键帧值来模拟立体视觉效果,增强可视化的深度。
|
|
- Comfy dtype: BOOLEAN
|
|
- Python dtype: bool
|
|
- frame_rate
|
|
- 动画或效果预期播放的帧率,影响可视化的时间和流畅度。
|
|
- Comfy dtype: INT
|
|
- Python dtype: float
|
|
- schedule_list_a
|
|
- 用于额外可视化的可选关键帧值列表,允许比较或叠加多个动画。
|
|
- Comfy dtype: LIST
|
|
- Python dtype: List[float]
|
|
- schedule_list_b
|
|
- 另一个用于可视化的可选关键帧值列表,实现更复杂的比较或分层动画。
|
|
- Comfy dtype: LIST
|
|
- Python dtype: List[float]
|
|
- schedule_list_c
|
|
- 第三个用于可视化的可选关键帧值列表,进一步扩展复杂动画分析的能力。
|
|
- Comfy dtype: LIST
|
|
- Python dtype: List[float]
|
|
|
|
# Output types
|
|
- ui
|
|
- 生成关键帧数据的可视化表示,包括指标和图表,以便分析和优化动画。
|
|
|
|
|
|
## Usage tips
|
|
- Infra type: `CPU`
|
|
- Common nodes: unknown
|
|
|
|
|
|
## Source code
|
|
```python
|
|
class SaltKeyframeVisualizer:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
input_types = {
|
|
"required": {
|
|
"schedule_list": ("LIST",),
|
|
},
|
|
"optional": {
|
|
"start_frame": ("INT", {"min": 0, "default": 0}),
|
|
"end_frame": ("INT", {"min": 0, "default": -1}),
|
|
"simulate_stereo": ("BOOLEAN", {"default": False}),
|
|
"frame_rate": ("INT", {"min": 1, "default": 24}),
|
|
"schedule_list_a": ("LIST", {"default": None}),
|
|
"schedule_list_b": ("LIST", {"default": None}),
|
|
"schedule_list_c": ("LIST", {"default": None}),
|
|
}
|
|
}
|
|
return input_types
|
|
|
|
RETURN_TYPES = ()
|
|
RETURN_NAMES = ()
|
|
OUTPUT_NODE = True
|
|
FUNCTION = "visualize_keyframes"
|
|
CATEGORY = "SALT/Scheduling/Util"
|
|
|
|
def visualize_keyframes(self, schedule_list, start_frame=0, end_frame=-1, simulate_stereo=False, frame_rate=24.0, schedule_list_a=None, schedule_list_b=None, schedule_list_c=None):
|
|
TEMP = folder_paths.get_temp_directory()
|
|
os.makedirs(TEMP, exist_ok=True)
|
|
|
|
schedule_lists = [schedule_list, schedule_list_a, schedule_list_b, schedule_list_c]
|
|
colors = ['tab:blue', 'tab:orange', 'tab:green', 'tab:red']
|
|
schedule_names = ['Schedule List', 'Schedule List A', 'Schedule List B', 'Schedule List C']
|
|
metrics_data = []
|
|
|
|
plt.figure(figsize=(10, 14 if simulate_stereo else 8))
|
|
|
|
for i, sched_list in enumerate(schedule_lists):
|
|
if sched_list is not None:
|
|
if end_frame == -1 or end_frame > len(sched_list):
|
|
end_frame = len(sched_list)
|
|
|
|
num_frames = max(2, end_frame - start_frame)
|
|
frames = np.linspace(start_frame, end_frame - 1, num=num_frames, endpoint=True)
|
|
keyframe_values = np.array(sched_list[start_frame:end_frame])
|
|
|
|
plt.plot(frames, keyframe_values, color=colors[i], linewidth=0.5, label=schedule_names[i] + ' Left')
|
|
if simulate_stereo:
|
|
plt.plot(frames, -keyframe_values, color=colors[i], linewidth=0.5, linestyle='dashed', label=schedule_names[i] + ' Right')
|
|
plt.fill_between(frames, keyframe_values, 0, color=colors[i], alpha=0.3)
|
|
plt.fill_between(frames, -keyframe_values, 0, color=colors[i], alpha=0.3)
|
|
|
|
metrics = {
|
|
"Max": np.round(np.max(keyframe_values), 2),
|
|
"Min": np.round(np.min(keyframe_values), 2),
|
|
"Sum": np.round(np.sum(keyframe_values), 2),
|
|
"Avg": np.round(np.mean(keyframe_values), 2),
|
|
"Abs Sum": np.round(np.sum(np.abs(keyframe_values)), 2),
|
|
"Abs Avg": np.round(np.mean(np.abs(keyframe_values)), 2),
|
|
"Duration": (num_frames / frame_rate)
|
|
}
|
|
metrics_data.append((schedule_names[i], metrics))
|
|
|
|
plt.title('Schedule Visualization')
|
|
plt.xlabel('Frame')
|
|
plt.ylabel('Value')
|
|
plt.legend(loc='upper right')
|
|
|
|
metrics_text = "Metric Values:\n"
|
|
for name, data in metrics_data:
|
|
metrics_text += f"{name}: "
|
|
metrics_text += ' | '.join([f"{k}: {v}" for k, v in data.items()])
|
|
metrics_text += "\n"
|
|
|
|
plt.figtext(0.5, -0.2 if simulate_stereo else -0.1, metrics_text, ha="center", fontsize=12,
|
|
bbox={"facecolor": "lightblue", "alpha": 0.5, "pad": 5}, wrap=True)
|
|
|
|
filename = str(uuid.uuid4()) + "_keyframe_visualization.png"
|
|
file_path = os.path.join(TEMP, filename)
|
|
|
|
plt.savefig(file_path, bbox_inches="tight", pad_inches=1 if simulate_stereo else 0.1)
|
|
plt.close()
|
|
|
|
return {
|
|
"ui": {
|
|
"images": [
|
|
{
|
|
"filename": filename,
|
|
"subfolder": "",
|
|
"type": "temp"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
@staticmethod
|
|
def gen_hash(input_dict):
|
|
sorted_json = json.dumps(input_dict, sort_keys=True)
|
|
hash_obj = hashlib.sha256()
|
|
hash_obj.update(sorted_json.encode('utf-8'))
|
|
return hash_obj.hexdigest()
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, **kwargs):
|
|
return cls.gen_hash(kwargs)
|
|
|
|
```
|