mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-05-30 19:07:25 +08:00
Merge branch 'master' into feat/api-nodes/Tripo3D-P1
This commit is contained in:
commit
41229705d7
24
.github/workflows/detect-unreviewed-merge.yml
vendored
Normal file
24
.github/workflows/detect-unreviewed-merge.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
name: Detect Unreviewed Merge
|
||||||
|
|
||||||
|
# SOC 2 compliance — reusable workflow lives in Comfy-Org/github-workflows,
|
||||||
|
# tracking issues are filed in Comfy-Org/unreviewed-merges.
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: detect-unreviewed-merge-${{ github.sha }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
detect:
|
||||||
|
uses: Comfy-Org/github-workflows/.github/workflows/detect-unreviewed-merge.yml@4d9cb6b87f953bb7cd69954280e1465fb9bd2040 # v1
|
||||||
|
with:
|
||||||
|
approval-mode: latest-per-reviewer
|
||||||
|
secrets:
|
||||||
|
UNREVIEWED_MERGES_TOKEN: ${{ secrets.UNREVIEWED_MERGES_TOKEN }}
|
||||||
@ -55,12 +55,7 @@ class BackgroundRemovalModel():
|
|||||||
out = torch.nn.functional.interpolate(out, size=(H, W), mode="bicubic", antialias=False)
|
out = torch.nn.functional.interpolate(out, size=(H, W), mode="bicubic", antialias=False)
|
||||||
|
|
||||||
mask = out.sigmoid().to(device=comfy.model_management.intermediate_device(), dtype=comfy.model_management.intermediate_dtype())
|
mask = out.sigmoid().to(device=comfy.model_management.intermediate_device(), dtype=comfy.model_management.intermediate_dtype())
|
||||||
if mask.ndim == 3:
|
return mask.squeeze(1) # (B, 1, H, W) -> (B, H, W)
|
||||||
mask = mask.unsqueeze(0)
|
|
||||||
if mask.shape[1] != 1:
|
|
||||||
mask = mask.movedim(-1, 1)
|
|
||||||
|
|
||||||
return mask
|
|
||||||
|
|
||||||
|
|
||||||
def load_background_removal_model(sd):
|
def load_background_removal_model(sd):
|
||||||
|
|||||||
@ -1,5 +1,20 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
|
_CK_STOCHASTIC_ROUNDING_AVAILABLE = False
|
||||||
|
try:
|
||||||
|
import comfy_kitchen as ck
|
||||||
|
_ck_stochastic_rounding_fp8 = ck.stochastic_rounding_fp8
|
||||||
|
_CK_STOCHASTIC_ROUNDING_AVAILABLE = True
|
||||||
|
except (AttributeError, ImportError):
|
||||||
|
logging.warning("comfy_kitchen does not support stochastic FP8 rounding, please update comfy_kitchen.")
|
||||||
|
|
||||||
|
if not _CK_STOCHASTIC_ROUNDING_AVAILABLE:
|
||||||
|
def _ck_stochastic_rounding_fp8(value, rng, dtype):
|
||||||
|
raise NotImplementedError("comfy_kitchen does not support stochastic FP8 rounding")
|
||||||
|
|
||||||
|
|
||||||
def calc_mantissa(abs_x, exponent, normal_mask, MANTISSA_BITS, EXPONENT_BIAS, generator=None):
|
def calc_mantissa(abs_x, exponent, normal_mask, MANTISSA_BITS, EXPONENT_BIAS, generator=None):
|
||||||
mantissa_scaled = torch.where(
|
mantissa_scaled = torch.where(
|
||||||
normal_mask,
|
normal_mask,
|
||||||
@ -57,6 +72,10 @@ def stochastic_rounding(value, dtype, seed=0):
|
|||||||
if dtype == torch.float8_e4m3fn or dtype == torch.float8_e5m2:
|
if dtype == torch.float8_e4m3fn or dtype == torch.float8_e5m2:
|
||||||
generator = torch.Generator(device=value.device)
|
generator = torch.Generator(device=value.device)
|
||||||
generator.manual_seed(seed)
|
generator.manual_seed(seed)
|
||||||
|
if _CK_STOCHASTIC_ROUNDING_AVAILABLE:
|
||||||
|
rng = torch.randint(0, 256, value.size(), dtype=torch.uint8, layout=value.layout, device=value.device, generator=generator)
|
||||||
|
return _ck_stochastic_rounding_fp8(value, rng, dtype)
|
||||||
|
|
||||||
output = torch.empty_like(value, dtype=dtype)
|
output = torch.empty_like(value, dtype=dtype)
|
||||||
num_slices = max(1, (value.numel() / (4096 * 4096)))
|
num_slices = max(1, (value.numel() / (4096 * 4096)))
|
||||||
slice_size = max(1, round(value.shape[0] / num_slices))
|
slice_size = max(1, round(value.shape[0] / num_slices))
|
||||||
|
|||||||
@ -762,21 +762,32 @@ class Accumulation(ComfyTypeIO):
|
|||||||
@comfytype(io_type="LOAD3D_CAMERA")
|
@comfytype(io_type="LOAD3D_CAMERA")
|
||||||
class Load3DCamera(ComfyTypeIO):
|
class Load3DCamera(ComfyTypeIO):
|
||||||
class CameraInfo(TypedDict):
|
class CameraInfo(TypedDict):
|
||||||
position: dict[str, float | int]
|
# Coordinate system: right-handed, Y-up, camera looks down -Z
|
||||||
target: dict[str, float | int]
|
position: dict[str, float | int] # scene units
|
||||||
zoom: int
|
target: dict[str, float | int] # scene units; OrbitControls focus point
|
||||||
cameraType: str
|
zoom: float | int # dimensionless, 1 = 100%
|
||||||
quaternion: NotRequired[dict[str, float | int]]
|
cameraType: str # 'perspective' | 'orthographic'
|
||||||
rotation: NotRequired[dict[str, float | int | str]]
|
quaternion: NotRequired[dict[str, float | int]] # normalized, dimensionless; camera world rotation
|
||||||
fov: NotRequired[float | int]
|
fov: NotRequired[float | int] # degrees, vertical FOV (perspective only)
|
||||||
aspect: NotRequired[float | int]
|
aspect: NotRequired[float | int] # width / height (perspective only)
|
||||||
near: NotRequired[float | int]
|
near: NotRequired[float | int] # scene units
|
||||||
far: NotRequired[float | int]
|
far: NotRequired[float | int] # scene units
|
||||||
frustum: NotRequired[dict[str, float | int]]
|
frustum: NotRequired[dict[str, float | int]] # orthographic only: {left, right, top, bottom} in scene units
|
||||||
|
|
||||||
Type = CameraInfo
|
Type = CameraInfo
|
||||||
|
|
||||||
|
|
||||||
|
@comfytype(io_type="LOAD3D_MODEL_INFO")
|
||||||
|
class Load3DModelInfo(ComfyTypeIO):
|
||||||
|
class Model3DTransform(TypedDict):
|
||||||
|
# Coordinate system: right-handed, Y-up, world space
|
||||||
|
position: dict[str, float | int] # scene units
|
||||||
|
quaternion: dict[str, float | int] # normalized, dimensionless; world rotation
|
||||||
|
scale: dict[str, float | int] # dimensionless multiplier
|
||||||
|
|
||||||
|
Type = list[Model3DTransform]
|
||||||
|
|
||||||
|
|
||||||
@comfytype(io_type="LOAD_3D")
|
@comfytype(io_type="LOAD_3D")
|
||||||
class Load3D(ComfyTypeIO):
|
class Load3D(ComfyTypeIO):
|
||||||
"""3D models are stored as a dictionary."""
|
"""3D models are stored as a dictionary."""
|
||||||
@ -786,6 +797,7 @@ class Load3D(ComfyTypeIO):
|
|||||||
normal: str
|
normal: str
|
||||||
camera_info: Load3DCamera.CameraInfo
|
camera_info: Load3DCamera.CameraInfo
|
||||||
recording: NotRequired[str]
|
recording: NotRequired[str]
|
||||||
|
model_3d_info: NotRequired[list[Load3DModelInfo.Model3DTransform]]
|
||||||
|
|
||||||
Type = Model3DDict
|
Type = Model3DDict
|
||||||
|
|
||||||
@ -2298,6 +2310,7 @@ __all__ = [
|
|||||||
"FlowControl",
|
"FlowControl",
|
||||||
"Accumulation",
|
"Accumulation",
|
||||||
"Load3DCamera",
|
"Load3DCamera",
|
||||||
|
"Load3DModelInfo",
|
||||||
"Load3D",
|
"Load3D",
|
||||||
"Load3DAnimation",
|
"Load3DAnimation",
|
||||||
"Photomaker",
|
"Photomaker",
|
||||||
|
|||||||
@ -206,7 +206,7 @@ class BeebleSwitchXVideoEdit(IO.ComfyNode):
|
|||||||
return IO.Schema(
|
return IO.Schema(
|
||||||
node_id="BeebleSwitchXVideoEdit",
|
node_id="BeebleSwitchXVideoEdit",
|
||||||
display_name="Beeble SwitchX Video Edit",
|
display_name="Beeble SwitchX Video Edit",
|
||||||
category="api node/video/Beeble",
|
category="video/partner/Beeble",
|
||||||
description=(
|
description=(
|
||||||
"Edit a video with Beeble SwitchX. Switches anything in the scene (background, "
|
"Edit a video with Beeble SwitchX. Switches anything in the scene (background, "
|
||||||
"lighting, costume) while preserving the original subject's pixels and motion. "
|
"lighting, costume) while preserving the original subject's pixels and motion. "
|
||||||
@ -302,7 +302,7 @@ class BeebleSwitchXImageEdit(IO.ComfyNode):
|
|||||||
return IO.Schema(
|
return IO.Schema(
|
||||||
node_id="BeebleSwitchXImageEdit",
|
node_id="BeebleSwitchXImageEdit",
|
||||||
display_name="Beeble SwitchX Image Edit",
|
display_name="Beeble SwitchX Image Edit",
|
||||||
category="api node/image/Beeble",
|
category="image/partner/Beeble",
|
||||||
description=(
|
description=(
|
||||||
"Edit a single image with Beeble SwitchX. Switches anything in the scene "
|
"Edit a single image with Beeble SwitchX. Switches anything in the scene "
|
||||||
"(background, lighting, costume) while preserving the original subject's pixels. "
|
"(background, lighting, costume) while preserving the original subject's pixels. "
|
||||||
|
|||||||
@ -58,7 +58,6 @@ class GrokImageNode(IO.ComfyNode):
|
|||||||
"grok-imagine-image-quality",
|
"grok-imagine-image-quality",
|
||||||
"grok-imagine-image-pro",
|
"grok-imagine-image-pro",
|
||||||
"grok-imagine-image",
|
"grok-imagine-image",
|
||||||
"grok-imagine-image-beta",
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
IO.String.Input(
|
IO.String.Input(
|
||||||
@ -233,7 +232,6 @@ class GrokImageEditNode(IO.ComfyNode):
|
|||||||
"grok-imagine-image-quality",
|
"grok-imagine-image-quality",
|
||||||
"grok-imagine-image-pro",
|
"grok-imagine-image-pro",
|
||||||
"grok-imagine-image",
|
"grok-imagine-image",
|
||||||
"grok-imagine-image-beta",
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
IO.Image.Input("image", display_name="images"),
|
IO.Image.Input("image", display_name="images"),
|
||||||
@ -506,7 +504,7 @@ class GrokVideoNode(IO.ComfyNode):
|
|||||||
category="video/partner/Grok",
|
category="video/partner/Grok",
|
||||||
description="Generate video from a prompt or an image",
|
description="Generate video from a prompt or an image",
|
||||||
inputs=[
|
inputs=[
|
||||||
IO.Combo.Input("model", options=["grok-imagine-video", "grok-imagine-video-beta"]),
|
IO.Combo.Input("model", options=["grok-imagine-video"]),
|
||||||
IO.String.Input(
|
IO.String.Input(
|
||||||
"prompt",
|
"prompt",
|
||||||
multiline=True,
|
multiline=True,
|
||||||
@ -576,8 +574,6 @@ class GrokVideoNode(IO.ComfyNode):
|
|||||||
seed: int,
|
seed: int,
|
||||||
image: Input.Image | None = None,
|
image: Input.Image | None = None,
|
||||||
) -> IO.NodeOutput:
|
) -> IO.NodeOutput:
|
||||||
if model == "grok-imagine-video-beta":
|
|
||||||
model = "grok-imagine-video"
|
|
||||||
image_url = None
|
image_url = None
|
||||||
if image is not None:
|
if image is not None:
|
||||||
if get_number_of_images(image) != 1:
|
if get_number_of_images(image) != 1:
|
||||||
@ -618,7 +614,7 @@ class GrokVideoEditNode(IO.ComfyNode):
|
|||||||
category="video/partner/Grok",
|
category="video/partner/Grok",
|
||||||
description="Edit an existing video based on a text prompt.",
|
description="Edit an existing video based on a text prompt.",
|
||||||
inputs=[
|
inputs=[
|
||||||
IO.Combo.Input("model", options=["grok-imagine-video", "grok-imagine-video-beta"]),
|
IO.Combo.Input("model", options=["grok-imagine-video"]),
|
||||||
IO.String.Input(
|
IO.String.Input(
|
||||||
"prompt",
|
"prompt",
|
||||||
multiline=True,
|
multiline=True,
|
||||||
|
|||||||
@ -157,7 +157,7 @@ class LoadImageTextDataSetFromFolderNode(io.ComfyNode):
|
|||||||
return io.NodeOutput(output_tensor, captions)
|
return io.NodeOutput(output_tensor, captions)
|
||||||
|
|
||||||
|
|
||||||
def save_images_to_folder(image_list, output_dir, prefix="image"):
|
def save_images_to_folder(image_list, output_dir, prefix="image", overwrite=True):
|
||||||
"""Utility function to save a list of image tensors to disk.
|
"""Utility function to save a list of image tensors to disk.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -197,7 +197,11 @@ def save_images_to_folder(image_list, output_dir, prefix="image"):
|
|||||||
raise ValueError(f"Expected torch.Tensor, got {type(img_tensor)}")
|
raise ValueError(f"Expected torch.Tensor, got {type(img_tensor)}")
|
||||||
|
|
||||||
# Save image
|
# Save image
|
||||||
filename = f"{prefix}_{idx:05d}.png"
|
if overwrite:
|
||||||
|
filename = f"{prefix}_{idx:05d}.png"
|
||||||
|
else:
|
||||||
|
_, _, counter, _, resolved_prefix = folder_paths.get_save_image_path(prefix, output_dir)
|
||||||
|
filename = f"{resolved_prefix}_{counter:05}_{idx:05d}.png"
|
||||||
filepath = os.path.join(output_dir, filename)
|
filepath = os.path.join(output_dir, filename)
|
||||||
img.save(filepath)
|
img.save(filepath)
|
||||||
saved_files.append(filename)
|
saved_files.append(filename)
|
||||||
@ -230,19 +234,26 @@ class SaveImageDataSetToFolderNode(io.ComfyNode):
|
|||||||
tooltip="Prefix for saved image filenames.",
|
tooltip="Prefix for saved image filenames.",
|
||||||
advanced=True,
|
advanced=True,
|
||||||
),
|
),
|
||||||
|
io.Combo.Input(
|
||||||
|
"mode",
|
||||||
|
default="overwrite",
|
||||||
|
options=["overwrite", "increment"],
|
||||||
|
tooltip="Whether to overwrite existing files or increment filenames to avoid overwriting."
|
||||||
|
),
|
||||||
],
|
],
|
||||||
outputs=[],
|
outputs=[],
|
||||||
is_deprecated=True, # This node is redundant and superseded by existing Save Image nodes where the target folder can be specified in the filename_prefix
|
is_deprecated=True, # This node is redundant and superseded by existing Save Image nodes where the target folder can be specified in the filename_prefix
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, images, folder_name, filename_prefix):
|
def execute(cls, images, folder_name, filename_prefix, mode):
|
||||||
# Extract scalar values
|
# Extract scalar values
|
||||||
folder_name = folder_name[0]
|
folder_name = folder_name[0]
|
||||||
filename_prefix = filename_prefix[0]
|
filename_prefix = filename_prefix[0]
|
||||||
|
mode = mode[0]
|
||||||
|
|
||||||
output_dir = os.path.join(folder_paths.get_output_directory(), folder_name)
|
output_dir = os.path.join(folder_paths.get_output_directory(), folder_name)
|
||||||
saved_files = save_images_to_folder(images, output_dir, filename_prefix)
|
saved_files = save_images_to_folder(images, output_dir, filename_prefix, mode=='overwrite')
|
||||||
|
|
||||||
logging.info(f"Saved {len(saved_files)} images to {output_dir}.")
|
logging.info(f"Saved {len(saved_files)} images to {output_dir}.")
|
||||||
return io.NodeOutput()
|
return io.NodeOutput()
|
||||||
@ -278,18 +289,25 @@ class SaveImageTextDataSetToFolderNode(io.ComfyNode):
|
|||||||
tooltip="Prefix for saved image filenames.",
|
tooltip="Prefix for saved image filenames.",
|
||||||
advanced=True,
|
advanced=True,
|
||||||
),
|
),
|
||||||
|
io.Combo.Input(
|
||||||
|
"mode",
|
||||||
|
default="overwrite",
|
||||||
|
options=["overwrite", "increment"],
|
||||||
|
tooltip="Whether to overwrite existing files or increment filenames to avoid overwriting."
|
||||||
|
),
|
||||||
],
|
],
|
||||||
outputs=[],
|
outputs=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, images, folder_name, filename_prefix, texts=None):
|
def execute(cls, images, folder_name, filename_prefix, mode, texts=None):
|
||||||
# Extract scalar values
|
# Extract scalar values
|
||||||
folder_name = folder_name[0]
|
folder_name = folder_name[0]
|
||||||
filename_prefix = filename_prefix[0]
|
filename_prefix = filename_prefix[0]
|
||||||
|
mode = mode[0]
|
||||||
|
|
||||||
output_dir = os.path.join(folder_paths.get_output_directory(), folder_name)
|
output_dir = os.path.join(folder_paths.get_output_directory(), folder_name)
|
||||||
saved_files = save_images_to_folder(images, output_dir, filename_prefix)
|
saved_files = save_images_to_folder(images, output_dir, filename_prefix, mode=='overwrite')
|
||||||
|
|
||||||
# Save captions
|
# Save captions
|
||||||
if texts:
|
if texts:
|
||||||
|
|||||||
@ -47,6 +47,7 @@ class Load3D(IO.ComfyNode):
|
|||||||
IO.Load3DCamera.Output(display_name="camera_info"),
|
IO.Load3DCamera.Output(display_name="camera_info"),
|
||||||
IO.Video.Output(display_name="recording_video"),
|
IO.Video.Output(display_name="recording_video"),
|
||||||
IO.File3DAny.Output(display_name="model_3d"),
|
IO.File3DAny.Output(display_name="model_3d"),
|
||||||
|
IO.Load3DModelInfo.Output(display_name="model_3d_info"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -73,7 +74,8 @@ class Load3D(IO.ComfyNode):
|
|||||||
if model_file and model_file != "none":
|
if model_file and model_file != "none":
|
||||||
file_3d = Types.File3D(folder_paths.get_annotated_filepath(model_file))
|
file_3d = Types.File3D(folder_paths.get_annotated_filepath(model_file))
|
||||||
mesh_path = model_file
|
mesh_path = model_file
|
||||||
return IO.NodeOutput(output_image, output_mask, mesh_path, normal_image, image['camera_info'], video, file_3d)
|
model_3d_info = image.get('model_3d_info', [])
|
||||||
|
return IO.NodeOutput(output_image, output_mask, mesh_path, normal_image, image['camera_info'], video, file_3d, model_3d_info)
|
||||||
|
|
||||||
process = execute # TODO: remove
|
process = execute # TODO: remove
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
comfyui-frontend-package==1.44.19
|
comfyui-frontend-package==1.44.19
|
||||||
comfyui-workflow-templates==0.9.85
|
comfyui-workflow-templates==0.9.91
|
||||||
comfyui-embedded-docs==0.5.1
|
comfyui-embedded-docs==0.5.1
|
||||||
torch
|
torch
|
||||||
torchsde
|
torchsde
|
||||||
@ -21,8 +21,8 @@ psutil
|
|||||||
alembic
|
alembic
|
||||||
SQLAlchemy>=2.0.0
|
SQLAlchemy>=2.0.0
|
||||||
filelock
|
filelock
|
||||||
av>=14.2.0
|
av>=16.0.0
|
||||||
comfy-kitchen>=0.2.8
|
comfy-kitchen==0.2.9
|
||||||
comfy-aimdo==0.4.5
|
comfy-aimdo==0.4.5
|
||||||
requests
|
requests
|
||||||
simpleeval>=1.0.0
|
simpleeval>=1.0.0
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user