mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-01-31 16:50:17 +08:00
Merge branch 'master' into automation/comfyui-frontend-bump
This commit is contained in:
commit
d2b8fb110d
@ -189,9 +189,12 @@ class AudioVAE(torch.nn.Module):
|
|||||||
waveform = self.device_manager.move_to_load_device(waveform)
|
waveform = self.device_manager.move_to_load_device(waveform)
|
||||||
expected_channels = self.autoencoder.encoder.in_channels
|
expected_channels = self.autoencoder.encoder.in_channels
|
||||||
if waveform.shape[1] != expected_channels:
|
if waveform.shape[1] != expected_channels:
|
||||||
raise ValueError(
|
if waveform.shape[1] == 1:
|
||||||
f"Input audio must have {expected_channels} channels, got {waveform.shape[1]}"
|
waveform = waveform.expand(-1, expected_channels, *waveform.shape[2:])
|
||||||
)
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f"Input audio must have {expected_channels} channels, got {waveform.shape[1]}"
|
||||||
|
)
|
||||||
|
|
||||||
mel_spec = self.preprocessor.waveform_to_mel(
|
mel_spec = self.preprocessor.waveform_to_mel(
|
||||||
waveform, waveform_sample_rate, device=self.device_manager.load_device
|
waveform, waveform_sample_rate, device=self.device_manager.load_device
|
||||||
|
|||||||
@ -61,6 +61,7 @@ def te(dtype_llama=None, llama_quantization_metadata=None):
|
|||||||
if dtype_llama is not None:
|
if dtype_llama is not None:
|
||||||
dtype = dtype_llama
|
dtype = dtype_llama
|
||||||
if llama_quantization_metadata is not None:
|
if llama_quantization_metadata is not None:
|
||||||
|
model_options = model_options.copy()
|
||||||
model_options["quantization_metadata"] = llama_quantization_metadata
|
model_options["quantization_metadata"] = llama_quantization_metadata
|
||||||
super().__init__(device=device, dtype=dtype, model_options=model_options)
|
super().__init__(device=device, dtype=dtype, model_options=model_options)
|
||||||
return OvisTEModel_
|
return OvisTEModel_
|
||||||
|
|||||||
@ -40,6 +40,7 @@ def te(dtype_llama=None, llama_quantization_metadata=None):
|
|||||||
if dtype_llama is not None:
|
if dtype_llama is not None:
|
||||||
dtype = dtype_llama
|
dtype = dtype_llama
|
||||||
if llama_quantization_metadata is not None:
|
if llama_quantization_metadata is not None:
|
||||||
|
model_options = model_options.copy()
|
||||||
model_options["quantization_metadata"] = llama_quantization_metadata
|
model_options["quantization_metadata"] = llama_quantization_metadata
|
||||||
super().__init__(device=device, dtype=dtype, model_options=model_options)
|
super().__init__(device=device, dtype=dtype, model_options=model_options)
|
||||||
return ZImageTEModel_
|
return ZImageTEModel_
|
||||||
|
|||||||
@ -639,6 +639,8 @@ def flux_to_diffusers(mmdit_config, output_prefix=""):
|
|||||||
"proj_out.bias": "linear2.bias",
|
"proj_out.bias": "linear2.bias",
|
||||||
"attn.norm_q.weight": "norm.query_norm.scale",
|
"attn.norm_q.weight": "norm.query_norm.scale",
|
||||||
"attn.norm_k.weight": "norm.key_norm.scale",
|
"attn.norm_k.weight": "norm.key_norm.scale",
|
||||||
|
"attn.to_qkv_mlp_proj.weight": "linear1.weight", # Flux 2
|
||||||
|
"attn.to_out.weight": "linear2.weight", # Flux 2
|
||||||
}
|
}
|
||||||
|
|
||||||
for k in block_map:
|
for k in block_map:
|
||||||
|
|||||||
@ -1000,20 +1000,38 @@ class Autogrow(ComfyTypeI):
|
|||||||
names = [f"{prefix}{i}" for i in range(max)]
|
names = [f"{prefix}{i}" for i in range(max)]
|
||||||
# need to create a new input based on the contents of input
|
# need to create a new input based on the contents of input
|
||||||
template_input = None
|
template_input = None
|
||||||
for _, dict_input in input.items():
|
template_required = True
|
||||||
# for now, get just the first value from dict_input
|
for _input_type, dict_input in input.items():
|
||||||
|
# for now, get just the first value from dict_input; if not required, min can be ignored
|
||||||
|
if len(dict_input) == 0:
|
||||||
|
continue
|
||||||
template_input = list(dict_input.values())[0]
|
template_input = list(dict_input.values())[0]
|
||||||
|
template_required = _input_type == "required"
|
||||||
|
break
|
||||||
|
if template_input is None:
|
||||||
|
raise Exception("template_input could not be determined from required or optional; this should never happen.")
|
||||||
new_dict = {}
|
new_dict = {}
|
||||||
|
new_dict_added_to = False
|
||||||
|
# first, add possible inputs into out_dict
|
||||||
for i, name in enumerate(names):
|
for i, name in enumerate(names):
|
||||||
expected_id = finalize_prefix(curr_prefix, name)
|
expected_id = finalize_prefix(curr_prefix, name)
|
||||||
|
# required
|
||||||
|
if i < min and template_required:
|
||||||
|
out_dict["required"][expected_id] = template_input
|
||||||
|
type_dict = new_dict.setdefault("required", {})
|
||||||
|
# optional
|
||||||
|
else:
|
||||||
|
out_dict["optional"][expected_id] = template_input
|
||||||
|
type_dict = new_dict.setdefault("optional", {})
|
||||||
if expected_id in live_inputs:
|
if expected_id in live_inputs:
|
||||||
# required
|
# NOTE: prefix gets added in parse_class_inputs
|
||||||
if i < min:
|
|
||||||
type_dict = new_dict.setdefault("required", {})
|
|
||||||
# optional
|
|
||||||
else:
|
|
||||||
type_dict = new_dict.setdefault("optional", {})
|
|
||||||
type_dict[name] = template_input
|
type_dict[name] = template_input
|
||||||
|
new_dict_added_to = True
|
||||||
|
# account for the edge case that all inputs are optional and no values are received
|
||||||
|
if not new_dict_added_to:
|
||||||
|
finalized_prefix = finalize_prefix(curr_prefix)
|
||||||
|
out_dict["dynamic_paths"][finalized_prefix] = finalized_prefix
|
||||||
|
out_dict["dynamic_paths_default_value"][finalized_prefix] = DynamicPathsDefaultValue.EMPTY_DICT
|
||||||
parse_class_inputs(out_dict, live_inputs, new_dict, curr_prefix)
|
parse_class_inputs(out_dict, live_inputs, new_dict, curr_prefix)
|
||||||
|
|
||||||
@comfytype(io_type="COMFY_DYNAMICCOMBO_V3")
|
@comfytype(io_type="COMFY_DYNAMICCOMBO_V3")
|
||||||
@ -1151,6 +1169,8 @@ class V3Data(TypedDict):
|
|||||||
'Dictionary where the keys are the hidden input ids and the values are the values of the hidden inputs.'
|
'Dictionary where the keys are the hidden input ids and the values are the values of the hidden inputs.'
|
||||||
dynamic_paths: dict[str, Any]
|
dynamic_paths: dict[str, Any]
|
||||||
'Dictionary where the keys are the input ids and the values dictate how to turn the inputs into a nested dictionary.'
|
'Dictionary where the keys are the input ids and the values dictate how to turn the inputs into a nested dictionary.'
|
||||||
|
dynamic_paths_default_value: dict[str, Any]
|
||||||
|
'Dictionary where the keys are the input ids and the values are a string from DynamicPathsDefaultValue for the inputs if value is None.'
|
||||||
create_dynamic_tuple: bool
|
create_dynamic_tuple: bool
|
||||||
'When True, the value of the dynamic input will be in the format (value, path_key).'
|
'When True, the value of the dynamic input will be in the format (value, path_key).'
|
||||||
|
|
||||||
@ -1504,6 +1524,7 @@ def get_finalized_class_inputs(d: dict[str, Any], live_inputs: dict[str, Any], i
|
|||||||
"required": {},
|
"required": {},
|
||||||
"optional": {},
|
"optional": {},
|
||||||
"dynamic_paths": {},
|
"dynamic_paths": {},
|
||||||
|
"dynamic_paths_default_value": {},
|
||||||
}
|
}
|
||||||
d = d.copy()
|
d = d.copy()
|
||||||
# ignore hidden for parsing
|
# ignore hidden for parsing
|
||||||
@ -1513,8 +1534,12 @@ def get_finalized_class_inputs(d: dict[str, Any], live_inputs: dict[str, Any], i
|
|||||||
out_dict["hidden"] = hidden
|
out_dict["hidden"] = hidden
|
||||||
v3_data = {}
|
v3_data = {}
|
||||||
dynamic_paths = out_dict.pop("dynamic_paths", None)
|
dynamic_paths = out_dict.pop("dynamic_paths", None)
|
||||||
if dynamic_paths is not None:
|
if dynamic_paths is not None and len(dynamic_paths) > 0:
|
||||||
v3_data["dynamic_paths"] = dynamic_paths
|
v3_data["dynamic_paths"] = dynamic_paths
|
||||||
|
# this list is used for autogrow, in the case all inputs are optional and no values are passed
|
||||||
|
dynamic_paths_default_value = out_dict.pop("dynamic_paths_default_value", None)
|
||||||
|
if dynamic_paths_default_value is not None and len(dynamic_paths_default_value) > 0:
|
||||||
|
v3_data["dynamic_paths_default_value"] = dynamic_paths_default_value
|
||||||
return out_dict, hidden, v3_data
|
return out_dict, hidden, v3_data
|
||||||
|
|
||||||
def parse_class_inputs(out_dict: dict[str, Any], live_inputs: dict[str, Any], curr_dict: dict[str, Any], curr_prefix: list[str] | None=None) -> None:
|
def parse_class_inputs(out_dict: dict[str, Any], live_inputs: dict[str, Any], curr_dict: dict[str, Any], curr_prefix: list[str] | None=None) -> None:
|
||||||
@ -1551,11 +1576,16 @@ def add_to_dict_v1(i: Input, d: dict):
|
|||||||
def add_to_dict_v3(io: Input | Output, d: dict):
|
def add_to_dict_v3(io: Input | Output, d: dict):
|
||||||
d[io.id] = (io.get_io_type(), io.as_dict())
|
d[io.id] = (io.get_io_type(), io.as_dict())
|
||||||
|
|
||||||
|
class DynamicPathsDefaultValue:
|
||||||
|
EMPTY_DICT = "empty_dict"
|
||||||
|
|
||||||
def build_nested_inputs(values: dict[str, Any], v3_data: V3Data):
|
def build_nested_inputs(values: dict[str, Any], v3_data: V3Data):
|
||||||
paths = v3_data.get("dynamic_paths", None)
|
paths = v3_data.get("dynamic_paths", None)
|
||||||
|
default_value_dict = v3_data.get("dynamic_paths_default_value", {})
|
||||||
if paths is None:
|
if paths is None:
|
||||||
return values
|
return values
|
||||||
values = values.copy()
|
values = values.copy()
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
|
|
||||||
create_tuple = v3_data.get("create_dynamic_tuple", False)
|
create_tuple = v3_data.get("create_dynamic_tuple", False)
|
||||||
@ -1569,6 +1599,11 @@ def build_nested_inputs(values: dict[str, Any], v3_data: V3Data):
|
|||||||
|
|
||||||
if is_last:
|
if is_last:
|
||||||
value = values.pop(key, None)
|
value = values.pop(key, None)
|
||||||
|
if value is None:
|
||||||
|
# see if a default value was provided for this key
|
||||||
|
default_option = default_value_dict.get(key, None)
|
||||||
|
if default_option == DynamicPathsDefaultValue.EMPTY_DICT:
|
||||||
|
value = {}
|
||||||
if create_tuple:
|
if create_tuple:
|
||||||
value = (value, key)
|
value = (value, key)
|
||||||
current[p] = value
|
current[p] = value
|
||||||
|
|||||||
61
comfy_api_nodes/apis/bria.py
Normal file
61
comfy_api_nodes/apis/bria.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class InputModerationSettings(TypedDict):
|
||||||
|
prompt_content_moderation: bool
|
||||||
|
visual_input_moderation: bool
|
||||||
|
visual_output_moderation: bool
|
||||||
|
|
||||||
|
|
||||||
|
class BriaEditImageRequest(BaseModel):
|
||||||
|
instruction: str | None = Field(...)
|
||||||
|
structured_instruction: str | None = Field(
|
||||||
|
...,
|
||||||
|
description="Use this instead of instruction for precise, programmatic control.",
|
||||||
|
)
|
||||||
|
images: list[str] = Field(
|
||||||
|
...,
|
||||||
|
description="Required. Publicly available URL or Base64-encoded. Must contain exactly one item.",
|
||||||
|
)
|
||||||
|
mask: str | None = Field(
|
||||||
|
None,
|
||||||
|
description="Mask image (black and white). Black areas will be preserved, white areas will be edited. "
|
||||||
|
"If omitted, the edit applies to the entire image. "
|
||||||
|
"The input image and the the input mask must be of the same size.",
|
||||||
|
)
|
||||||
|
negative_prompt: str | None = Field(None)
|
||||||
|
guidance_scale: float = Field(...)
|
||||||
|
model_version: str = Field(...)
|
||||||
|
steps_num: int = Field(...)
|
||||||
|
seed: int = Field(...)
|
||||||
|
ip_signal: bool = Field(
|
||||||
|
False,
|
||||||
|
description="If true, returns a warning for potential IP content in the instruction.",
|
||||||
|
)
|
||||||
|
prompt_content_moderation: bool = Field(
|
||||||
|
False, description="If true, returns 422 on instruction moderation failure."
|
||||||
|
)
|
||||||
|
visual_input_content_moderation: bool = Field(
|
||||||
|
False, description="If true, returns 422 on images or mask moderation failure."
|
||||||
|
)
|
||||||
|
visual_output_content_moderation: bool = Field(
|
||||||
|
False, description="If true, returns 422 on visual output moderation failure."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BriaStatusResponse(BaseModel):
|
||||||
|
request_id: str = Field(...)
|
||||||
|
status_url: str = Field(...)
|
||||||
|
warning: str | None = Field(None)
|
||||||
|
|
||||||
|
|
||||||
|
class BriaResult(BaseModel):
|
||||||
|
structured_prompt: str = Field(...)
|
||||||
|
image_url: str = Field(...)
|
||||||
|
|
||||||
|
|
||||||
|
class BriaResponse(BaseModel):
|
||||||
|
status: str = Field(...)
|
||||||
|
result: BriaResult | None = Field(None)
|
||||||
198
comfy_api_nodes/nodes_bria.py
Normal file
198
comfy_api_nodes/nodes_bria.py
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
|
from comfy_api.latest import IO, ComfyExtension, Input
|
||||||
|
from comfy_api_nodes.apis.bria import (
|
||||||
|
BriaEditImageRequest,
|
||||||
|
BriaResponse,
|
||||||
|
BriaStatusResponse,
|
||||||
|
InputModerationSettings,
|
||||||
|
)
|
||||||
|
from comfy_api_nodes.util import (
|
||||||
|
ApiEndpoint,
|
||||||
|
convert_mask_to_image,
|
||||||
|
download_url_to_image_tensor,
|
||||||
|
get_number_of_images,
|
||||||
|
poll_op,
|
||||||
|
sync_op,
|
||||||
|
upload_images_to_comfyapi,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BriaImageEditNode(IO.ComfyNode):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def define_schema(cls):
|
||||||
|
return IO.Schema(
|
||||||
|
node_id="BriaImageEditNode",
|
||||||
|
display_name="Bria Image Edit",
|
||||||
|
category="api node/image/Bria",
|
||||||
|
description="Edit images using Bria latest model",
|
||||||
|
inputs=[
|
||||||
|
IO.Combo.Input("model", options=["FIBO"]),
|
||||||
|
IO.Image.Input("image"),
|
||||||
|
IO.String.Input(
|
||||||
|
"prompt",
|
||||||
|
multiline=True,
|
||||||
|
default="",
|
||||||
|
tooltip="Instruction to edit image",
|
||||||
|
),
|
||||||
|
IO.String.Input("negative_prompt", multiline=True, default=""),
|
||||||
|
IO.String.Input(
|
||||||
|
"structured_prompt",
|
||||||
|
multiline=True,
|
||||||
|
default="",
|
||||||
|
tooltip="A string containing the structured edit prompt in JSON format. "
|
||||||
|
"Use this instead of usual prompt for precise, programmatic control.",
|
||||||
|
),
|
||||||
|
IO.Int.Input(
|
||||||
|
"seed",
|
||||||
|
default=1,
|
||||||
|
min=1,
|
||||||
|
max=2147483647,
|
||||||
|
step=1,
|
||||||
|
display_mode=IO.NumberDisplay.number,
|
||||||
|
control_after_generate=True,
|
||||||
|
),
|
||||||
|
IO.Float.Input(
|
||||||
|
"guidance_scale",
|
||||||
|
default=3,
|
||||||
|
min=3,
|
||||||
|
max=5,
|
||||||
|
step=0.01,
|
||||||
|
display_mode=IO.NumberDisplay.number,
|
||||||
|
tooltip="Higher value makes the image follow the prompt more closely.",
|
||||||
|
),
|
||||||
|
IO.Int.Input(
|
||||||
|
"steps",
|
||||||
|
default=50,
|
||||||
|
min=20,
|
||||||
|
max=50,
|
||||||
|
step=1,
|
||||||
|
display_mode=IO.NumberDisplay.number,
|
||||||
|
),
|
||||||
|
IO.DynamicCombo.Input(
|
||||||
|
"moderation",
|
||||||
|
options=[
|
||||||
|
IO.DynamicCombo.Option(
|
||||||
|
"true",
|
||||||
|
[
|
||||||
|
IO.Boolean.Input(
|
||||||
|
"prompt_content_moderation", default=False
|
||||||
|
),
|
||||||
|
IO.Boolean.Input(
|
||||||
|
"visual_input_moderation", default=False
|
||||||
|
),
|
||||||
|
IO.Boolean.Input(
|
||||||
|
"visual_output_moderation", default=True
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
IO.DynamicCombo.Option("false", []),
|
||||||
|
],
|
||||||
|
tooltip="Moderation settings",
|
||||||
|
),
|
||||||
|
IO.Mask.Input(
|
||||||
|
"mask",
|
||||||
|
tooltip="If omitted, the edit applies to the entire image.",
|
||||||
|
optional=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
outputs=[
|
||||||
|
IO.Image.Output(),
|
||||||
|
IO.String.Output(display_name="structured_prompt"),
|
||||||
|
],
|
||||||
|
hidden=[
|
||||||
|
IO.Hidden.auth_token_comfy_org,
|
||||||
|
IO.Hidden.api_key_comfy_org,
|
||||||
|
IO.Hidden.unique_id,
|
||||||
|
],
|
||||||
|
is_api_node=True,
|
||||||
|
price_badge=IO.PriceBadge(
|
||||||
|
expr="""{"type":"usd","usd":0.04}""",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def execute(
|
||||||
|
cls,
|
||||||
|
model: str,
|
||||||
|
image: Input.Image,
|
||||||
|
prompt: str,
|
||||||
|
negative_prompt: str,
|
||||||
|
structured_prompt: str,
|
||||||
|
seed: int,
|
||||||
|
guidance_scale: float,
|
||||||
|
steps: int,
|
||||||
|
moderation: InputModerationSettings,
|
||||||
|
mask: Input.Image | None = None,
|
||||||
|
) -> IO.NodeOutput:
|
||||||
|
if not prompt and not structured_prompt:
|
||||||
|
raise ValueError(
|
||||||
|
"One of prompt or structured_prompt is required to be non-empty."
|
||||||
|
)
|
||||||
|
if get_number_of_images(image) != 1:
|
||||||
|
raise ValueError("Exactly one input image is required.")
|
||||||
|
mask_url = None
|
||||||
|
if mask is not None:
|
||||||
|
mask_url = (
|
||||||
|
await upload_images_to_comfyapi(
|
||||||
|
cls,
|
||||||
|
convert_mask_to_image(mask),
|
||||||
|
max_images=1,
|
||||||
|
mime_type="image/png",
|
||||||
|
wait_label="Uploading mask",
|
||||||
|
)
|
||||||
|
)[0]
|
||||||
|
response = await sync_op(
|
||||||
|
cls,
|
||||||
|
ApiEndpoint(path="proxy/bria/v2/image/edit", method="POST"),
|
||||||
|
data=BriaEditImageRequest(
|
||||||
|
instruction=prompt if prompt else None,
|
||||||
|
structured_instruction=structured_prompt if structured_prompt else None,
|
||||||
|
images=await upload_images_to_comfyapi(
|
||||||
|
cls,
|
||||||
|
image,
|
||||||
|
max_images=1,
|
||||||
|
mime_type="image/png",
|
||||||
|
wait_label="Uploading image",
|
||||||
|
),
|
||||||
|
mask=mask_url,
|
||||||
|
negative_prompt=negative_prompt if negative_prompt else None,
|
||||||
|
guidance_scale=guidance_scale,
|
||||||
|
seed=seed,
|
||||||
|
model_version=model,
|
||||||
|
steps_num=steps,
|
||||||
|
prompt_content_moderation=moderation.get(
|
||||||
|
"prompt_content_moderation", False
|
||||||
|
),
|
||||||
|
visual_input_content_moderation=moderation.get(
|
||||||
|
"visual_input_moderation", False
|
||||||
|
),
|
||||||
|
visual_output_content_moderation=moderation.get(
|
||||||
|
"visual_output_moderation", False
|
||||||
|
),
|
||||||
|
),
|
||||||
|
response_model=BriaStatusResponse,
|
||||||
|
)
|
||||||
|
response = await poll_op(
|
||||||
|
cls,
|
||||||
|
ApiEndpoint(path=f"/proxy/bria/v2/status/{response.request_id}"),
|
||||||
|
status_extractor=lambda r: r.status,
|
||||||
|
response_model=BriaResponse,
|
||||||
|
)
|
||||||
|
return IO.NodeOutput(
|
||||||
|
await download_url_to_image_tensor(response.result.image_url),
|
||||||
|
response.result.structured_prompt,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BriaExtension(ComfyExtension):
|
||||||
|
@override
|
||||||
|
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
||||||
|
return [
|
||||||
|
BriaImageEditNode,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def comfy_entrypoint() -> BriaExtension:
|
||||||
|
return BriaExtension()
|
||||||
@ -11,6 +11,7 @@ from .conversions import (
|
|||||||
audio_input_to_mp3,
|
audio_input_to_mp3,
|
||||||
audio_to_base64_string,
|
audio_to_base64_string,
|
||||||
bytesio_to_image_tensor,
|
bytesio_to_image_tensor,
|
||||||
|
convert_mask_to_image,
|
||||||
downscale_image_tensor,
|
downscale_image_tensor,
|
||||||
image_tensor_pair_to_batch,
|
image_tensor_pair_to_batch,
|
||||||
pil_to_bytesio,
|
pil_to_bytesio,
|
||||||
@ -72,6 +73,7 @@ __all__ = [
|
|||||||
"audio_input_to_mp3",
|
"audio_input_to_mp3",
|
||||||
"audio_to_base64_string",
|
"audio_to_base64_string",
|
||||||
"bytesio_to_image_tensor",
|
"bytesio_to_image_tensor",
|
||||||
|
"convert_mask_to_image",
|
||||||
"downscale_image_tensor",
|
"downscale_image_tensor",
|
||||||
"image_tensor_pair_to_batch",
|
"image_tensor_pair_to_batch",
|
||||||
"pil_to_bytesio",
|
"pil_to_bytesio",
|
||||||
|
|||||||
@ -451,6 +451,12 @@ def resize_mask_to_image(
|
|||||||
return mask
|
return mask
|
||||||
|
|
||||||
|
|
||||||
|
def convert_mask_to_image(mask: Input.Image) -> torch.Tensor:
|
||||||
|
"""Make mask have the expected amount of dims (4) and channels (3) to be recognized as an image."""
|
||||||
|
mask = mask.unsqueeze(-1)
|
||||||
|
return torch.cat([mask] * 3, dim=-1)
|
||||||
|
|
||||||
|
|
||||||
def text_filepath_to_base64_string(filepath: str) -> str:
|
def text_filepath_to_base64_string(filepath: str) -> str:
|
||||||
"""Converts a text file to a base64 string."""
|
"""Converts a text file to a base64 string."""
|
||||||
with open(filepath, "rb") as f:
|
with open(filepath, "rb") as f:
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
# This file is automatically generated by the build process when version is
|
# This file is automatically generated by the build process when version is
|
||||||
# updated in pyproject.toml.
|
# updated in pyproject.toml.
|
||||||
__version__ = "0.9.2"
|
__version__ = "0.10.0"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "ComfyUI"
|
name = "ComfyUI"
|
||||||
version = "0.9.2"
|
version = "0.10.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = { file = "LICENSE" }
|
license = { file = "LICENSE" }
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
comfyui-frontend-package==1.37.11
|
comfyui-frontend-package==1.37.11
|
||||||
comfyui-workflow-templates==0.8.11
|
comfyui-workflow-templates==0.8.14
|
||||||
comfyui-embedded-docs==0.4.0
|
comfyui-embedded-docs==0.4.0
|
||||||
torch
|
torch
|
||||||
torchsde
|
torchsde
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user