Spaces:
Sleeping
Sleeping
| # run_local.py — Teste local para app2.py com YOLO + OpenCV | |
| import os | |
| import sys | |
| import types | |
| from pathlib import Path | |
| from fastapi.responses import HTMLResponse | |
| import uvicorn | |
| # ================================================================ | |
| # 0️⃣ Preparar ambiente fake ANTES de importar app2.py | |
| # ================================================================ | |
| # Variáveis de ambiente fake para evitar erros no import | |
| os.environ.setdefault("AWS_ACCESS_KEY_ID", "fake") | |
| os.environ.setdefault("AWS_SECRET_ACCESS_KEY", "fake") | |
| os.environ.setdefault("AWS_S3_BUCKET_NAME", "fake-bucket") | |
| os.environ.setdefault("AWS_S3_REGION", "us-east-1") | |
| os.environ.setdefault("SUPABASE_URL", "http://fake.supabase") | |
| os.environ.setdefault("SUPABASE_KEY", "fake-key") | |
| os.environ.setdefault("YOLO_MODEL_PATH", "best.pt") # opcional | |
| # Fake ultralytics caso você não queira instalar local | |
| try: | |
| import ultralytics # tenta usar real se tiver | |
| except ImportError: | |
| ultra_mod = types.ModuleType("ultralytics") | |
| class DummyBox: | |
| def __init__(self): pass | |
| class DummyResult: | |
| def __init__(self): | |
| self.names = {0: "screw"} | |
| self.boxes = None | |
| class DummyYOLO: | |
| def __init__(self, *args, **kwargs): pass | |
| def predict(self, img, *args, **kwargs): | |
| return [DummyResult()] | |
| ultra_mod.YOLO = DummyYOLO | |
| sys.modules["ultralytics"] = ultra_mod | |
| print("[Fake] ultralytics carregado!") | |
| # ================================================================ | |
| # 1️⃣ Importar app real | |
| # ================================================================ | |
| import app2 | |
| app = app2.app # reusar FastAPI original | |
| # ================================================================ | |
| # 2️⃣ Fake S3 e Fake Supabase | |
| # ================================================================ | |
| LOCAL_ROOT = Path("./local_storage").resolve() | |
| (LOCAL_ROOT / "imagens_originais").mkdir(parents=True, exist_ok=True) | |
| (LOCAL_ROOT / "imagens_resultados").mkdir(parents=True, exist_ok=True) | |
| class FakeS3: | |
| def put_object(self, Bucket, Key, Body, ContentType): | |
| dest = LOCAL_ROOT / Key | |
| dest.parent.mkdir(parents=True, exist_ok=True) | |
| with open(dest, "wb") as f: | |
| f.write(Body) | |
| print(f"[FakeS3] gravado: {dest}") | |
| def delete_object(self, Bucket, Key): | |
| dest = LOCAL_ROOT / Key | |
| try: | |
| dest.unlink() | |
| except: pass | |
| def generate_presigned_url(self, *_args, **kwargs): | |
| key = kwargs["Params"]["Key"] | |
| print(f"[FakeS3] URL gerada local para {key}") | |
| return f"file://{(LOCAL_ROOT / key).as_posix()}" | |
| class _FakeResp: | |
| def __init__(self, data): self.data = data | |
| def execute(self): return self | |
| class FakeTable: | |
| def __init__(self, store): self.store = store | |
| def insert(self, payload): | |
| new_id = self.store["next_id"] | |
| self.store["next_id"] += 1 | |
| row = dict(payload) | |
| row["id"] = new_id | |
| self.store["rows"][new_id] = row | |
| print(f"[FakeSupabase] inserido id={new_id}") | |
| return _FakeResp([row]) | |
| def select(self, *_cols): | |
| class _Sel: | |
| def __init__(self, table): self.table=table; self._id=None | |
| def eq(self, fld, val): | |
| if fld=="id": self._id = int(val) | |
| return self | |
| def single(self): | |
| return _FakeResp(self.table.store["rows"].get(self._id)) | |
| return _Sel(self) | |
| class FakeSupabase: | |
| def __init__(self): | |
| self.tables = {"amostras": {"next_id": 1, "rows": {}}} | |
| def from_(self, name): return FakeTable(self.tables[name]) | |
| # Patchar os clientes globais do app | |
| app2.s3_client = FakeS3() | |
| app2.supabase_client = FakeSupabase() | |
| # ================================================================ | |
| # 3️⃣ Interface Web Local (UI) | |
| # ================================================================ | |
| def local_ui(): | |
| return """ | |
| <!DOCTYPE html> | |
| <html lang="pt-br"> | |
| <head> | |
| <meta charset="UTF-8"/> | |
| <title>Teste Local - YOLO + Corrosão</title> | |
| <style> | |
| body { background:#0d1117; color:#e6edf3; font-family:Arial; padding:24px; } | |
| h1 { font-size:20px; } | |
| .card { background:#161b22; padding:20px; border-radius:12px; max-width:1100px; margin:auto; } | |
| button { padding:10px 20px; background:#238636; border:none; color:white; border-radius:6px; cursor:pointer; } | |
| button:hover { background:#2ea043; } | |
| .grid { display:grid; gap:16px; } | |
| .grid.crops { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); } | |
| .cropbox { background:#1f242c; padding:12px; border-radius:10px; } | |
| img { width:100%; border-radius:6px; background:white; } | |
| label { display:block; margin:10px 0 4px; } | |
| select, input[type=file] { background:#0d1117; color:#e6edf3; border:1px solid #30363d; border-radius:6px; padding:8px; } | |
| small { opacity:0.7; } | |
| .badge { display:inline-block; background:#30363d; padding:4px 8px; border-radius:999px; font-size:12px; margin-left:8px; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="card"> | |
| <h1>Teste Local — YOLO detecta parafusos → OpenCV mede corrosão</h1> | |
| <form id="form"> | |
| <label for="file">Imagem</label> | |
| <input type="file" id="file" name="file" accept="image/*" required /> | |
| <label for="ctype">Tipo de corrosão</label> | |
| <select id="ctype" name="corrosion_type"> | |
| <option value="white">Branca</option> | |
| <option value="black">Preta</option> | |
| <option value="red">Avermelhada</option> | |
| </select> | |
| <div style="margin-top:12px;"> | |
| <button>Enviar</button> | |
| <span class="badge">Dica: teste com iluminação estável</span> | |
| </div> | |
| </form> | |
| <div id="out" style="display:none; margin-top:20px;"> | |
| <h2>Detecções:</h2> | |
| <p><strong id="detCount"></strong> <span class="badge" id="ctypeBadge"></span></p> | |
| <h3>Imagem anotada:</h3> | |
| <img id="annot"/> | |
| <h3 style="margin-top:20px;">Resultados por parafuso (crop):</h3> | |
| <div id="grid" class="grid crops"></div> | |
| </div> | |
| </div> | |
| <script> | |
| document.getElementById("form").addEventListener("submit", async e => { | |
| e.preventDefault(); | |
| const fd = new FormData(e.target); | |
| const res = await fetch("/analyze", { method:"POST", body:fd }); | |
| if(!res.ok){ | |
| const t = await res.text(); | |
| alert("Erro no processamento: " + t); | |
| return; | |
| } | |
| const data = await res.json(); | |
| document.getElementById("detCount").textContent = data.detections_count + " parafusos detectados"; | |
| document.getElementById("ctypeBadge").textContent = "tipo: " + (data.corrosion_type || "white"); | |
| document.getElementById("annot").src = data.annotated_image; | |
| const grid = document.getElementById("grid"); | |
| grid.innerHTML=""; | |
| data.detections.forEach(det => { | |
| const a = det.analysis || {}; | |
| const div = document.createElement("div"); | |
| div.className="cropbox"; | |
| div.innerHTML = ` | |
| <strong>#${det.index} — ${det.class_name} (${det.score})</strong><br/> | |
| Corrosão: ${a.percent ?? "?"}%<br/> | |
| <small>Pixels: ${a.corrosion_pixels ?? "?"} / ${a.total_pixels ?? "?"}</small><br/><br/> | |
| <img src="${a.isolated_image}" /> | |
| <img style="margin-top:8px;" src="${a.corrosion_image}" /> | |
| `; | |
| grid.appendChild(div); | |
| }); | |
| document.getElementById("out").style.display="block"; | |
| }); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| # ================================================================ | |
| # 4️⃣ Subir servidor local | |
| # ================================================================ | |
| if __name__ == "__main__": | |
| print("\n✅ Desenvolvimento LOCAL iniciado!") | |
| print("Abra: http://localhost:8000/ui\n") | |
| uvicorn.run(app, host="0.0.0.0", port=8000) | |