Archive of fixing Florence2 Loader to transformers 4.50.0
2025年7月7日現在、ノード最新版を使用する事により以下の問題は発生しなくなっていますが、Archiveとして保存しておきます。
例によって、動画に意味はありませんよ…本題は以下。
先だって、transformers 4.50.0に備えて、Florence2のモデル側のコードを修正していましたが、
本日そのtransformers 4.50.0に更新を行った処、上記事によるモデル側の修正に加えて、結局ノード側の修正も余儀なくされました。
ComfyUI\custom_nodes\ComfyUI-Florence2\nodes.pyです。
パワーアップしたCursor先生で、Claude-3.7-sonnetをフル活用して尚、結構苦戦してリテイクを繰り返し、トークン回数をかなり使用しました

コード自体も、初期から相当書き足しています...つか、transformersは各種Pythonライブラリーの中でも主役級の代物なので、その内ノード側が対応してくるとは思うのですが、少なくとも現時点ではコードを修正しなければ、Florence2そのものがエラーを起こします。
from collections.abc import Callable
import torch
import torchvision.transforms.functional as F
import io
import os
# 環境変数で警告を抑制(より早い段階で)
os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "true"
os.environ["TRANSFORMERS_NO_WARNINGS"] = "true"
os.environ["TOKENIZERS_PARALLELISM"] = "false"
# transformersのロギングレベルを変更 - 最も効果的な方法
import logging
logging.getLogger("transformers").setLevel(logging.ERROR)
import sys
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from PIL import Image, ImageDraw, ImageColor, ImageFont
import random
import numpy as np
import re
from pathlib import Path
import warnings
# すべてのwarningsを無視 - より強力な設定
warnings.filterwarnings("ignore")
# もし上記で抑制できない場合のバックアップとして特定の警告を抑制
warnings.filterwarnings("ignore", message=".*GenerationMixin.*")
warnings.filterwarnings("ignore", message=".*generate.*")
warnings.filterwarnings("ignore", message=".*has generative capabilities.*")
warnings.filterwarnings("ignore", message=".*Florence2.*")
#workaround for unnecessary flash_attn requirement
from unittest.mock import patch
from transformers.dynamic_module_utils import get_imports
def fixed_get_imports(filename: str | os.PathLike) -> list[str]:
try:
if not str(filename).endswith("modeling_florence2.py"):
return get_imports(filename)
imports = get_imports(filename)
imports.remove("flash_attn")
except:
print(f"No flash_attn import to remove")
pass
return imports
def create_path_dict(paths: list[str], predicate: Callable[[Path], bool] = lambda _: True) -> dict[str, str]:
"""
Creates a flat dictionary of the contents of all given paths: ``{name: absolute_path}``.
Non-recursive. Optionally takes a predicate to filter items. Duplicate names overwrite (the last one wins).
Args:
paths (list[str]):
The paths to search for items.
predicate (Callable[[Path], bool]):
(Optional) If provided, each path is tested against this filter.
Returns ``True`` to include a path.
Default: Include everything
"""
flattened_paths = [item for path in paths if Path(path).exists() for item in Path(path).iterdir() if predicate(item)]
return {item.name: str(item.absolute()) for item in flattened_paths}
import comfy.model_management as mm
from comfy.utils import ProgressBar
import folder_paths
script_directory = os.path.dirname(os.path.abspath(__file__))
model_directory = os.path.join(folder_paths.models_dir, "LLM")
os.makedirs(model_directory, exist_ok=True)
# Ensure ComfyUI knows about the LLM model path
folder_paths.add_model_folder_path("LLM", model_directory)
from transformers import AutoProcessor, set_seed, AutoConfig, PreTrainedModel, AutoModelForCausalLM
class DownloadAndLoadFlorence2Model:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"model": (
[
'microsoft/Florence-2-base',
'microsoft/Florence-2-base-ft',
'microsoft/Florence-2-large',
'microsoft/Florence-2-large-ft',
'HuggingFaceM4/Florence-2-DocVQA',
'thwri/CogFlorence-2.1-Large',
'thwri/CogFlorence-2.2-Large',
'gokaygokay/Florence-2-SD3-Captioner',
'gokaygokay/Florence-2-Flux-Large',
'MiaoshouAI/Florence-2-base-PromptGen-v1.5',
'MiaoshouAI/Florence-2-large-PromptGen-v1.5',
'MiaoshouAI/Florence-2-base-PromptGen-v2.0',
'MiaoshouAI/Florence-2-large-PromptGen-v2.0'
],
{
"default": 'microsoft/Florence-2-base'
}),
"precision": ([ 'fp16','bf16','fp32'],
{
"default": 'fp16'
}),
"attention": (
[ 'flash_attention_2', 'sdpa', 'eager'],
{
"default": 'sdpa'
}),
},
"optional": {
"lora": ("PEFTLORA",),
}
}
RETURN_TYPES = ("FL2MODEL",)
RETURN_NAMES = ("florence2_model",)
FUNCTION = "loadmodel"
CATEGORY = "Florence2"
def loadmodel(self, model, precision, attention, lora=None):
device = mm.get_torch_device()
offload_device = mm.unet_offload_device()
dtype = {"bf16": torch.bfloat16, "fp16": torch.float16, "fp32": torch.float32}[precision]
model_name = model.rsplit('/', 1)[-1]
model_path = os.path.join(model_directory, model_name)
if not os.path.exists(model_path):
print(f"Downloading Florence2 model to: {model_path}")
from huggingface_hub import snapshot_download
snapshot_download(repo_id=model,
local_dir=model_path,
local_dir_use_symlinks=False)
print(f"Florence2 using {attention} for attention")
# Set model directory as a separate module
if model_path not in sys.path:
sys.path.append(model_path)
try:
# 相対インポートの問題を解決するためにファイルを修正
config_path = os.path.join(model_path, "configuration_florence2.py")
model_file_path = os.path.join(model_path, "modeling_florence2.py")
# 設定ファイルの修正
if os.path.exists(config_path):
with open(config_path, 'r', encoding='utf-8') as f:
config_content = f.read()
# from . 形式の相対インポートを修正
modified_content = config_content.replace('from .', 'from ')
with open(config_path, 'w', encoding='utf-8') as f:
f.write(modified_content)
# モデルファイルの修正
if os.path.exists(model_file_path):
with open(model_file_path, 'r', encoding='utf-8') as f:
model_content = f.read()
# from . 形式の相対インポートを修正
modified_content = model_content.replace('from .', 'from ')
with open(model_file_path, 'w', encoding='utf-8') as f:
f.write(modified_content)
# モデルファイルから直接クラスをインポート
import importlib.util
with patch("transformers.dynamic_module_utils.get_imports", fixed_get_imports):
# Florence2の設定クラスを直接インポート
spec = importlib.util.spec_from_file_location("configuration_florence2", config_path)
config_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(config_module)
Florence2Config = getattr(config_module, "Florence2Config")
# Florence2モデルクラスを直接インポート
spec = importlib.util.spec_from_file_location("modeling_florence2", model_file_path)
model_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(model_module)
Florence2Class = getattr(model_module, "Florence2ForConditionalGeneration")
# 設定を作成してモデルを読み込む
config = Florence2Config.from_pretrained(model_path)
model = Florence2Class.from_pretrained(
model_path,
config=config,
attn_implementation=attention,
device_map=device,
torch_dtype=dtype,
trust_remote_code=True
)
print("モジュールからFlorence2モデルを正常にロードしました")
except Exception as module_e:
print(f"モジュールからのロードでエラーが発生しました: {module_e}")
# フォールバックとして、HuggingFaceからモデルを直接ロード
try:
print("AutoModelForCausalLMを使用してモデルを直接ロードします...")
model = AutoModelForCausalLM.from_pretrained(
model_path,
trust_remote_code=True,
attn_implementation=attention,
device_map=device,
torch_dtype=dtype
)
except Exception as final_e:
print(f"すべてのロード方法で失敗しました: {final_e}")
raise ValueError(f"モデルのロードに失敗しました。詳細なエラー: {module_e} -> {final_e}")
# プロセッサーをロード
processor = AutoProcessor.from_pretrained(model_path, trust_remote_code=True)
if lora is not None:
from peft import PeftModel
adapter_name = lora
model = PeftModel.from_pretrained(model, adapter_name, trust_remote_code=True)
florence2_model = {
'model': model,
'processor': processor,
'dtype': dtype
}
return (florence2_model,)
class DownloadAndLoadFlorence2Lora:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"model": (
[
'NikshepShetty/Florence-2-pixelprose',
],
),
},
}
RETURN_TYPES = ("PEFTLORA",)
RETURN_NAMES = ("lora",)
FUNCTION = "loadmodel"
CATEGORY = "Florence2"
def loadmodel(self, model):
model_name = model.rsplit('/', 1)[-1]
model_path = os.path.join(model_directory, model_name)
if not os.path.exists(model_path):
print(f"Downloading Florence2 lora model to: {model_path}")
from huggingface_hub import snapshot_download
snapshot_download(repo_id=model,
local_dir=model_path,
local_dir_use_symlinks=False)
return (model_path,)
class Florence2ModelLoader:
@classmethod
def INPUT_TYPES(s):
all_llm_paths = folder_paths.get_folder_paths("LLM")
s.model_paths = create_path_dict(all_llm_paths, lambda x: x.is_dir())
return {"required": {
"model": ([*s.model_paths], {"tooltip": "models are expected to be in Comfyui/models/LLM folder"}),
"precision": (['fp16','bf16','fp32'],),
"attention": (
[ 'flash_attention_2', 'sdpa', 'eager'],
{
"default": 'sdpa'
}),
},
"optional": {
"lora": ("PEFTLORA",),
}
}
RETURN_TYPES = ("FL2MODEL",)
RETURN_NAMES = ("florence2_model",)
FUNCTION = "loadmodel"
CATEGORY = "Florence2"
def loadmodel(self, model, precision, attention, lora=None):
device = mm.get_torch_device()
dtype = {"bf16": torch.bfloat16, "fp16": torch.float16, "fp32": torch.float32}[precision]
model_path = Florence2ModelLoader.model_paths.get(model)
print(f"Loading model from {model_path}")
print(f"Florence2 using {attention} for attention")
# Set model directory as a separate module
if model_path not in sys.path:
sys.path.append(model_path)
try:
# 相対インポートの問題を解決するためにファイルを修正
config_path = os.path.join(model_path, "configuration_florence2.py")
model_file_path = os.path.join(model_path, "modeling_florence2.py")
# 設定ファイルの修正
if os.path.exists(config_path):
with open(config_path, 'r', encoding='utf-8') as f:
config_content = f.read()
# from . 形式の相対インポートを修正
modified_content = config_content.replace('from .', 'from ')
with open(config_path, 'w', encoding='utf-8') as f:
f.write(modified_content)
# モデルファイルの修正
if os.path.exists(model_file_path):
with open(model_file_path, 'r', encoding='utf-8') as f:
model_content = f.read()
# from . 形式の相対インポートを修正
modified_content = model_content.replace('from .', 'from ')
with open(model_file_path, 'w', encoding='utf-8') as f:
f.write(modified_content)
# モデルファイルから直接クラスをインポート
import importlib.util
with patch("transformers.dynamic_module_utils.get_imports", fixed_get_imports):
# Florence2の設定クラスを直接インポート
spec = importlib.util.spec_from_file_location("configuration_florence2", config_path)
config_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(config_module)
Florence2Config = getattr(config_module, "Florence2Config")
# Florence2モデルクラスを直接インポート
spec = importlib.util.spec_from_file_location("modeling_florence2", model_file_path)
model_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(model_module)
Florence2Class = getattr(model_module, "Florence2ForConditionalGeneration")
# 設定を作成してモデルを読み込む
config = Florence2Config.from_pretrained(model_path)
model = Florence2Class.from_pretrained(
model_path,
config=config,
attn_implementation=attention,
device_map=device,
torch_dtype=dtype,
trust_remote_code=True
)
print("モジュールからFlorence2モデルを正常にロードしました")
except Exception as module_e:
print(f"モジュールからのロードでエラーが発生しました: {module_e}")
# フォールバックとして、HuggingFaceからモデルを直接ロード
try:
print("AutoModelForCausalLMを使用してモデルを直接ロードします...")
model = AutoModelForCausalLM.from_pretrained(
model_path,
trust_remote_code=True,
attn_implementation=attention,
device_map=device,
torch_dtype=dtype
)
except Exception as final_e:
print(f"すべてのロード方法で失敗しました: {final_e}")
raise ValueError(f"モデルのロードに失敗しました。詳細なエラー: {module_e} -> {final_e}")
# プロセッサーをロード
processor = AutoProcessor.from_pretrained(model_path, trust_remote_code=True)
if lora is not None:
from peft import PeftModel
adapter_name = lora
model = PeftModel.from_pretrained(model, adapter_name, trust_remote_code=True)
florence2_model = {
'model': model,
'processor': processor,
'dtype': dtype
}
return (florence2_model,)
class Florence2Run:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE", ),
"florence2_model": ("FL2MODEL", ),
"text_input": ("STRING", {"default": "", "multiline": True}),
"task": (
[
'region_caption',
'dense_region_caption',
'region_proposal',
'caption',
'detailed_caption',
'more_detailed_caption',
'caption_to_phrase_grounding',
'referring_expression_segmentation',
'ocr',
'ocr_with_region',
'docvqa',
'prompt_gen_tags',
'prompt_gen_mixed_caption',
'prompt_gen_analyze',
'prompt_gen_mixed_caption_plus',
],
),
"fill_mask": ("BOOLEAN", {"default": True}),
},
"optional": {
"keep_model_loaded": ("BOOLEAN", {"default": False}),
"max_new_tokens": ("INT", {"default": 1024, "min": 1, "max": 4096}),
"num_beams": ("INT", {"default": 3, "min": 1, "max": 64}),
"do_sample": ("BOOLEAN", {"default": True}),
"output_mask_select": ("STRING", {"default": ""}),
"seed": ("INT", {"default": 1, "min": 1, "max": 0xffffffffffffffff}),
}
}
RETURN_TYPES = ("IMAGE", "MASK", "STRING", "JSON")
RETURN_NAMES =("image", "mask", "caption", "data")
FUNCTION = "encode"
CATEGORY = "Florence2"
def hash_seed(self, seed):
import hashlib
# Convert the seed to a string and then to bytes
seed_bytes = str(seed).encode('utf-8')
# Create a SHA-256 hash of the seed bytes
hash_object = hashlib.sha256(seed_bytes)
# Convert the hash to an integer
hashed_seed = int(hash_object.hexdigest(), 16)
# Ensure the hashed seed is within the acceptable range for set_seed
return hashed_seed % (2**32)
def encode(self, image, text_input, florence2_model, task, fill_mask, keep_model_loaded=False,
num_beams=3, max_new_tokens=1024, do_sample=True, output_mask_select="", seed=None):
device = mm.get_torch_device()
_, height, width, _ = image.shape
offload_device = mm.unet_offload_device()
annotated_image_tensor = None
mask_tensor = None
processor = florence2_model['processor']
model = florence2_model['model']
dtype = florence2_model['dtype']
model.to(device)
if seed:
set_seed(self.hash_seed(seed))
colormap = ['blue','orange','green','purple','brown','pink','olive','cyan','red',
'lime','indigo','violet','aqua','magenta','gold','tan','skyblue']
prompts = {
'region_caption': '<OD>',
'dense_region_caption': '<DENSE_REGION_CAPTION>',
'region_proposal': '<REGION_PROPOSAL>',
'caption': '<CAPTION>',
'detailed_caption': '<DETAILED_CAPTION>',
'more_detailed_caption': '<MORE_DETAILED_CAPTION>',
'caption_to_phrase_grounding': '<CAPTION_TO_PHRASE_GROUNDING>',
'referring_expression_segmentation': '<REFERRING_EXPRESSION_SEGMENTATION>',
'ocr': '<OCR>',
'ocr_with_region': '<OCR_WITH_REGION>',
'docvqa': '<DocVQA>',
'prompt_gen_tags': '<GENERATE_TAGS>',
'prompt_gen_mixed_caption': '<MIXED_CAPTION>',
'prompt_gen_analyze': '<ANALYZE>',
'prompt_gen_mixed_caption_plus': '<MIXED_CAPTION_PLUS>',
}
task_prompt = prompts.get(task, '<OD>')
if (task not in ['referring_expression_segmentation', 'caption_to_phrase_grounding', 'docvqa']) and text_input:
raise ValueError("Text input (prompt) is only supported for 'referring_expression_segmentation', 'caption_to_phrase_grounding', and 'docvqa'")
if text_input != "":
prompt = task_prompt + " " + text_input
else:
prompt = task_prompt
image = image.permute(0, 3, 1, 2)
out = []
out_masks = []
out_results = []
out_data = []
pbar = ProgressBar(len(image))
for img in image:
image_pil = F.to_pil_image(img)
inputs = processor(text=prompt, images=image_pil, return_tensors="pt", do_rescale=False).to(dtype).to(device)
generated_ids = model.generate(
input_ids=inputs["input_ids"],
pixel_values=inputs["pixel_values"],
max_new_tokens=max_new_tokens,
do_sample=do_sample,
num_beams=num_beams,
)
# まず特殊トークンをスキップしてデコード
try:
clean_results = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
results = processor.batch_decode(generated_ids, skip_special_tokens=False)[0]
print(results)
except Exception as e:
print(f"特殊トークンスキップ処理でエラー: {e}")
# エラーが発生した場合は通常の処理を実行
results = processor.batch_decode(generated_ids, skip_special_tokens=False)[0]
print(results)
# 特殊トークンを手動で削除
if task == 'ocr_with_region':
clean_results = str(results)
cleaned_string = re.sub(r'</?s>|<[^>]*>', '\n', clean_results)
clean_results = re.sub(r'\n+', '\n', cleaned_string)
else:
clean_results = str(results)
# より強力な特殊トークン削除
clean_results = re.sub(r'<pad>+|<s>|</s>', '', clean_results)
clean_results = re.sub(r'<pad>', '', clean_results) # 単一のpadトークンも削除
# すべての特殊トークンを一度に削除
special_tokens = ['</s>', '<s>', '<pad>']
for token in special_tokens:
clean_results = clean_results.replace(token, '')
clean_results = clean_results.strip()
#return single string if only one image for compatibility with nodes that can't handle string lists
if len(image) == 1:
out_results = clean_results
else:
out_results.append(clean_results)
W, H = image_pil.size
parsed_answer = processor.post_process_generation(results, task=task_prompt, image_size=(W, H))
if task == 'region_caption' or task == 'dense_region_caption' or task == 'caption_to_phrase_grounding' or task == 'region_proposal':
fig, ax = plt.subplots(figsize=(W / 100, H / 100), dpi=100)
fig.subplots_adjust(left=0, right=1, top=1, bottom=0)
ax.imshow(image_pil)
bboxes = parsed_answer[task_prompt]['bboxes']
labels = parsed_answer[task_prompt]['labels']
mask_indexes = []
# Determine mask indexes outside the loop
if output_mask_select != "":
mask_indexes = [n for n in output_mask_select.split(",")]
print(mask_indexes)
else:
mask_indexes = [str(i) for i in range(len(bboxes))]
# Initialize mask_layer only if needed
if fill_mask:
mask_layer = Image.new('RGB', image_pil.size, (0, 0, 0))
mask_draw = ImageDraw.Draw(mask_layer)
for index, (bbox, label) in enumerate(zip(bboxes, labels)):
# Modify the label to include the index
indexed_label = f"{index}.{label}"
if fill_mask:
if str(index) in mask_indexes:
print("match index:", str(index), "in mask_indexes:", mask_indexes)
mask_draw.rectangle([bbox[0], bbox[1], bbox[2], bbox[3]], fill=(255, 255, 255))
if label in mask_indexes:
print("match label")
mask_draw.rectangle([bbox[0], bbox[1], bbox[2], bbox[3]], fill=(255, 255, 255))
# Create a Rectangle patch
rect = patches.Rectangle(
(bbox[0], bbox[1]), # (x,y) - lower left corner
bbox[2] - bbox[0], # Width
bbox[3] - bbox[1], # Height
linewidth=1,
edgecolor='r',
facecolor='none',
label=indexed_label
)
# Calculate text width with a rough estimation
text_width = len(label) * 6 # Adjust multiplier based on your font size
text_height = 12 # Adjust based on your font size
# Initial text position
text_x = bbox[0]
text_y = bbox[1] - text_height # Position text above the top-left of the bbox
# Adjust text_x if text is going off the left or right edge
if text_x < 0:
text_x = 0
elif text_x + text_width > W:
text_x = W - text_width
# Adjust text_y if text is going off the top edge
if text_y < 0:
text_y = bbox[3] # Move text below the bottom-left of the bbox if it doesn't overlap with bbox
# Add the rectangle to the plot
ax.add_patch(rect)
facecolor = random.choice(colormap) if len(image) == 1 else 'red'
# Add the label
plt.text(
text_x,
text_y,
indexed_label,
color='white',
fontsize=12,
bbox=dict(facecolor=facecolor, alpha=0.5)
)
if fill_mask:
mask_tensor = F.to_tensor(mask_layer)
mask_tensor = mask_tensor.unsqueeze(0).permute(0, 2, 3, 1).cpu().float()
mask_tensor = mask_tensor.mean(dim=0, keepdim=True)
mask_tensor = mask_tensor.repeat(1, 1, 1, 3)
mask_tensor = mask_tensor[:, :, :, 0]
out_masks.append(mask_tensor)
# Remove axis and padding around the image
ax.axis('off')
ax.margins(0,0)
ax.get_xaxis().set_major_locator(plt.NullLocator())
ax.get_yaxis().set_major_locator(plt.NullLocator())
fig.canvas.draw()
buf = io.BytesIO()
plt.savefig(buf, format='png', pad_inches=0)
buf.seek(0)
annotated_image_pil = Image.open(buf)
annotated_image_tensor = F.to_tensor(annotated_image_pil)
out_tensor = annotated_image_tensor[:3, :, :].unsqueeze(0).permute(0, 2, 3, 1).cpu().float()
out.append(out_tensor)
if task == 'caption_to_phrase_grounding':
out_data.append({'bboxes': bboxes, 'labels': labels, 'captions': parsed_answer})
plt.close()
elif 'referring_expression_segmentation' == task:
if fill_mask:
mask_array = parsed_answer["<REFERRING_EXPRESSION_SEGMENTATION>"]["mask"] * 255.0
mask_pil = Image.fromarray(mask_array.astype(np.uint8))
mask_tensor = F.to_tensor(mask_pil)
mask_tensor = mask_tensor.unsqueeze(0).permute(0, 2, 3, 1).cpu().float()
out_masks.append(mask_tensor)
out.append(F.to_tensor(image_pil).unsqueeze(0).permute(0, 2, 3, 1).cpu().float())
elif 'docvqa' == task:
if not text_input:
raise ValueError("Text input required for docvqa task")
mask_image = parsed_answer.get("<DocVQA>", {}).get("mask")
if mask_image is not None and fill_mask:
# 確実に2次元配列であることを確認
if isinstance(mask_image, np.ndarray):
if len(mask_image.shape) == 2:
# グレースケールマスク
mask_array = mask_image * 255.0
else:
# RGBマスク - グレースケールに変換
mask_array = np.mean(mask_image, axis=2) * 255.0
mask_pil = Image.fromarray(mask_array.astype(np.uint8))
mask_tensor = F.to_tensor(mask_pil)
mask_tensor = mask_tensor.unsqueeze(0).permute(0, 2, 3, 1).cpu().float()
out_masks.append(mask_tensor)
annotated_image = parsed_answer.get("<DocVQA>", {}).get("annotated_image")
if annotated_image is not None:
if isinstance(annotated_image, np.ndarray):
# NumPy配列からPIL Imageに変換
annotated_image_pil = Image.fromarray(annotated_image)
annotated_image_tensor = F.to_tensor(annotated_image_pil)
out_tensor = annotated_image_tensor[:3, :, :].unsqueeze(0).permute(0, 2, 3, 1).cpu().float()
out.append(out_tensor)
else:
# すでにPIL画像の場合
annotated_image_tensor = F.to_tensor(annotated_image)
out_tensor = annotated_image_tensor[:3, :, :].unsqueeze(0).permute(0, 2, 3, 1).cpu().float()
out.append(out_tensor)
else:
# アノテーション画像がない場合は元の画像を使用
out.append(F.to_tensor(image_pil).unsqueeze(0).permute(0, 2, 3, 1).cpu().float())
else:
out.append(F.to_tensor(image_pil).unsqueeze(0).permute(0, 2, 3, 1).cpu().float())
# 解析結果をJSON形式で出力
try:
if task in parsed_answer:
out_data.append(parsed_answer[task])
else:
# タスクキーがない場合は全データを追加
out_data.append(parsed_answer)
except Exception as e:
print(f"データの解析中にエラーが発生しました: {e}")
# エラーが発生した場合は空のデータを追加
out_data.append({})
pbar.update(1)
# 出力テンソルの作成
if out:
out_tensor = torch.cat(out, dim=0)
else:
# 空の場合はダミーテンソルを作成
out_tensor = torch.zeros((1, height, width, 3), dtype=torch.float32)
# マスクテンソルの作成
if len(out_masks) < len(out):
# マスクが不足している場合は空のマスクを追加
for _ in range(len(out) - len(out_masks)):
empty_mask = torch.zeros((1, height, width, 1), dtype=torch.float32)
out_masks.append(empty_mask)
if out_masks:
out_mask_tensor = torch.cat(out_masks, dim=0)
else:
# 空の場合はダミーマスクテンソルを作成
out_mask_tensor = torch.zeros((1, height, width, 1), dtype=torch.float32)
# モデルのアンロード(必要に応じて)
if not keep_model_loaded:
mm.soft_empty_cache()
if device != offload_device:
model.to(offload_device)
# 単一画像の場合はJSON出力を簡素化
if len(image) == 1 and isinstance(out_data, list) and len(out_data) == 1:
out_data = out_data[0]
return out_tensor, out_mask_tensor, out_results, out_data
NODE_CLASS_MAPPINGS = {
"DownloadAndLoadFlorence2Model": DownloadAndLoadFlorence2Model,
"DownloadAndLoadFlorence2Lora": DownloadAndLoadFlorence2Lora,
"Florence2ModelLoader": Florence2ModelLoader,
"Florence2Run": Florence2Run,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"DownloadAndLoadFlorence2Model": "Download and Load Florence2 Model (HF)",
"DownloadAndLoadFlorence2Lora": "Download Florence2 Lora",
"Florence2ModelLoader": "Load Florence2 Model (Local)",
"Florence2Run": "Florence2 Run",
}今や、動画生成でも画像生成でも、これがなくては始まらない…て位、使うノードです、Florence2は。

