fix: 移除多参考图同尺寸限制,改为独立tensor列表传递
Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
+24
-19
@@ -231,11 +231,11 @@ class GptImageClient:
|
|||||||
size: str,
|
size: str,
|
||||||
n: int,
|
n: int,
|
||||||
seed: int,
|
seed: int,
|
||||||
image_tensor: Optional[torch.Tensor] = None,
|
image_list: Optional[List[torch.Tensor]] = None,
|
||||||
) -> List[Image.Image]:
|
) -> List[Image.Image]:
|
||||||
"""
|
"""
|
||||||
调用 /v1/images/generations/ 接口。
|
调用 /v1/images/generations/ 接口。
|
||||||
当传入 image_tensor 时,以 data URI 格式内联图像(图生图)。
|
当传入 image_list 时,以 data URI 格式内联图像(图生图)。
|
||||||
"""
|
"""
|
||||||
# 模型名映射:UI 显示名 → API 参数名
|
# 模型名映射:UI 显示名 → API 参数名
|
||||||
api_model = _MODEL_NAME_MAP.get(model, model)
|
api_model = _MODEL_NAME_MAP.get(model, model)
|
||||||
@@ -251,22 +251,23 @@ class GptImageClient:
|
|||||||
|
|
||||||
body["size"] = size if size else "auto"
|
body["size"] = size if size else "auto"
|
||||||
|
|
||||||
# 图生图:将 tensor 转成 data URI 内联
|
# 图生图:将 tensor 列表转成 data URI 内联
|
||||||
if image_tensor is not None:
|
if image_list is not None:
|
||||||
pil_images = tensor_to_pil(image_tensor)
|
|
||||||
data_urls = []
|
data_urls = []
|
||||||
for idx_img, img in enumerate(pil_images):
|
for idx_img, img_tensor in enumerate(image_list):
|
||||||
|
pil_images = tensor_to_pil(img_tensor)
|
||||||
|
img = pil_images[0]
|
||||||
buf = BytesIO()
|
buf = BytesIO()
|
||||||
img.save(buf, format="PNG")
|
img.save(buf, format="PNG")
|
||||||
png_bytes = buf.getvalue()
|
png_bytes = buf.getvalue()
|
||||||
# 单张图像预算:20MB 按图数平摊,至少保留 1MB 给其他字段
|
# 单张图像预算:20MB 按图数平摊,至少保留 1MB 给其他字段
|
||||||
per_image_budget = max(
|
per_image_budget = max(
|
||||||
1024 * 1024,
|
1024 * 1024,
|
||||||
(self._MAX_BODY_BYTES - 1024 * 1024) // len(pil_images),
|
(self._MAX_BODY_BYTES - 1024 * 1024) // len(image_list),
|
||||||
)
|
)
|
||||||
# base64 膨胀约 4/3,所以 PNG 目标上限 = budget * 3/4
|
# base64 膨胀约 4/3,所以 PNG 目标上限 = budget * 3/4
|
||||||
png_budget = int(per_image_budget * 3 / 4)
|
png_budget = int(per_image_budget * 3 / 4)
|
||||||
label = f"第{idx_img + 1}张" if len(pil_images) > 1 else ""
|
label = f"第{idx_img + 1}张" if len(image_list) > 1 else ""
|
||||||
png_bytes = self._shrink_png_to_limit(png_bytes, png_budget, label)
|
png_bytes = self._shrink_png_to_limit(png_bytes, png_budget, label)
|
||||||
b64 = base64.b64encode(png_bytes).decode("utf-8")
|
b64 = base64.b64encode(png_bytes).decode("utf-8")
|
||||||
data_urls.append(f"data:image/png;base64,{b64}")
|
data_urls.append(f"data:image/png;base64,{b64}")
|
||||||
@@ -322,7 +323,7 @@ class GptImageClient:
|
|||||||
size: str,
|
size: str,
|
||||||
n: int,
|
n: int,
|
||||||
seed: int,
|
seed: int,
|
||||||
image_tensor: torch.Tensor,
|
image_list: List[torch.Tensor],
|
||||||
mask_tensor: Optional[torch.Tensor] = None,
|
mask_tensor: Optional[torch.Tensor] = None,
|
||||||
) -> List[Image.Image]:
|
) -> List[Image.Image]:
|
||||||
"""
|
"""
|
||||||
@@ -333,10 +334,13 @@ class GptImageClient:
|
|||||||
# 模型名映射:UI 显示名 → API 参数名
|
# 模型名映射:UI 显示名 → API 参数名
|
||||||
api_model = _MODEL_NAME_MAP.get(model, model)
|
api_model = _MODEL_NAME_MAP.get(model, model)
|
||||||
|
|
||||||
# 将 batch tensor 拆成逐帧列表
|
# 统一 tensors 为 [1,H,W,C] 格式,支持不同尺寸
|
||||||
if image_tensor.dim() == 3:
|
normalized_tensors = []
|
||||||
image_tensor = image_tensor.unsqueeze(0) # [H,W,C] → [1,H,W,C]
|
for t in image_list:
|
||||||
num_images = image_tensor.shape[0]
|
if t.dim() == 3:
|
||||||
|
t = t.unsqueeze(0) # [H,W,C] → [1,H,W,C]
|
||||||
|
normalized_tensors.append(t)
|
||||||
|
num_images = len(normalized_tensors)
|
||||||
|
|
||||||
# o1key 中转服务的 edits 接口暂不支持 background / moderation / seed,
|
# o1key 中转服务的 edits 接口暂不支持 background / moderation / seed,
|
||||||
# 待服务方更新后可重新加入。
|
# 待服务方更新后可重新加入。
|
||||||
@@ -355,8 +359,7 @@ class GptImageClient:
|
|||||||
1024 * 1024,
|
1024 * 1024,
|
||||||
(self._MAX_BODY_BYTES - mask_reserve) // num_images,
|
(self._MAX_BODY_BYTES - mask_reserve) // num_images,
|
||||||
)
|
)
|
||||||
for i in range(num_images):
|
for i, frame in enumerate(normalized_tensors):
|
||||||
frame = image_tensor[i:i+1] # [1,H,W,C]
|
|
||||||
img_bytes = self._tensor_to_png_bytes(frame)
|
img_bytes = self._tensor_to_png_bytes(frame)
|
||||||
label = f"第{i + 1}张" if num_images > 1 else ""
|
label = f"第{i + 1}张" if num_images > 1 else ""
|
||||||
img_bytes = self._shrink_png_to_limit(img_bytes, per_image_budget, label)
|
img_bytes = self._shrink_png_to_limit(img_bytes, per_image_budget, label)
|
||||||
@@ -367,7 +370,9 @@ class GptImageClient:
|
|||||||
content_type="image/png",
|
content_type="image/png",
|
||||||
)
|
)
|
||||||
|
|
||||||
ih, iw = image_tensor.shape[1], image_tensor.shape[2]
|
# 蒙版尺寸校验以第一张图为基准
|
||||||
|
first_tensor = normalized_tensors[0]
|
||||||
|
ih, iw = first_tensor.shape[1], first_tensor.shape[2]
|
||||||
|
|
||||||
if mask_tensor is not None:
|
if mask_tensor is not None:
|
||||||
mask_png = self._mask_tensor_to_rgba_png_bytes(mask_tensor, (ih, iw))
|
mask_png = self._mask_tensor_to_rgba_png_bytes(mask_tensor, (ih, iw))
|
||||||
@@ -429,7 +434,7 @@ class GptImageClient:
|
|||||||
size: str,
|
size: str,
|
||||||
n: int,
|
n: int,
|
||||||
seed: int,
|
seed: int,
|
||||||
image_tensor: Optional[torch.Tensor] = None,
|
image_tensor: Optional[List[torch.Tensor]] = None,
|
||||||
mask_tensor: Optional[torch.Tensor] = None,
|
mask_tensor: Optional[torch.Tensor] = None,
|
||||||
) -> List[Image.Image]:
|
) -> List[Image.Image]:
|
||||||
"""
|
"""
|
||||||
@@ -447,13 +452,13 @@ class GptImageClient:
|
|||||||
coro = self._edit_async(
|
coro = self._edit_async(
|
||||||
prompt=prompt, model=model, quality=quality,
|
prompt=prompt, model=model, quality=quality,
|
||||||
background=background, size=size, n=n, seed=seed,
|
background=background, size=size, n=n, seed=seed,
|
||||||
image_tensor=image_tensor, mask_tensor=mask_tensor,
|
image_list=image_tensor, mask_tensor=mask_tensor,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
coro = self._generate_async(
|
coro = self._generate_async(
|
||||||
prompt=prompt, model=model, quality=quality,
|
prompt=prompt, model=model, quality=quality,
|
||||||
background=background, size=size, n=n, seed=seed,
|
background=background, size=size, n=n, seed=seed,
|
||||||
image_tensor=image_tensor,
|
image_list=image_tensor,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _run():
|
def _run():
|
||||||
|
|||||||
+1
-6
@@ -4,8 +4,6 @@ o1key GPT Image 节点
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import torch
|
|
||||||
|
|
||||||
from ..clients.gpt_image_client import GptImageClient
|
from ..clients.gpt_image_client import GptImageClient
|
||||||
from ..utils.image_utils import parse_batch_prompts
|
from ..utils.image_utils import parse_batch_prompts
|
||||||
|
|
||||||
@@ -132,10 +130,7 @@ class O1keyGPTImage:
|
|||||||
if key in kwargs and kwargs[key] is not None:
|
if key in kwargs and kwargs[key] is not None:
|
||||||
reference_tensors.append(kwargs[key])
|
reference_tensors.append(kwargs[key])
|
||||||
|
|
||||||
if reference_tensors:
|
图片 = reference_tensors if reference_tensors else None
|
||||||
图片 = torch.cat(reference_tensors, dim=0)
|
|
||||||
else:
|
|
||||||
图片 = None
|
|
||||||
|
|
||||||
# ── 1. 参数校验 ───────────────────────────────────────────────────────
|
# ── 1. 参数校验 ───────────────────────────────────────────────────────
|
||||||
if 遮罩 is not None and 图片 is None:
|
if 遮罩 is not None and 图片 is None:
|
||||||
|
|||||||
Reference in New Issue
Block a user