Spaces:
Sleeping
Sleeping
Commit ·
df37a62
1
Parent(s): 3ef914c
Model path check in App
Browse files- planparser/api.py +6 -43
- planparser/app.py +20 -10
planparser/api.py
CHANGED
|
@@ -1,56 +1,18 @@
|
|
| 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 |
-
|
| 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)
|
|
@@ -77,9 +39,11 @@ def health():
|
|
| 77 |
@app.post("/predict", response_model=PredictResponse)
|
| 78 |
async def predict(
|
| 79 |
file: UploadFile = File(...),
|
| 80 |
-
|
| 81 |
):
|
| 82 |
-
weights_path =
|
|
|
|
|
|
|
| 83 |
|
| 84 |
raw = await file.read()
|
| 85 |
img = Image.open(io.BytesIO(raw)).convert("RGB")
|
|
@@ -99,4 +63,3 @@ async def predict(
|
|
| 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
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import io
|
|
|
|
| 3 |
|
| 4 |
from ultralytics import YOLO
|
| 5 |
from PIL import Image
|
| 6 |
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
|
| 7 |
from pydantic import BaseModel
|
|
|
|
| 8 |
|
|
|
|
| 9 |
|
| 10 |
+
app = FastAPI(title="Planparser API")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
_models: dict[str, YOLO] = {}
|
| 12 |
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
def load_model(weights_path: str) -> YOLO:
|
| 15 |
+
weights_path = os.path.abspath(os.path.expanduser(weights_path))
|
| 16 |
m = _models.get(weights_path)
|
| 17 |
if m is None:
|
| 18 |
m = YOLO(weights_path)
|
|
|
|
| 39 |
@app.post("/predict", response_model=PredictResponse)
|
| 40 |
async def predict(
|
| 41 |
file: UploadFile = File(...),
|
| 42 |
+
weights_path: str = Form(...),
|
| 43 |
):
|
| 44 |
+
weights_path = os.path.abspath(os.path.expanduser(weights_path))
|
| 45 |
+
if not (os.path.isfile(weights_path) and weights_path.lower().endswith(".pt")):
|
| 46 |
+
raise HTTPException(status_code=400, detail=f"weights_path must be an existing .pt file: {weights_path}")
|
| 47 |
|
| 48 |
raw = await file.read()
|
| 49 |
img = Image.open(io.BytesIO(raw)).convert("RGB")
|
|
|
|
| 63 |
dets.append(Detection(class_id=cls_id, class_name=cls_name, confidence=conf, xyxy=xyxy))
|
| 64 |
|
| 65 |
return PredictResponse(detections=dets)
|
|
|
planparser/app.py
CHANGED
|
@@ -21,15 +21,27 @@ if not API_URL:
|
|
| 21 |
|
| 22 |
EXAMPLES_DIR = os.getenv("EXAMPLES_DIR")
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
MODEL_MAP = {
|
| 25 |
-
"yolo11l_custom":
|
| 26 |
-
"custom":
|
| 27 |
}
|
| 28 |
MODEL_MAP = {k: v for k, v in MODEL_MAP.items() if v}
|
| 29 |
|
| 30 |
MODEL_CHOICES = list(MODEL_MAP.keys())
|
| 31 |
-
if
|
| 32 |
-
raise RuntimeError("
|
| 33 |
|
| 34 |
DEFAULT_MODEL = MODEL_CHOICES[0]
|
| 35 |
|
|
@@ -80,8 +92,8 @@ def _collect_example_images(max_n: int = 30) -> list[str]:
|
|
| 80 |
if not files:
|
| 81 |
return []
|
| 82 |
|
| 83 |
-
|
| 84 |
-
return [str(x) for x in
|
| 85 |
|
| 86 |
|
| 87 |
def _hex2rgb(h: str) -> tuple[int, int, int]:
|
|
@@ -154,7 +166,7 @@ def _request_predict(model_label: str, img: Image.Image) -> tuple[list[dict], fl
|
|
| 154 |
r = requests.post(
|
| 155 |
f"{API_URL}/predict",
|
| 156 |
files={"file": ("image.jpg", buf, "image/jpeg")},
|
| 157 |
-
data={"
|
| 158 |
timeout=60,
|
| 159 |
)
|
| 160 |
dt = time.perf_counter() - t0
|
|
@@ -220,7 +232,7 @@ def maybe_autorun(model_label: str, img: Image.Image, auto_run: bool):
|
|
| 220 |
return run_predict(model_label, img)
|
| 221 |
|
| 222 |
|
| 223 |
-
with gr.Blocks(title="
|
| 224 |
gr.Markdown("# Architectural plan elements detection")
|
| 225 |
|
| 226 |
with gr.Row():
|
|
@@ -281,5 +293,3 @@ with gr.Blocks(title="Architectural plan elements detection") as demo:
|
|
| 281 |
|
| 282 |
if __name__ == "__main__":
|
| 283 |
demo.launch(server_name="0.0.0.0", server_port=7860)
|
| 284 |
-
|
| 285 |
-
# uv run gradio planparser/app.py
|
|
|
|
| 21 |
|
| 22 |
EXAMPLES_DIR = os.getenv("EXAMPLES_DIR")
|
| 23 |
|
| 24 |
+
MODEL_DIR = os.getenv("MODEL_DIR")
|
| 25 |
+
MODEL_1 = os.getenv("MODEL_1")
|
| 26 |
+
MODEL_2 = os.getenv("MODEL_2")
|
| 27 |
+
|
| 28 |
+
def join_pt(folder: str | None, name: str | None) -> str | None:
|
| 29 |
+
if not folder or not name:
|
| 30 |
+
return None
|
| 31 |
+
p = (Path(folder).expanduser() / name).resolve()
|
| 32 |
+
if p.is_file() and p.suffix.lower() == ".pt":
|
| 33 |
+
return str(p)
|
| 34 |
+
return None
|
| 35 |
+
|
| 36 |
MODEL_MAP = {
|
| 37 |
+
"yolo11l_custom": join_pt(MODEL_DIR, MODEL_1),
|
| 38 |
+
"custom": join_pt(MODEL_DIR, MODEL_2),
|
| 39 |
}
|
| 40 |
MODEL_MAP = {k: v for k, v in MODEL_MAP.items() if v}
|
| 41 |
|
| 42 |
MODEL_CHOICES = list(MODEL_MAP.keys())
|
| 43 |
+
if not MODEL_CHOICES:
|
| 44 |
+
raise RuntimeError("No valid .pt models found via MODEL_DIR + MODEL_1/MODEL_2")
|
| 45 |
|
| 46 |
DEFAULT_MODEL = MODEL_CHOICES[0]
|
| 47 |
|
|
|
|
| 92 |
if not files:
|
| 93 |
return []
|
| 94 |
|
| 95 |
+
random.shuffle(files)
|
| 96 |
+
return [str(x) for x in files[:min(max_n, len(files))]]
|
| 97 |
|
| 98 |
|
| 99 |
def _hex2rgb(h: str) -> tuple[int, int, int]:
|
|
|
|
| 166 |
r = requests.post(
|
| 167 |
f"{API_URL}/predict",
|
| 168 |
files={"file": ("image.jpg", buf, "image/jpeg")},
|
| 169 |
+
data={"weights_path": MODEL_MAP[model_label]},
|
| 170 |
timeout=60,
|
| 171 |
)
|
| 172 |
dt = time.perf_counter() - t0
|
|
|
|
| 232 |
return run_predict(model_label, img)
|
| 233 |
|
| 234 |
|
| 235 |
+
with gr.Blocks(title="Planparser") as demo:
|
| 236 |
gr.Markdown("# Architectural plan elements detection")
|
| 237 |
|
| 238 |
with gr.Row():
|
|
|
|
| 293 |
|
| 294 |
if __name__ == "__main__":
|
| 295 |
demo.launch(server_name="0.0.0.0", server_port=7860)
|
|
|
|
|
|