Spaces:
Sleeping
Sleeping
Commit ·
909fab2
1
Parent(s): f78216c
mvp
Browse files- .env.example +0 -3
- README.md +1 -0
- mvp/api.py +102 -0
- mvp/app.py +103 -0
- mvp/utils/images.py +17 -0
- notebooks/choose_model.ipynb +0 -0
- planparser/api.py +102 -0
- planparser/app.py +87 -26
- pyproject.toml +8 -0
- src/data/data.yaml +13 -0
- src/examples/example1.jpg +3 -0
- src/examples/example2.jpg +3 -0
.env.example
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
MODEL_DIR=models
|
| 2 |
-
MODEL_FILE=model.pt
|
| 3 |
-
GRADIO_WATCH_DIRS="planparser,src"
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[Lind for data downloading](https://universe.roboflow.com/research-g8szb/floorplan-details-fork)
|
mvp/api.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# planparser/api.py
|
| 2 |
+
import os
|
| 3 |
+
import io
|
| 4 |
+
from glob import glob
|
| 5 |
+
|
| 6 |
+
from ultralytics import YOLO
|
| 7 |
+
from PIL import Image
|
| 8 |
+
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
|
| 9 |
+
from pydantic import BaseModel
|
| 10 |
+
from dotenv import load_dotenv
|
| 11 |
+
|
| 12 |
+
load_dotenv()
|
| 13 |
+
|
| 14 |
+
MODEL_DIR = os.getenv("MODEL_DIR")
|
| 15 |
+
if not MODEL_DIR:
|
| 16 |
+
raise RuntimeError("MODEL_DIR is not set")
|
| 17 |
+
MODEL_DIR = os.path.abspath(os.path.expanduser(MODEL_DIR))
|
| 18 |
+
|
| 19 |
+
app = FastAPI(title="Model API")
|
| 20 |
+
_models: dict[str, YOLO] = {}
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def _pick_weights(path: str) -> str:
|
| 24 |
+
if os.path.isfile(path):
|
| 25 |
+
return path
|
| 26 |
+
if not os.path.isdir(path):
|
| 27 |
+
raise HTTPException(status_code=400, detail=f"model not found: {path}")
|
| 28 |
+
|
| 29 |
+
for name in ["model.pt", "best.pt"]:
|
| 30 |
+
cand = os.path.join(path, name)
|
| 31 |
+
if os.path.isfile(cand):
|
| 32 |
+
return cand
|
| 33 |
+
|
| 34 |
+
pts = sorted(glob(os.path.join(path, "*.pt")))
|
| 35 |
+
if pts:
|
| 36 |
+
return pts[0]
|
| 37 |
+
|
| 38 |
+
raise HTTPException(status_code=400, detail=f"no .pt weights in: {path}")
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def _resolve_weights_path(model_name: str) -> str:
|
| 42 |
+
if not model_name:
|
| 43 |
+
raise HTTPException(status_code=400, detail="model_name is required")
|
| 44 |
+
|
| 45 |
+
p = os.path.abspath(os.path.join(MODEL_DIR, model_name))
|
| 46 |
+
if os.path.commonpath([MODEL_DIR, p]) != MODEL_DIR:
|
| 47 |
+
raise HTTPException(status_code=400, detail="model_name must be inside MODEL_DIR")
|
| 48 |
+
|
| 49 |
+
return _pick_weights(p)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def load_model(weights_path: str) -> YOLO:
|
| 53 |
+
weights_path = os.path.abspath(weights_path)
|
| 54 |
+
m = _models.get(weights_path)
|
| 55 |
+
if m is None:
|
| 56 |
+
m = YOLO(weights_path)
|
| 57 |
+
_models[weights_path] = m
|
| 58 |
+
return m
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
class Detection(BaseModel):
|
| 62 |
+
class_id: int
|
| 63 |
+
class_name: str
|
| 64 |
+
confidence: float
|
| 65 |
+
xyxy: list[float]
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
class PredictResponse(BaseModel):
|
| 69 |
+
detections: list[Detection]
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
@app.get("/health")
|
| 73 |
+
def health():
|
| 74 |
+
return {"ok": True}
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
@app.post("/predict", response_model=PredictResponse)
|
| 78 |
+
async def predict(
|
| 79 |
+
file: UploadFile = File(...),
|
| 80 |
+
model_name: str = Form(...),
|
| 81 |
+
):
|
| 82 |
+
weights_path = _resolve_weights_path(model_name)
|
| 83 |
+
|
| 84 |
+
raw = await file.read()
|
| 85 |
+
img = Image.open(io.BytesIO(raw)).convert("RGB")
|
| 86 |
+
|
| 87 |
+
r0 = load_model(weights_path)(img)[0]
|
| 88 |
+
boxes = getattr(r0, "boxes", None)
|
| 89 |
+
if boxes is None:
|
| 90 |
+
raise HTTPException(status_code=500, detail="model output has no boxes (not a detection model?)")
|
| 91 |
+
|
| 92 |
+
names = getattr(r0, "names", {}) or {}
|
| 93 |
+
dets: list[Detection] = []
|
| 94 |
+
for cls_t, conf_t, xyxy_t in zip(boxes.cls, boxes.conf, boxes.xyxy):
|
| 95 |
+
cls_id = int(cls_t.item())
|
| 96 |
+
conf = float(conf_t.item())
|
| 97 |
+
xyxy = [float(x) for x in xyxy_t.tolist()]
|
| 98 |
+
cls_name = names.get(cls_id, str(cls_id)) if isinstance(names, dict) else str(cls_id)
|
| 99 |
+
dets.append(Detection(class_id=cls_id, class_name=cls_name, confidence=conf, xyxy=xyxy))
|
| 100 |
+
|
| 101 |
+
return PredictResponse(detections=dets)
|
| 102 |
+
# uv run uvicorn planparser.api:app --host 0.0.0.0 --port 8000
|
mvp/app.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# planparser/app.py
|
| 2 |
+
import os
|
| 3 |
+
import io
|
| 4 |
+
import time
|
| 5 |
+
|
| 6 |
+
import gradio as gr
|
| 7 |
+
import requests
|
| 8 |
+
from PIL import Image, ImageDraw
|
| 9 |
+
from dotenv import load_dotenv
|
| 10 |
+
|
| 11 |
+
load_dotenv()
|
| 12 |
+
|
| 13 |
+
API_URL = os.getenv("API_URL")
|
| 14 |
+
if not API_URL:
|
| 15 |
+
raise RuntimeError("API_URL is not set")
|
| 16 |
+
|
| 17 |
+
EXAMPLE_1 = os.getenv("EXAMPLE_1")
|
| 18 |
+
EXAMPLE_2 = os.getenv("EXAMPLE_2")
|
| 19 |
+
|
| 20 |
+
MODEL_MAP = {
|
| 21 |
+
"yolo11n": os.getenv("MODEL_1"),
|
| 22 |
+
"yolo11s": os.getenv("MODEL_2"),
|
| 23 |
+
"yolo11l": os.getenv("MODEL_3"),
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
MODEL_CHOICES = list(MODEL_MAP.keys())
|
| 27 |
+
DEFAULT_MODEL = MODEL_CHOICES[0]
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _draw_detections(img: Image.Image, dets: list[dict]) -> Image.Image:
|
| 31 |
+
out = img.copy().convert("RGB")
|
| 32 |
+
d = ImageDraw.Draw(out)
|
| 33 |
+
|
| 34 |
+
for det in dets:
|
| 35 |
+
x1, y1, x2, y2 = det["xyxy"]
|
| 36 |
+
d.rectangle([x1, y1, x2, y2], width=2)
|
| 37 |
+
txt = f'{det["class_name"]} {det["confidence"]:.2f}'
|
| 38 |
+
d.text((x1, max(0, y1 - 12)), txt)
|
| 39 |
+
|
| 40 |
+
return out
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def predict(model_label: str, img: Image.Image):
|
| 44 |
+
if img is None or not model_label:
|
| 45 |
+
return None, [], ""
|
| 46 |
+
|
| 47 |
+
buf = io.BytesIO()
|
| 48 |
+
img.convert("RGB").save(buf, format="JPEG")
|
| 49 |
+
buf.seek(0)
|
| 50 |
+
|
| 51 |
+
t0 = time.perf_counter()
|
| 52 |
+
r = requests.post(
|
| 53 |
+
f"{API_URL}/predict",
|
| 54 |
+
files={"file": ("image.jpg", buf, "image/jpeg")},
|
| 55 |
+
data={"model_name": MODEL_MAP[model_label]},
|
| 56 |
+
timeout=60,
|
| 57 |
+
)
|
| 58 |
+
dt = time.perf_counter() - t0
|
| 59 |
+
|
| 60 |
+
if r.status_code != 200:
|
| 61 |
+
return None, {"error": r.text}, f"{dt:.3f} s"
|
| 62 |
+
|
| 63 |
+
data = r.json()
|
| 64 |
+
dets = data.get("detections", []) or []
|
| 65 |
+
vis = _draw_detections(img, dets)
|
| 66 |
+
|
| 67 |
+
return vis, dets, f"{dt:.3f} s"
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
with gr.Blocks(title="YOLO detection demo") as demo:
|
| 71 |
+
gr.Markdown("# YOLO detection demo")
|
| 72 |
+
|
| 73 |
+
model_dd = gr.Dropdown(
|
| 74 |
+
choices=MODEL_CHOICES,
|
| 75 |
+
value=DEFAULT_MODEL,
|
| 76 |
+
label="Model",
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
with gr.Row():
|
| 80 |
+
with gr.Column(scale=1):
|
| 81 |
+
img_in = gr.Image(
|
| 82 |
+
type="pil",
|
| 83 |
+
label="Image",
|
| 84 |
+
sources=["upload"],
|
| 85 |
+
height=200,
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
ex = [p for p in [EXAMPLE_1, EXAMPLE_2] if p]
|
| 89 |
+
if ex:
|
| 90 |
+
gr.Examples(examples=ex, inputs=img_in)
|
| 91 |
+
|
| 92 |
+
btn = gr.Button("Submit")
|
| 93 |
+
|
| 94 |
+
with gr.Column(scale=2):
|
| 95 |
+
out_img = gr.Image(type="pil", label="Result", height=600)
|
| 96 |
+
out_json = gr.JSON(label="Detections")
|
| 97 |
+
out_time = gr.Textbox(label="Processing time")
|
| 98 |
+
|
| 99 |
+
btn.click(predict, inputs=[model_dd, img_in], outputs=[out_img, out_json, out_time])
|
| 100 |
+
|
| 101 |
+
if __name__ == "__main__":
|
| 102 |
+
demo.launch(server_name="0.0.0.0", server_port=7860)
|
| 103 |
+
# uv run gradio planparser/app.py
|
mvp/utils/images.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
from PIL import Image
|
| 3 |
+
|
| 4 |
+
def to_white_bg(in_path: str | Path, out_path: str | Path) -> None:
|
| 5 |
+
in_path = Path(in_path)
|
| 6 |
+
out_path = Path(out_path)
|
| 7 |
+
|
| 8 |
+
im = Image.open(in_path)
|
| 9 |
+
if im.mode in ("RGBA", "LA") or ("transparency" in im.info):
|
| 10 |
+
im = im.convert("RGBA")
|
| 11 |
+
white = Image.new("RGBA", im.size, (255, 255, 255, 255))
|
| 12 |
+
out = Image.alpha_composite(white, im).convert("RGB")
|
| 13 |
+
else:
|
| 14 |
+
out = im.convert("RGB")
|
| 15 |
+
|
| 16 |
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
| 17 |
+
out.save(out_path)
|
notebooks/choose_model.ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
planparser/api.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# planparser/api.py
|
| 2 |
+
import os
|
| 3 |
+
import io
|
| 4 |
+
from glob import glob
|
| 5 |
+
|
| 6 |
+
from ultralytics import YOLO
|
| 7 |
+
from PIL import Image
|
| 8 |
+
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
|
| 9 |
+
from pydantic import BaseModel
|
| 10 |
+
from dotenv import load_dotenv
|
| 11 |
+
|
| 12 |
+
load_dotenv()
|
| 13 |
+
|
| 14 |
+
MODEL_DIR = os.getenv("MODEL_DIR")
|
| 15 |
+
if not MODEL_DIR:
|
| 16 |
+
raise RuntimeError("MODEL_DIR is not set")
|
| 17 |
+
MODEL_DIR = os.path.abspath(os.path.expanduser(MODEL_DIR))
|
| 18 |
+
|
| 19 |
+
app = FastAPI(title="Model API")
|
| 20 |
+
_models: dict[str, YOLO] = {}
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def _pick_weights(path: str) -> str:
|
| 24 |
+
if os.path.isfile(path):
|
| 25 |
+
return path
|
| 26 |
+
if not os.path.isdir(path):
|
| 27 |
+
raise HTTPException(status_code=400, detail=f"model not found: {path}")
|
| 28 |
+
|
| 29 |
+
for name in ["model.pt", "best.pt"]:
|
| 30 |
+
cand = os.path.join(path, name)
|
| 31 |
+
if os.path.isfile(cand):
|
| 32 |
+
return cand
|
| 33 |
+
|
| 34 |
+
pts = sorted(glob(os.path.join(path, "*.pt")))
|
| 35 |
+
if pts:
|
| 36 |
+
return pts[0]
|
| 37 |
+
|
| 38 |
+
raise HTTPException(status_code=400, detail=f"no .pt weights in: {path}")
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def _resolve_weights_path(model_name: str) -> str:
|
| 42 |
+
if not model_name:
|
| 43 |
+
raise HTTPException(status_code=400, detail="model_name is required")
|
| 44 |
+
|
| 45 |
+
p = os.path.abspath(os.path.join(MODEL_DIR, model_name))
|
| 46 |
+
if os.path.commonpath([MODEL_DIR, p]) != MODEL_DIR:
|
| 47 |
+
raise HTTPException(status_code=400, detail="model_name must be inside MODEL_DIR")
|
| 48 |
+
|
| 49 |
+
return _pick_weights(p)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def load_model(weights_path: str) -> YOLO:
|
| 53 |
+
weights_path = os.path.abspath(weights_path)
|
| 54 |
+
m = _models.get(weights_path)
|
| 55 |
+
if m is None:
|
| 56 |
+
m = YOLO(weights_path)
|
| 57 |
+
_models[weights_path] = m
|
| 58 |
+
return m
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
class Detection(BaseModel):
|
| 62 |
+
class_id: int
|
| 63 |
+
class_name: str
|
| 64 |
+
confidence: float
|
| 65 |
+
xyxy: list[float]
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
class PredictResponse(BaseModel):
|
| 69 |
+
detections: list[Detection]
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
@app.get("/health")
|
| 73 |
+
def health():
|
| 74 |
+
return {"ok": True}
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
@app.post("/predict", response_model=PredictResponse)
|
| 78 |
+
async def predict(
|
| 79 |
+
file: UploadFile = File(...),
|
| 80 |
+
model_name: str = Form(...),
|
| 81 |
+
):
|
| 82 |
+
weights_path = _resolve_weights_path(model_name)
|
| 83 |
+
|
| 84 |
+
raw = await file.read()
|
| 85 |
+
img = Image.open(io.BytesIO(raw)).convert("RGB")
|
| 86 |
+
|
| 87 |
+
r0 = load_model(weights_path)(img)[0]
|
| 88 |
+
boxes = getattr(r0, "boxes", None)
|
| 89 |
+
if boxes is None:
|
| 90 |
+
raise HTTPException(status_code=500, detail="model output has no boxes (not a detection model?)")
|
| 91 |
+
|
| 92 |
+
names = getattr(r0, "names", {}) or {}
|
| 93 |
+
dets: list[Detection] = []
|
| 94 |
+
for cls_t, conf_t, xyxy_t in zip(boxes.cls, boxes.conf, boxes.xyxy):
|
| 95 |
+
cls_id = int(cls_t.item())
|
| 96 |
+
conf = float(conf_t.item())
|
| 97 |
+
xyxy = [float(x) for x in xyxy_t.tolist()]
|
| 98 |
+
cls_name = names.get(cls_id, str(cls_id)) if isinstance(names, dict) else str(cls_id)
|
| 99 |
+
dets.append(Detection(class_id=cls_id, class_name=cls_name, confidence=conf, xyxy=xyxy))
|
| 100 |
+
|
| 101 |
+
return PredictResponse(detections=dets)
|
| 102 |
+
# uv run uvicorn planparser.api:app --host 0.0.0.0 --port 8000
|
planparser/app.py
CHANGED
|
@@ -1,42 +1,103 @@
|
|
|
|
|
| 1 |
import os
|
|
|
|
|
|
|
|
|
|
| 2 |
import gradio as gr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
-
|
| 5 |
-
|
| 6 |
|
| 7 |
-
|
|
|
|
|
|
|
| 8 |
|
|
|
|
| 9 |
|
| 10 |
-
def get_model_path() -> str:
|
| 11 |
-
path = os.path.join(MODEL_DIR, MODEL_FILE)
|
| 12 |
-
if not os.path.exists(path):
|
| 13 |
-
raise FileNotFoundError(
|
| 14 |
-
f"Model not found: {path}. Put the file there or set MODEL_DIR and MODEL_FILE."
|
| 15 |
-
)
|
| 16 |
-
return path
|
| 17 |
|
|
|
|
|
|
|
| 18 |
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
-
|
| 30 |
-
_ = load_model()
|
| 31 |
-
return f"Loaded local model: {os.path.join(MODEL_DIR, MODEL_FILE)}. Input: {text}"
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
-
|
| 35 |
-
fn=predict,
|
| 36 |
-
inputs=gr.Textbox(label="Input"),
|
| 37 |
-
outputs=gr.Textbox(label="Output"),
|
| 38 |
-
title="Minimal demo",
|
| 39 |
-
)
|
| 40 |
|
| 41 |
if __name__ == "__main__":
|
| 42 |
demo.launch(server_name="0.0.0.0", server_port=7860)
|
|
|
|
|
|
| 1 |
+
# planparser/app.py
|
| 2 |
import os
|
| 3 |
+
import io
|
| 4 |
+
import time
|
| 5 |
+
|
| 6 |
import gradio as gr
|
| 7 |
+
import requests
|
| 8 |
+
from PIL import Image, ImageDraw
|
| 9 |
+
from dotenv import load_dotenv
|
| 10 |
+
|
| 11 |
+
load_dotenv()
|
| 12 |
+
|
| 13 |
+
API_URL = os.getenv("API_URL")
|
| 14 |
+
if not API_URL:
|
| 15 |
+
raise RuntimeError("API_URL is not set")
|
| 16 |
+
|
| 17 |
+
EXAMPLE_1 = os.getenv("EXAMPLE_1")
|
| 18 |
+
EXAMPLE_2 = os.getenv("EXAMPLE_2")
|
| 19 |
+
|
| 20 |
+
MODEL_MAP = {
|
| 21 |
+
"yolo11n": os.getenv("MODEL_1"),
|
| 22 |
+
"yolo11s": os.getenv("MODEL_2"),
|
| 23 |
+
"yolo11l": os.getenv("MODEL_3"),
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
MODEL_CHOICES = list(MODEL_MAP.keys())
|
| 27 |
+
DEFAULT_MODEL = MODEL_CHOICES[0]
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _draw_detections(img: Image.Image, dets: list[dict]) -> Image.Image:
|
| 31 |
+
out = img.copy().convert("RGB")
|
| 32 |
+
d = ImageDraw.Draw(out)
|
| 33 |
+
|
| 34 |
+
for det in dets:
|
| 35 |
+
x1, y1, x2, y2 = det["xyxy"]
|
| 36 |
+
d.rectangle([x1, y1, x2, y2], width=2)
|
| 37 |
+
txt = f'{det["class_name"]} {det["confidence"]:.2f}'
|
| 38 |
+
d.text((x1, max(0, y1 - 12)), txt)
|
| 39 |
+
|
| 40 |
+
return out
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def predict(model_label: str, img: Image.Image):
|
| 44 |
+
if img is None or not model_label:
|
| 45 |
+
return None, [], ""
|
| 46 |
+
|
| 47 |
+
buf = io.BytesIO()
|
| 48 |
+
img.convert("RGB").save(buf, format="JPEG")
|
| 49 |
+
buf.seek(0)
|
| 50 |
+
|
| 51 |
+
t0 = time.perf_counter()
|
| 52 |
+
r = requests.post(
|
| 53 |
+
f"{API_URL}/predict",
|
| 54 |
+
files={"file": ("image.jpg", buf, "image/jpeg")},
|
| 55 |
+
data={"model_name": MODEL_MAP[model_label]},
|
| 56 |
+
timeout=60,
|
| 57 |
+
)
|
| 58 |
+
dt = time.perf_counter() - t0
|
| 59 |
|
| 60 |
+
if r.status_code != 200:
|
| 61 |
+
return None, {"error": r.text}, f"{dt:.3f} s"
|
| 62 |
|
| 63 |
+
data = r.json()
|
| 64 |
+
dets = data.get("detections", []) or []
|
| 65 |
+
vis = _draw_detections(img, dets)
|
| 66 |
|
| 67 |
+
return vis, dets, f"{dt:.3f} s"
|
| 68 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
+
with gr.Blocks(title="YOLO detection demo") as demo:
|
| 71 |
+
gr.Markdown("# YOLO detection demo")
|
| 72 |
|
| 73 |
+
model_dd = gr.Dropdown(
|
| 74 |
+
choices=MODEL_CHOICES,
|
| 75 |
+
value=DEFAULT_MODEL,
|
| 76 |
+
label="Model",
|
| 77 |
+
)
|
| 78 |
|
| 79 |
+
with gr.Row():
|
| 80 |
+
with gr.Column(scale=1):
|
| 81 |
+
img_in = gr.Image(
|
| 82 |
+
type="pil",
|
| 83 |
+
label="Image",
|
| 84 |
+
sources=["upload"],
|
| 85 |
+
height=200,
|
| 86 |
+
)
|
| 87 |
|
| 88 |
+
ex = [p for p in [EXAMPLE_1, EXAMPLE_2] if p]
|
| 89 |
+
if ex:
|
| 90 |
+
gr.Examples(examples=ex, inputs=img_in)
|
| 91 |
|
| 92 |
+
btn = gr.Button("Submit")
|
|
|
|
|
|
|
| 93 |
|
| 94 |
+
with gr.Column(scale=2):
|
| 95 |
+
out_img = gr.Image(type="pil", label="Result", height=600)
|
| 96 |
+
out_json = gr.JSON(label="Detections")
|
| 97 |
+
out_time = gr.Textbox(label="Processing time")
|
| 98 |
|
| 99 |
+
btn.click(predict, inputs=[model_dd, img_in], outputs=[out_img, out_json, out_time])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
if __name__ == "__main__":
|
| 102 |
demo.launch(server_name="0.0.0.0", server_port=7860)
|
| 103 |
+
# uv run gradio planparser/app.py
|
pyproject.toml
CHANGED
|
@@ -6,6 +6,14 @@ readme = "README.md"
|
|
| 6 |
requires-python = ">=3.12"
|
| 7 |
dependencies = [
|
| 8 |
"gradio>=6.2.0",
|
|
|
|
| 9 |
"torch>=2.9.1",
|
| 10 |
"torchvision>=0.24.1",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
]
|
|
|
|
| 6 |
requires-python = ">=3.12"
|
| 7 |
dependencies = [
|
| 8 |
"gradio>=6.2.0",
|
| 9 |
+
"requests>=2.32.5",
|
| 10 |
"torch>=2.9.1",
|
| 11 |
"torchvision>=0.24.1",
|
| 12 |
+
"ultralytics>=8.3.243",
|
| 13 |
+
]
|
| 14 |
+
|
| 15 |
+
[dependency-groups]
|
| 16 |
+
dev = [
|
| 17 |
+
"jupyterlab>=4.5.1",
|
| 18 |
+
"python-dotenv>=1.2.1",
|
| 19 |
]
|
src/data/data.yaml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
train: ../train/images
|
| 2 |
+
val: ../valid/images
|
| 3 |
+
test: ../test/images
|
| 4 |
+
|
| 5 |
+
nc: 15
|
| 6 |
+
names: ['bathtub', 'bed', 'bed2', 'chair', 'door', 'door2', 'shower', 'sink', 'sofa1', 'sofa2', 'sofa3', 'stove', 'table', 'toilet', 'vanity']
|
| 7 |
+
|
| 8 |
+
roboflow:
|
| 9 |
+
workspace: research-g8szb
|
| 10 |
+
project: floorplan-details-fork
|
| 11 |
+
version: 1
|
| 12 |
+
license: CC BY 4.0
|
| 13 |
+
url: https://universe.roboflow.com/research-g8szb/floorplan-details-fork/dataset/1
|
src/examples/example1.jpg
ADDED
|
Git LFS Details
|
src/examples/example2.jpg
ADDED
|
Git LFS Details
|