164 lines
5.1 KiB
Python
164 lines
5.1 KiB
Python
import os
|
|
import torch
|
|
import numpy as np
|
|
from PIL import Image
|
|
|
|
import folder_paths
|
|
|
|
# Scan all registered checkpoint directories for HuggingFace model subdirs
|
|
# (identified by the presence of config.json). This picks up models on any
|
|
# drive mapped in extra_model_paths.yaml as well as the default models/ folder.
|
|
def _list_model_dirs():
|
|
checkpoint_dirs = folder_paths.get_folder_paths("checkpoints")
|
|
found = {} # label -> absolute path
|
|
|
|
for base in checkpoint_dirs:
|
|
if not os.path.isdir(base):
|
|
continue
|
|
for entry in os.listdir(base):
|
|
full = os.path.join(base, entry)
|
|
if os.path.isdir(full) and os.path.isfile(os.path.join(full, "config.json")):
|
|
found[entry] = full # later paths override earlier if same name
|
|
|
|
return (list(found.keys()), found) if found else (
|
|
["(no HuggingFace model folders found in checkpoints/)"], {}
|
|
)
|
|
|
|
|
|
# Cache at import time so INPUT_TYPES and load_model share the same map
|
|
_MODEL_LABELS, _MODEL_PATHS = _list_model_dirs()
|
|
|
|
|
|
class GLMOCRModelLoader:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"model_name": (_MODEL_LABELS,),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("GLM_OCR_MODEL",)
|
|
RETURN_NAMES = ("glm_ocr_model",)
|
|
FUNCTION = "load_model"
|
|
CATEGORY = "GLM-OCR"
|
|
|
|
def load_model(self, model_name):
|
|
import transformers
|
|
from transformers import AutoProcessor, AutoModelForImageTextToText
|
|
|
|
model_path = _MODEL_PATHS.get(model_name, model_name)
|
|
print(f"[GLM-OCR] Loading model from: {model_path}")
|
|
|
|
dtype = torch.float16 if torch.cuda.is_available() else torch.float32
|
|
|
|
processor = AutoProcessor.from_pretrained(model_path, trust_remote_code=True)
|
|
try:
|
|
model = AutoModelForImageTextToText.from_pretrained(
|
|
model_path,
|
|
torch_dtype=dtype,
|
|
device_map="auto",
|
|
trust_remote_code=True,
|
|
)
|
|
except ValueError as e:
|
|
if "glm_ocr" in str(e) or "does not recognize this architecture" in str(e):
|
|
raise RuntimeError(
|
|
f"[GLM-OCR] transformers {transformers.__version__} does not yet include "
|
|
f"the glm_ocr architecture. Fix: run the following command and restart ComfyUI:\n\n"
|
|
f' "C:\\ComfyUI_files\\.venv\\Scripts\\pip" install '
|
|
f"git+https://github.com/huggingface/transformers.git"
|
|
) from e
|
|
raise
|
|
|
|
model.eval()
|
|
print(f"[GLM-OCR] Model loaded successfully.")
|
|
return ({"model": model, "processor": processor},)
|
|
|
|
|
|
class GLMOCRRun:
|
|
TASK_PROMPTS = [
|
|
"Text Recognition:",
|
|
"Formula Recognition:",
|
|
"Table Recognition:",
|
|
"Describe the image.",
|
|
]
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"glm_ocr_model": ("GLM_OCR_MODEL",),
|
|
"image": ("IMAGE",),
|
|
"task_prompt": (cls.TASK_PROMPTS,),
|
|
"max_new_tokens": ("INT", {
|
|
"default": 2048, "min": 64, "max": 8192, "step": 64
|
|
}),
|
|
},
|
|
"optional": {
|
|
"custom_prompt": ("STRING", {
|
|
"default": "",
|
|
"multiline": False,
|
|
"placeholder": "Leave empty to use task_prompt above",
|
|
}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("text",)
|
|
FUNCTION = "run"
|
|
CATEGORY = "GLM-OCR"
|
|
OUTPUT_NODE = True
|
|
|
|
def run(self, glm_ocr_model, image, task_prompt, max_new_tokens, custom_prompt=""):
|
|
model = glm_ocr_model["model"]
|
|
processor = glm_ocr_model["processor"]
|
|
|
|
prompt = custom_prompt.strip() if custom_prompt.strip() else task_prompt
|
|
|
|
# Convert ComfyUI IMAGE tensor [B, H, W, C] → PIL
|
|
img_np = (image[0].cpu().numpy() * 255).clip(0, 255).astype(np.uint8)
|
|
pil_image = Image.fromarray(img_np).convert("RGB")
|
|
|
|
messages = [
|
|
{
|
|
"role": "user",
|
|
"content": [
|
|
{"type": "image", "image": pil_image},
|
|
{"type": "text", "text": prompt},
|
|
],
|
|
}
|
|
]
|
|
|
|
inputs = processor.apply_chat_template(
|
|
messages,
|
|
tokenize=True,
|
|
add_generation_prompt=True,
|
|
return_dict=True,
|
|
return_tensors="pt",
|
|
).to(model.device)
|
|
|
|
inputs.pop("token_type_ids", None)
|
|
|
|
with torch.no_grad():
|
|
generated_ids = model.generate(**inputs, max_new_tokens=max_new_tokens)
|
|
|
|
output_text = processor.decode(
|
|
generated_ids[0][inputs["input_ids"].shape[1]:],
|
|
skip_special_tokens=True,
|
|
)
|
|
|
|
print(f"[GLM-OCR] Output:\n{output_text}")
|
|
|
|
return {"ui": {"text": [output_text]}, "result": (output_text,)}
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"GLMOCRModelLoader": GLMOCRModelLoader,
|
|
"GLMOCRRun": GLMOCRRun,
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"GLMOCRModelLoader": "GLM-OCR Model Loader",
|
|
"GLMOCRRun": "GLM-OCR Run",
|
|
}
|