Spaces:
Runtime error
Runtime error
| import os | |
| import base64 | |
| from pathlib import Path | |
| import cv2 | |
| import numpy as np | |
| import onnxruntime as ort | |
| from fastapi import FastAPI, File, UploadFile | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import JSONResponse | |
| from ultralytics import YOLO | |
| from torchvision import transforms | |
| from huggingface_hub import hf_hub_download | |
| from llama_cpp import Llama | |
| from ctransformers import AutoModelForCausalLM | |
| from collections import Counter | |
| import re | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # CONFIG | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| YOLO_PATH = Path("yolo_deart.onnx") | |
| STYLE_MODEL_PATH = Path("style_classifier.onnx") | |
| LLM_PATH = hf_hub_download( | |
| repo_id="Qwen/Qwen2.5-1.5B-Instruct-GGUF", | |
| filename="qwen2.5-1.5b-instruct-fp16.gguf" | |
| ) | |
| FRONTEND_ORIGIN = os.environ.get( | |
| "FRONTEND_ORIGIN", | |
| "https://heigon77.github.io" | |
| ) | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # APP | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| app = FastAPI() | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=[FRONTEND_ORIGIN, "http://localhost:4200"], | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # YOLO | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| yolo_model = YOLO(str(YOLO_PATH)) | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # STYLE MODEL (ONNX) | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| ort_session = ort.InferenceSession( | |
| str(STYLE_MODEL_PATH), | |
| providers=["CPUExecutionProvider"] | |
| ) | |
| STYLES = [ | |
| "Abstract Expressionism","Action painting","Analytical Cubism","Art Nouveau", | |
| "Baroque","Color Field Painting","Contemporary Realism","Cubism", | |
| "Early Renaissance","Expressionism","Fauvism","High Renaissance", | |
| "Impressionism","Mannerism (Late Renaissance)","Minimalism", | |
| "Naive Art (Primitivism)","New Realism","Northern Renaissance", | |
| "Pointillism","Pop Art","Post Impressionism","Realism", | |
| "Rococo","Romanticism","Symbolism","Synthetic Cubism","Ukiyo-e" | |
| ] | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # LLM (phi3 GGUF) | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # Inicializando o modelo | |
| llm = Llama( | |
| model_path=LLM_PATH, | |
| n_ctx=2048, # Tamanho da janela de contexto | |
| n_threads=2, # O Space free tem 2 vCPUs | |
| n_gpu_layers=0 # Garante que ele nΓ£o busque CUDA | |
| ) | |
| def generate_poem(detections, style): | |
| counts = Counter([d["class_name"] for d in detections]) | |
| objects = ", ".join( | |
| f"{v} {k}{'s' if v > 1 and not k.endswith('s') else ''}" | |
| for k, v in counts.items() | |
| ) or "silence" | |
| prompt = f"""<|im_start|>system | |
| You are a master of classical poetry. You write only the poem requested, following strict metrical and rhyme rules. No titles, no explanations, no markdown.<|im_end|> | |
| <|im_start|>user | |
| Write a Shakespearean sonnet (14 lines, ABAB CDCD EFEF GG) about: {objects}. | |
| Style of the artwork: {style["style_name"]}. | |
| Directives: | |
| - Strictly 14 lines. | |
| - No markdown (no bold, no italics). | |
| - No title. | |
| - Output only the poem text.<|im_end|> | |
| <|im_start|>assistant | |
| """ | |
| output = llm( | |
| prompt, | |
| max_tokens=300, | |
| temperature=0.7, # EquilΓbrio entre criatividade e ordem | |
| top_p=0.9, | |
| repeat_penalty=1.1, # Reduzido, pois o FP16 Γ© naturalmente mais coerente | |
| stop=["<|im_end|>", "User:", "\n\n\n\n"] | |
| ) | |
| poem_text = output["choices"][0]["text"].strip() | |
| return poem_text | |
| def format_poem(text: str, max_lines: int = 17): | |
| # ββ 1. Remove prefixos tipo [response]: ou "response:" | |
| text = re.sub(r'^\[.*?\]\s*:\s*', '', text.strip(), flags=re.IGNORECASE) | |
| text = re.sub(r'^[^:]{0,30}:\s*', '', text.strip()) | |
| # ββ 2. Quebra em linhas e limpa espaΓ§os | |
| lines = [l.strip() for l in text.split("\n") if l.strip()] | |
| # ββ 3. Remove lixo extra (mantΓ©m sΓ³ primeiras 14 + espaΓ§os) | |
| core = lines[:14] | |
| # ββ 4. Monta estrutura com 3 linhas em branco entre blocos | |
| # (4 blocos: 4 + 4 + 4 + 2 versos, ajustΓ‘vel se quiser) | |
| formatted = [] | |
| formatted += core[:4] | |
| formatted.append("") | |
| formatted += core[4:8] | |
| formatted.append("") | |
| formatted += core[8:12] | |
| formatted.append("") | |
| formatted += core[12:14] | |
| # ββ 5. Garante exatamente 17 linhas | |
| if len(formatted) > max_lines: | |
| formatted = formatted[:max_lines] | |
| else: | |
| while len(formatted) < max_lines: | |
| formatted.append("") | |
| return "\n".join(formatted) | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # TRANSFORM | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| style_tf = transforms.Compose([ | |
| transforms.ToPILImage(), | |
| transforms.Resize((224, 224)), | |
| transforms.ToTensor(), | |
| transforms.Normalize([0.485, 0.456, 0.406], | |
| [0.229, 0.224, 0.225]), | |
| ]) | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # UTILS | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| def encode_image(img_bgr: np.ndarray) -> str: | |
| _, buffer = cv2.imencode(".jpg", img_bgr, [cv2.IMWRITE_JPEG_QUALITY, 85]) | |
| return base64.b64encode(buffer).decode("utf-8") | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # STYLE INFERENCE | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| def predict_style(img_bgr: np.ndarray): | |
| img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) | |
| tensor = style_tf(img_rgb).unsqueeze(0).numpy() | |
| outputs = ort_session.run(["logits"], {"image": tensor})[0] | |
| exp = np.exp(outputs - np.max(outputs)) | |
| probs = exp / exp.sum(axis=1, keepdims=True) | |
| idx = int(np.argmax(probs)) | |
| return { | |
| "style_id": idx, | |
| "style_name": STYLES[idx], | |
| "confidence": float(np.max(probs)) | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # ROUTES | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| def health(): | |
| return { | |
| "status": "ok", | |
| "yolo_classes": len(yolo_model.names), | |
| "style_classes": len(STYLES), | |
| "llm": "qwen2.5-1.5b-instruct-fp16.gguf" | |
| } | |
| async def detect(file: UploadFile = File(...)): | |
| contents = await file.read() | |
| img_np = cv2.imdecode( | |
| np.frombuffer(contents, np.uint8), | |
| cv2.IMREAD_COLOR | |
| ) | |
| # ββ YOLO βββββββββββββββββββββββββββββββ | |
| results = yolo_model(img_np)[0] | |
| detections = [ | |
| { | |
| "class_id": int(box.cls[0]), | |
| "class_name": yolo_model.names[int(box.cls[0])], | |
| "confidence": round(float(box.conf[0]), 3), | |
| } | |
| for box in results.boxes | |
| ] | |
| # ββ STYLE ββββββββββββββββββββββββββββββ | |
| style_pred = predict_style(img_np) | |
| # ββ LLM ββββββββββββββββββββββββββββββββ | |
| try: | |
| poem = generate_poem(detections, style_pred) | |
| poem = format_poem(poem) | |
| except Exception as e: | |
| poem = f"LLM error: {str(e)}" | |
| return JSONResponse({ | |
| "detections": sorted(detections, key=lambda d: -d["confidence"]), | |
| "style": style_pred, | |
| "poem": poem, | |
| "annotated_image": encode_image(results.plot()), | |
| "image_width": int(img_np.shape[1]), | |
| "image_height": int(img_np.shape[0]), | |
| }) | |
| def root(): | |
| return { | |
| "status": "ok", | |
| "description": "Art analysis API: detects objects, classifies artistic style and generates a Shakespearean sonnet from paintings.", | |
| "models": { | |
| "object_detection": { | |
| "file": "yolo_deart.onnx", | |
| "description": "Custom YOLOv8 fine-tuned for art object detection" | |
| }, | |
| "style_classification": { | |
| "file": "style_classifier.onnx", | |
| "classes": len(STYLES), | |
| "description": f"ONNX classifier for {len(STYLES)} artistic styles (Baroque, Impressionism, Cubismβ¦)" | |
| }, | |
| "poem_generation": { | |
| "model": "Qwen2.5-1.5B-Instruct (FP16 GGUF)", | |
| "backend": "llama-cpp", | |
| "description": "Generates a Shakespearean sonnet based on detected objects and art style" | |
| } | |
| }, | |
| "endpoints": { | |
| "GET /": "This overview", | |
| "GET /health": "Runtime status and loaded model info", | |
| "POST /detect": "Upload a painting image β returns detections, style, poem and annotated image" | |
| } | |
| } |