关于 comfyui-node-datatypes
ComfyUI 节点数据类型参考,面向开发自定义节点的开发者,说明张量、模型、3D 文件及界面控件的数据结构。
主要功能
- 01图像、遮罩与潜空间格式
- 02音频、视频及条件数据结构
- 03模型与 3D 类型参考
- 04节点界面控件类型
- 05特殊与动态类型说明
适用场景
- 01检查节点输入输出的数据格式
- 02处理 ComfyUI 图像或视频数据
- 03构建自定义节点的类型定义
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jtydhr88/ comfyui-custom-node-skills --skill comfyui-node-datatypes安装后在 Claude Code 中输入/comfyui-node-datatypes确认已加载。
详细介绍
ComfyUI 数据类型
ComfyUI 为节点输入和输出使用特定的数据类型。理解张量形状和数据格式至关重要。
完整类型参考
张量/数据类型
| 类型 | V3 类 | 格式 | 描述 |
|---|---|---|---|
| IMAGE | io.Image | torch.Tensor [B,H,W,C] float32 0-1 | RGB 图像批次 |
| MASK | io.Mask | torch.Tensor [H,W] 或 [B,H,W] float32 0-1 | 灰度蒙版 |
| LATENT | io.Latent | {"samples": Tensor[B,C,H,W] or [B,C,T,H,W], "noise_mask"?: Tensor, "batch_index"?: list[int], "type"?: str} | 潜空间(4D 图像 / 5D 视频) |
| CONDITIONING | io.Conditioning | list[tuple[Tensor, PooledDict]] | 包含池化输出的文本条件 |
| AUDIO | io.Audio | {"waveform": Tensor[B,C,T], "sample_rate": int} | 音频数据 |
| VIDEO | io.Video | VideoInput ABC | 视频数据(抽象基类) |
| SIGMAS | io.Sigmas | torch.Tensor 1D,长度为 steps+1 | 噪声调度 |
| NOISE | io.Noise | 具有 generate_noise() 的对象 | 噪声生成器 |
| LORA_MODEL | io.LoraModel | dict[str, torch.Tensor] | LoRA 权重增量 |
| LOSS_MAP | io.LossMap | {"loss": list[torch.Tensor]} | 损失映射 |
| TRACKS | io.Tracks | {"track_path": Tensor, "track_visibility": Tensor} | 运动跟踪数据 |
| WAN_CAMERA_EMBEDDING | io.WanCameraEmbedding | torch.Tensor | WAN 摄像头嵌入 |
| LATENT_OPERATION | io.LatentOperation | Callable[[Tensor], Tensor] | 潜变量变换函数 |
| TIMESTEPS_RANGE | io.TimestepsRange | tuple[int, int] | 0.0-1.0 范围 |
| DICT | io.Dict | dict | 通用字典 |
| ARRAY | io.Array | list | 通用列表/数组 |
| LAYERS | io.Layers | {"version": int, "canvas"?: (w, h), "layers": list[LayerItem]} | 图层堆栈(v0.31+);LayerItem:image 张量、type: "raster"、z_index,以及可选的 x、y、mask、name、opacity、blend_mode(io.Layers.BlendMode)、visible、flip_h/v、rotation、w、h |
模型类型(不透明,通常直接传递)
| 类型 | V3 类 | Python 类型 |
|---|---|---|
| MODEL | io.Model | ModelPatcher |
| CLIP | io.Clip | CLIP |
| VAE | io.Vae | VAE |
| CONTROL_NET | io.ControlNet | ControlNet |
| CLIP_VISION | io.ClipVision | ClipVisionModel |
| CLIP_VISION_OUTPUT | io.ClipVisionOutput | ClipVisionOutput |
| STYLE_MODEL | io.StyleModel | StyleModel |
| GLIGEN | io.Gligen | ModelPatcher(封装 Gligen) |
| UPSCALE_MODEL | io.UpscaleModel | ImageModelDescriptor |
| BACKGROUND_REMOVAL | io.BackgroundRemoval | BackgroundRemovalModel(例如 BiRefNet) |
| LATENT_UPSCALE_MODEL | io.LatentUpscaleModel | Any |
| SAMPLER | io.Sampler | Sampler |
| GUIDER | io.Guider | CFGGuider |
| HOOKS | io.Hooks | HookGroup |
| HOOK_KEYFRAMES | io.HookKeyframes | HookKeyframeGroup |
| MODEL_PATCH | io.ModelPatch | Any |
| AUDIO_ENCODER | io.AudioEncoder | Any |
| AUDIO_ENCODER_OUTPUT | io.AudioEncoderOutput | Any |
| PHOTOMAKER | io.Photomaker | Any |
| POINT | io.Point | Any |
| FACE_ANALYSIS | io.FaceAnalysis | Any |
| BBOX | io.BBOX | Any |
| SEGS | io.SEGS | Any |
3D 类型
| 类型 | V3 类 | Python 类型 | 描述 |
|---|---|---|---|
| MESH | io.Mesh | MESH(vertices, faces) | 包含顶点和面张量的 3D 网格 |
| VOXEL | io.Voxel | VOXEL(data) | 体素数据张量 |
| SPLAT | io.Splat | SPLAT | 高斯泼溅数据 |
| FILE_3D | io.File3DAny | File3D | 任意受支持的 3D 格式 |
| FILE_3D_GLB | io.File3DGLB | File3D | 二进制 glTF |
| FILE_3D_GLTF | io.File3DGLTF | File3D | 基于 JSON 的 glTF |
| FILE_3D_FBX | io.File3DFBX | File3D | FBX 格式 |
| FILE_3D_OBJ | io.File3DOBJ | File3D | OBJ 格式 |
| FILE_3D_STL | io.File3DSTL | File3D | STL 格式(用于 3D 打印) |
| FILE_3D_USDZ | io.File3DUSDZ | File3D | Apple AR 格式 |
| FILE_3D_PLY | io.File3DPLY | File3D | PLY(点云/泼溅) |
| FILE_3D_SPLAT | io.File3DSPLAT | File3D | .splat 高斯泼溅文件 |
| FILE_3D_SPZ | io.File3DSPZ | File3D | 压缩泼溅(.spz) |
| FILE_3D_KSPLAT | io.File3DKSPLAT | File3D | .ksplat 格式 |
| FILE_3D_SPLAT_ANY | io.File3DSplatAny | File3D | 任意泼溅格式 |
| FILE_3D_POINT_CLOUD_ANY | io.File3DPointCloudAny | File3D | 任意点云格式 |
| SVG | io.SVG | SVG | 可缩放矢量图形 |
| LOAD_3D | io.Load3D | Model3DDict(见下文) | 带渲染结果的 3D 模型 |
| LOAD_3D_ANIMATION | io.Load3DAnimation | 与 Load3D 相同 | 动画 3D 模型 |
| LOAD3D_CAMERA | io.Load3DCamera | CameraInfo(见下文) | 3D 相机信息 |
| LOAD3D_MODEL_INFO | io.Load3DModelInfo | list[Model3DTransform] | 每个模型的变换(位置/四元数/缩放) |
Load3D.Model3DDict:{"image": str, "mask": str, "normal": str, "camera_info": CameraInfo, "recording"?: str, "model_3d_info"?: list[Model3DTransform]}
Load3DCamera.CameraInfo(右手坐标系,Y 轴向上,相机朝 -Z 方向看):必需键为 position、target、zoom、cameraType('perspective' | 'orthographic');可选键为 quaternion(相机世界旋转)、useCustomUp(bool,v0.40+:使用四元数的上方向,而不是世界 +Y 方向)、fov(垂直视野角,单位为度,仅透视模式)、aspect、near、far、frustum(仅正交模式:{left, right, top, bottom})。
Load3DModelInfo.Model3DTransform:世界空间中的 {"position": dict, "quaternion": dict, "scale": dict}。
组件类型(创建 UI 控件)
| 类型 | V3 类 | Python 类型 | 描述 |
|---|---|---|---|
| INT | io.Int | int | 带最小值/最大值/步长的整数 |
| FLOAT | io.Float | float | 带最小值/最大值/步长/舍入设置的浮点数 |
| STRING | io.String | str | 文本(单行/多行) |
| BOOLEAN | io.Boolean | bool | 带标签的切换开关 |
| COMBO | io.Combo | str | 下拉选择 |
| COMBO (multi) | io.MultiCombo | list[str] | 多选下拉菜单 |
| COLOR | io.Color | str (hex) | 颜色选择器,默认值为 #ffffff |
| COLORS | io.Colors | list[str] (hex) | 调色板(颜色列表) |
| BOUNDING_BOX | io.BoundingBox | {"x": int, "y": int, "width": int, "height": int} | 矩形区域 |
| BOUNDING_BOXES | io.BoundingBoxes | list[{"x", "y", "width", "height", "metadata": dict}] | 多个带标签的区域 |
| CURVE | io.Curve | list[tuple[float, float]] | 样条曲线点 |
| RANGE | io.Range | RangeInput(最小值/最大值 + 可选中点) | 带渐变显示的色阶/范围编辑器 |
| IMAGECOMPARE | io.ImageCompare | dict | 图像比较控件 |
| WEBCAM | io.Webcam | str | 网络摄像头捕获控件 |
| HISTOGRAM | io.Histogram | list[int] | 直方图分箱计数 |
| VIDEO_EDIT | io.VideoEdit | {"trim"?: {start_time, duration}, "crop"?: {x, y, width, height}} | 视频修剪/裁剪编辑器(features=["trim","crop"]) |
| RESOLUTION_PREVIEW | io.ResolutionPreview | dict | 与其他控件按名称绑定的实时分辨率预览 |
| COMPOSITOR | io.Compositor | dict (LayerState) | 图层编辑器状态,默认值为 {} |
| CAMERA_INFO_STATE | io.CameraInfoState | dict | 相机编辑器状态(仅输入) |
特殊类型
| 类型 | V3 类 | 描述 |
|---|---|---|
* (ANY) | io.AnyType | 匹配任意类型 |
| COMFY_MULTITYPED_V3 | io.MultiType | 在一个输入上接受多个指定类型 |
| COMFY_MATCHTYPE_V3 | io.MatchType | 在输入/输出之间进行通用类型匹配 |
| COMFY_AUTOGROW_V3 | io.Autogrow | 动态增长的输入 |
| COMFY_DYNAMICCOMBO_V3 | io.DynamicCombo | 可根据选项显示子输入的组合框 |
| COMFY_DYNAMICSLOT_V3 | io.DynamicSlot | 连接后可显示子输入的连接槽(核心节点尚未使用) |
| COMFY_DYNAMICGROUP_V3 | (内部 _DynamicGroup) | 重复的控件行;未导出且不稳定 — 请勿使用(前端自 v1.57.0 起支持) |
| FLOW_CONTROL | io.FlowControl | 仅用于内部测试 |
| ACCUMULATION | io.Accumulation | 仅用于内部测试 |
IMAGE 类型
图像是形状为 [B, H, W, C] 的 torch.Tensor:
- B = 批次大小(单张图像为 1)
- H = 像素高度
- W = 像素宽度
- C = 通道数(RGB 为 3,取值范围为 0.0-1.0)
import torch
import numpy as np
from PIL import Image as PILImage
class ImageProcessor(io.ComfyNode):
@classmethod
def define_schema(cls):
return io.Schema(
node_id="ImageProcessor",
display_name="Image Processor",
category="image",
inputs=[io.Image.Input("image")],
outputs=[io.Image.Output("IMAGE")],
)
@classmethod
def execute(cls, image):
b, h, w, c = image.shape
result = torch.clamp(image * 1.5, 0.0, 1.0)
return io.NodeOutput(result)
加载/保存图像
from PIL import ImageOps
# Load from file → tensor
def load_image(path):
img = PILImage.open(path)
img = ImageOps.exif_transpose(img) # fix rotation from camera EXIF
if img.mode == "I": # handle 16-bit images
img = img.point(lambda i: i * (1 / 255))
img = img.convert("RGB")
return torch.from_numpy(np.array(img).astype(np.float32) / 255.0).unsqueeze(0)
# Tensor → save to file
def save_image(tensor, path):
if tensor.dim() == 4:
tensor = tensor[0]
PILImage.fromarray(np.clip(255.0 * tensor.cpu().numpy(), 0, 255).astype(np.uint8)).save(path)
# Batch operations
batch = torch.cat([img1, img2], dim=0) # stack into batch
single = image[i] # extract from batch [H,W,C]
single_batch = image.unsqueeze(0) # add batch dim [1,H,W,C]
MASK 类型
形状为 [H, W] 或 [B, H, W]、取值范围为 0.0-1.0 的 torch.Tensor。
# Invert mask
inverted = 1.0 - mask
# Mask ↔ Image conversion
alpha = mask.unsqueeze(0).unsqueeze(-1) # [1,H,W,1]
gray_mask = 0.299*img[:,:,:,0] + 0.587*img[:,:,:,1] + 0.114*img[:,:,:,2]
image_from_mask = mask.unsqueeze(-1).repeat(1, 1, 1, 3) # [B,H,W,3]
# Ensure batch dim
if mask.dim() == 2:
mask = mask.unsqueeze(0) # [1, H, W]
LATENT 类型
含有类型化键的字典:
class LatentDict(TypedDict):
samples: torch.Tensor # [B, C, H, W] (image) or [B, C, T, H, W] (video) - required
noise_mask: NotRequired[torch.Tensor]
batch_index: NotRequired[list[int]]
type: NotRequired[str] # only for "audio", "hunyuan3dv2"
图像模型(SD1.5、SDXL、SD3、Flux):4D [B, C, H, W] — SD1.5/SDXL 为 4 个通道,SD3/Flux 为 16 个通道。潜空间尺寸为像素尺寸的 1/8。
视频模型(Hunyuan Video、Wan、Cosmos、LTX Video、Mochi):5D [B, C, T, H, W] — T 为时间(帧)维度。
samples = latent["samples"]
# Check dimensionality:
if samples.ndim == 5:
B, C, T, H, W = samples.shape # video latent
else:
B, C, H, W = samples.shape # image latent
# Always preserve extra keys when modifying:
result = latent.copy()
result["samples"] = modified_samples
CONDITIONING 类型
list[tuple[Tensor, PooledDict]] — 由 (cond_tensor, metadata_dict) 对组成的列表。
PooledDict 包含许多适用于不同模型的可选键:
class PooledDict(TypedDict):
pooled_output: torch.Tensor
control: NotRequired[ControlNet]
area: NotRequired[tuple[int, ...]]
strength: NotRequired[float] # default 1.0
mask: NotRequired[torch.Tensor]
start_percent: NotRequired[float] # 0.0-1.0
end_percent: NotRequired[float] # 0.0-1.0
guidance: NotRequired[float] # Flux-like models
hooks: NotRequired[HookGroup]
# ... many more model-specific keys (SDXL, SVD, WAN, etc.)
合并 conditioning:result = cond_a + cond_b(列表拼接)。
VIDEO 类型
VideoInput 是抽象基类,包含以下方法:
class VideoInput(ABC):
def get_components(self) -> VideoComponents # images tensor + audio + frame_rate
def save_to(self, path, format=VideoContainer.AUTO, codec=VideoCodec.AUTO, metadata=None,
bit_depth=None, # None keeps native depth
crf=None, # H.264/AV1 CRF; None -> 18 (H.264) / 24 (AV1)
color_space=None, # "sRGB" | "HDR" (BT.2020 HLG) | "HDR PQ"
preset=None) # H.264 encoder preset, e.g. "ultrafast"
def as_trimmed(self, start_time=None, duration=None, strict_duration=False) -> VideoInput | None
def as_cropped(self, x=0, y=0, width=0, height=0) -> VideoInput # clamped, even-aligned; empty/full rect = self
def get_stream_source(self) -> str | BytesIO
def get_dimensions(self) -> tuple[int, int] # (width, height)
def get_duration(self) -> float # seconds
def get_frame_count(self) -> int
def get_frame_rate(self) -> Fraction
def get_container_format(self) -> str
def get_bit_depth(self) -> int # max component bits of the stream, e.g. 8/10/12 (default 8)
def get_color_space(self) -> str # "sRGB" | "HDR" | "HDR PQ" | "auto" (unknown)
端到端支持高位深和 HDR(v0.34+):加载器提供 get_bit_depth() / get_color_space(),保存节点会保留这些属性。编解码器:Types.VideoCodec.AUTO/H264/AV1;容器:Types.VideoContainer.AUTO/MP4/MKV/WEBM。
具体实现(通过 from comfy_api.latest import InputImpl 导入):
VideoFromFile(file, *, start_time=0, duration=0, crop=None)— 路径或 BytesIO;crop=(x, y, w, h)VideoFromComponents(components, bit_depth=8, color_space="sRGB")— 从张量创建VideoFromList(videos, complete_audio=None, codec=AUTO)— 拼接(v0.36+)
3D 类型
File3D
from comfy_api.latest import Types
# File3D wraps a 3D file (disk path or BytesIO stream)
file_3d = Types.File3D(source="/path/to/model.glb", file_format="glb")
file_3d.format # "glb"
file_3d.is_disk_backed # True
file_3d.get_data() # BytesIO
file_3d.get_bytes() # raw bytes
file_3d.save_to("/output/model.glb")
MESH、VOXEL 和 SPLAT
from comfy_api.latest import Types
mesh = Types.MESH(vertices=torch.tensor(...), faces=torch.tensor(...)) # (B,N,3), (B,M,3)
voxel = Types.VOXEL(data=torch.tensor(...), voxel_colors=None, resolution=None)
splat = Types.SPLAT(...) # gaussian splat data
MESH 可选字段(请通过关键字传递 — metallic_roughness 在 v0.34 中插入到 vertex_counts 前,导致位置参数调用失效):uvs (B,N,2)、vertex_colors (B,N,3|4)、texture (B,H,W,3 baseColor)、metallic_roughness (B,H,W,3;G=roughness,B=metallic)、vertex_counts/face_counts (B,) 用于填充批次的实际长度(两者必须同时提供或都不提供)、unlit、normals (B,N,3)、tangents (B,N,4)、normal_map、occlusion_in_mr、material (dict)、emissive。
具有特殊功能的控件类型
Color
io.Color.Input("color", default="#ff0000", socketless=True)
# Value is a hex string like "#ff0000"
Colors(调色板)
io.Colors.Input("palette", default=["#ff0000", "#00ff00"], socketless=True)
# Value is list[str] of hex colors
BoundingBox
io.BoundingBox.Input("bbox",
default={"x": 0, "y": 0, "width": 512, "height": 512},
socketless=True,
component="my_component", # optional custom UI component
)
# Value is {"x": int, "y": int, "width": int, "height": int}
BoundingBoxes(多个区域)
io.BoundingBoxes.Input("regions", default=[], socketless=True)
# Value is list of {"x": int, "y": int, "width": int, "height": int, "metadata": dict}
Curve
from comfy_api.input import CurveInput
io.Curve.Input("curve",
default=[(0.0, 0.0), (1.0, 1.0)], # linear
socketless=True,
)
# In execute(), normalize the raw value first:
curve = CurveInput.from_raw(curve)
Range(色阶编辑器)
from comfy_api.input import RangeInput
io.Range.Input("levels",
default={"min": 0.0, "max": 1.0},
display=None, # widget visualization mode
gradient_stops=None, # gradient background for the slider
show_midpoint=True, # show gamma midpoint handle
midpoint_scale=None,
value_min=0.0, value_max=1.0, # UI bounds
)
# In execute(), normalize with RangeInput.from_raw(value):
# .min_val, .max_val, .midpoint (gamma = -log2(midpoint), 0.5 = linear)
# .to_lut(size) generates a GIMP-style levels lookup table
MultiCombo
io.MultiCombo.Input("tags",
options=["tag1", "tag2", "tag3"],
default=["tag1"],
placeholder="Select tags...",
chip=True, # show as chips
)
# Value is list[str]
Webcam
io.Webcam.Input("webcam_capture")
# Value is str (captured image data)
ImageCompare
io.ImageCompare.Input("comparison", socketless=True)
# Value is dict
自定义类型
# Simple: create inline custom type
MyData = io.Custom("MY_DATA_TYPE")
# Use in inputs/outputs
io.Schema(
inputs=[MyData.Input("data")],
outputs=[MyData.Output("MY_DATA")],
)
进阶:@comfytype 装饰器
适用于带有类型提示或自定义 Input/Output 类的自定义类型:
from comfy_api.latest._io import comfytype, ComfyTypeIO
@comfytype(io_type="MY_DATA_TYPE")
class MyData(ComfyTypeIO):
Type = dict[str, Any] # type hint for the data
RAM 缓存大小计算:默认的 RAM 压力缓存会计算输出中的 CPU 张量(也包括列表/元组/字典中的张量);不透明对象按较小的默认值计算。对于持有大型张量的自定义类,请添加 def _comfy_cache_tensors(self): return (self.a, self.b)(核心 MESH/VOXEL/SPLAT 也采用此做法,v0.35+),以便正确计算其大小并进行淘汰。
AnyType / 通配符
# Accept any single type (always a connection input, no widget)
io.AnyType.Input("anything")
# Accept specific multiple types
io.MultiType.Input("data", types=[io.Image, io.Mask, io.Latent])
# MultiType with widget override (shows widget for first type)
io.MultiType.Input(
io.Float.Input("value", default=1.0),
types=[io.Float, io.Int],
)
从 comfy_api.latest 导入
from comfy_api.latest import (
ComfyExtension, # extension registration
ComfyAPI, # runtime API (progress, node replacement)
io, # all io types (io.Image, io.Schema, io.ComfyNode, etc.)
ui, # UI output helpers (ui.PreviewImage, ui.SavedImages, etc.)
Input, # Input.Image (ImageInput), Input.Audio, Input.Mask, Input.Latent, Input.Video
InputImpl, # InputImpl.VideoFromFile, .VideoFromComponents, .VideoFromList
Types, # Types.MESH, Types.VOXEL, Types.File3D, Types.VideoCodec, etc.
)
张量安全
检查张量是否存在时,始终使用 is not None,不要使用真值判断:
# CORRECT
if image is not None:
process(image)
# WRONG — multi-element tensors don't support bool()
if image: # raises RuntimeError
process(image)
# For boolean conditions on tensors, use .all() or .any()
if (mask > 0.5).all():
...
类型转换模式
# IMAGE [B,H,W,C] → MASK [B,H,W]
mask = 0.299 * image[:,:,:,0] + 0.587 * image[:,:,:,1] + 0.114 * image[:,:,:,2]
# MASK [B,H,W] → IMAGE [B,H,W,C]
image = mask.unsqueeze(-1).repeat(1, 1, 1, 3)
# Resize image tensor
import torch.nn.functional as F
resized = F.interpolate(
image.permute(0, 3, 1, 2), # [B,C,H,W] for interpolate
size=(new_h, new_w), mode='bilinear', align_corners=False
).permute(0, 2, 3, 1) # back to [B,H,W,C]
另请参阅
comfyui-node-basics- 节点类结构与注册comfyui-node-inputs- 输入配置详情(控件选项)comfyui-node-outputs- 输出类型与 UI 输出comfyui-node-advanced- MatchType、MultiType、Autogrow、DynamicCombo