Files
IxMxAMAR 15e4b0da4e release(v0.1.2): fix critical loader collision + 6 Gemini-review findings
Critical:
- rename nodes/ -> mp_nodes/, shared/ -> mp_shared/ to avoid shadowing
  ComfyUI core's nodes.py via sys.path injection (root cause of v0.1.1
  'nodes is not a package' startup failure)
- http_post now streams response and enforces 50MB cap mid-stream (DoS)
- AES key derivation: HKDF -> PBKDF2-HMAC-SHA256 600k iters + random salt
  (work factor against weak-key brute force; ciphertext format changed)
- fs_mkdir/copy/move/delete confined to ComfyUI input/output/temp + ~ by
  default; UTILITY_MEGAPACK_ALLOW_ARBITRARY_PATHS=1 to override

UX / correctness:
- combo settings now declare options array (themes dropdown was empty)
- LATENT default 1x4x8x8 zeros -> {} (avoids Flux/SD3 shape crashes)
- @op decorator rejects reserved widget names mode/theme at register time
- _pad_outputs docstring updated to match relaxed opaque-type behavior
2026-05-13 21:36:54 +05:30

56 lines
1.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Tensor ↔ PIL conversion helpers.
ComfyUI image tensors: shape (B, H, W, C), float32 in [0, 1], C=3 (RGB) or 4 (RGBA).
PIL images: H × W, mode "RGB" / "RGBA" / "L".
"""
from __future__ import annotations
import io
import numpy as np
import torch
from PIL import Image
def tensor_to_pil(tensor: torch.Tensor, frame: int = 0) -> Image.Image:
"""Convert a single frame from a ComfyUI image tensor to a PIL Image.
Accepts shape (B, H, W, C) or (H, W, C). Returns PIL "RGB" or "RGBA".
"""
if tensor.dim() == 4:
arr = tensor[frame].detach().cpu().numpy()
elif tensor.dim() == 3:
arr = tensor.detach().cpu().numpy()
else:
raise ValueError(f"expected 3D or 4D tensor, got shape {tuple(tensor.shape)}")
arr = np.clip(arr * 255.0, 0, 255).astype(np.uint8)
if arr.shape[-1] == 4:
return Image.fromarray(arr, mode="RGBA")
if arr.shape[-1] == 3:
return Image.fromarray(arr, mode="RGB")
if arr.shape[-1] == 1:
return Image.fromarray(arr.squeeze(-1), mode="L")
raise ValueError(f"unsupported channel count: {arr.shape[-1]}")
def pil_to_tensor(image: Image.Image) -> torch.Tensor:
"""Convert a PIL Image to a ComfyUI image tensor of shape (1, H, W, C)."""
if image.mode == "L":
image = image.convert("RGB")
arr = np.asarray(image).astype(np.float32) / 255.0
if arr.ndim == 2:
arr = arr[..., None]
return torch.from_numpy(arr).unsqueeze(0)
def pil_to_bytes(image: Image.Image, fmt: str = "PNG", **kwargs) -> bytes:
buf = io.BytesIO()
image.save(buf, format=fmt, **kwargs)
return buf.getvalue()
def bytes_to_pil(data: bytes) -> Image.Image:
return Image.open(io.BytesIO(data))