isLinXu
Pack FlorenceForge source for embedded HF Spaces deployment
e40db0e
Raw
History Blame Contribute Delete
9.77 kB
"""单次推理运行时:Florence2 生成、PIL/tensor 前向与可视化分发。"""
from __future__ import annotations
import logging
import time
from typing import Any, Callable, Optional, Tuple
import numpy as np
import torch
import torch.nn as nn
from . import inference_parsing as parsing
from . import inference_visualization as visualization
logger = logging.getLogger(__name__)
def is_florence2_model(model: nn.Module) -> bool:
if not hasattr(model, "__class__"):
return False
return (
hasattr(model, "generate")
and hasattr(model, "processor")
and (
"Florence2MultiTaskModel" in str(model.__class__)
or "florence" in str(model.__class__).lower()
)
)
def format_generate_output(generated_text: Any) -> str:
if generated_text is None:
return ""
if isinstance(generated_text, torch.Tensor):
return str(generated_text)
if isinstance(generated_text, (list, tuple)):
return str(generated_text[0]) if generated_text else ""
return str(generated_text)
def generate_florence2_text(
model: nn.Module,
image: Any,
*,
task_prompt: Optional[str],
text_input: Optional[str],
device: torch.device,
use_amp: bool,
) -> str:
with torch.no_grad():
if use_amp:
with torch.autocast(device_type=device.type):
generated = model.generate(
images=image,
task_prompt=task_prompt,
text_input=text_input,
)
else:
generated = model.generate(
images=image,
task_prompt=task_prompt,
text_input=text_input,
)
return format_generate_output(generated)
def pil_to_batched_tensor(image: Any, device: torch.device) -> torch.Tensor:
if hasattr(image, "mode") and image.mode != "RGB":
image = image.convert("RGB")
arr = np.array(image)
tensor = torch.from_numpy(arr).permute(2, 0, 1).float() / 255.0
return tensor.to(device).unsqueeze(0)
def forward_tensor(
model: nn.Module,
inputs: torch.Tensor,
*,
device: torch.device,
use_amp: bool,
) -> Any:
inputs = inputs.to(device)
if inputs.dim() == 3:
inputs = inputs.unsqueeze(0)
with torch.no_grad():
if use_amp:
with torch.autocast(device_type=device.type):
return model(inputs)
return model(inputs)
def ensure_tensor(inputs: Any, device: torch.device) -> torch.Tensor:
if not isinstance(inputs, torch.Tensor):
inputs = torch.tensor(inputs)
inputs = inputs.to(device)
if inputs.dim() == 3:
inputs = inputs.unsqueeze(0)
return inputs
def visualize_florence2_output(
image: Any,
outputs: str,
task_prompt: Optional[str],
*,
save_path: Optional[str] = None,
) -> None:
if not outputs or task_prompt is None:
return
size: Tuple[int, int] = image.size
try:
if "<OD>" in task_prompt or "detection" in task_prompt.lower():
detections = parsing.parse_florence2_output(outputs, size)
if detections:
visualization.visualize_detections(image, detections, save_path)
logger.info("检测到 %d 个目标并已可视化", len(detections))
else:
logger.warning("未检测到任何目标")
elif "segmentation" in task_prompt.lower() or "REGION_TO_SEGMENTATION" in task_prompt:
seg = parsing.parse_segmentation_output(outputs, size)
if seg:
visualization.visualize_segmentation(image.copy(), seg, save_path)
logger.info("分割结果已可视化")
else:
logger.warning("未解析到分割数据")
elif "<REGION_PROPOSAL>" in task_prompt:
bboxes = parsing.parse_bboxes(outputs, size)
if bboxes:
visualization.visualize_bboxes(image.copy(), bboxes, save_path)
logger.info("区域提议结果已可视化")
else:
logger.warning("未解析到区域提议")
elif "OCR_WITH_REGION" in task_prompt:
ocr_results = parsing.parse_ocr_with_region(outputs, size)
if ocr_results:
visualization.visualize_ocr_with_region(image.copy(), ocr_results, save_path)
logger.info("OCR区域结果已可视化")
else:
logger.warning("未解析到OCR区域结果")
elif "<REGION_TO_CATEGORY>" in task_prompt:
detections = parsing.parse_florence2_output(outputs, size)
if detections:
visualization.visualize_detections(image, detections, save_path)
logger.info("检测到 %d 个目标并已可视化", len(detections))
else:
logger.warning("未检测到任何目标")
else:
visualization.visualize_caption(image.copy(), outputs, save_path)
except Exception as exc:
logger.error("可视化失败: %s", exc)
def predict_pil_image(
model: nn.Module,
image: Any,
*,
device: torch.device,
use_amp: bool,
task_prompt: Optional[str],
text_input: Optional[str],
visualize: bool,
save_path: Optional[str],
) -> Any:
if is_florence2_model(model):
try:
outputs = generate_florence2_text(
model,
image,
task_prompt=task_prompt,
text_input=text_input,
device=device,
use_amp=use_amp,
)
if visualize and outputs:
visualize_florence2_output(
image, outputs, task_prompt, save_path=save_path
)
return outputs
except Exception as exc:
logger.error("Florence2模型推理失败: %s", exc)
if "embedding" in str(exc).lower() or "indices" in str(exc).lower():
logger.warning("检测到embedding相关错误,返回空结果")
return ""
logger.info("尝试回退到普通tensor处理方式")
try:
tensor_in = pil_to_batched_tensor(image, device)
return forward_tensor(model, tensor_in, device=device, use_amp=use_amp)
except Exception as fallback_exc:
logger.error("回退处理也失败: %s", fallback_exc)
return ""
tensor_in = pil_to_batched_tensor(image, device)
return forward_tensor(model, tensor_in, device=device, use_amp=use_amp)
def predict_batch_non_florence(
model: nn.Module,
inputs_list: list,
*,
device: torch.device,
use_amp: bool,
batch_size: int,
preprocessor: Optional[Callable],
postprocessor: Optional[Callable],
update_stats: Callable[[float, int], None],
) -> list:
"""普通模型的批量 tensor 推理。"""
results: list = []
for start in range(0, len(inputs_list), batch_size):
batch_inputs = inputs_list[start : start + batch_size]
if preprocessor is not None:
batch_inputs = [preprocessor(inp) for inp in batch_inputs]
processed: list = []
for inp in batch_inputs:
if hasattr(inp, "mode") and hasattr(inp, "size"):
try:
from PIL import Image
if isinstance(inp, Image.Image):
inp = pil_to_batched_tensor(inp, device).squeeze(0)
except ImportError:
logger.warning("PIL未安装,无法处理PIL Image")
if not isinstance(inp, torch.Tensor):
inp = torch.tensor(inp)
processed.append(inp)
batch_tensor = torch.stack(processed).to(device)
step_start = time.time()
batch_outputs = forward_tensor(
model, batch_tensor, device=device, use_amp=use_amp
)
update_stats(time.time() - step_start, len(batch_inputs))
if postprocessor is not None:
results.extend(
postprocessor(output.unsqueeze(0)) for output in batch_outputs
)
else:
results.extend(output for output in batch_outputs)
return results
def run_predict_core(
model: nn.Module,
inputs: Any,
*,
device: torch.device,
use_amp: bool,
preprocessor: Optional[Callable],
postprocessor: Optional[Callable],
task_prompt: Optional[str],
text_input: Optional[str],
return_raw: bool,
visualize: bool,
save_path: Optional[str],
) -> Any:
if preprocessor:
inputs = preprocessor(inputs, task_prompt=task_prompt, text_input=text_input)
if hasattr(inputs, "mode") and hasattr(inputs, "size"):
try:
from PIL import Image
if isinstance(inputs, Image.Image):
outputs = predict_pil_image(
model,
inputs,
device=device,
use_amp=use_amp,
task_prompt=task_prompt,
text_input=text_input,
visualize=visualize,
save_path=save_path,
)
if not return_raw and postprocessor is not None:
outputs = postprocessor(outputs)
return outputs
except ImportError:
logger.warning("PIL未安装,无法处理PIL Image")
tensor_in = ensure_tensor(inputs, device)
outputs = forward_tensor(model, tensor_in, device=device, use_amp=use_amp)
if not return_raw and postprocessor is not None:
outputs = postprocessor(outputs)
return outputs