Ann-Grabetski commited on
Commit
df37a62
·
1 Parent(s): 3ef914c

Model path check in App

Browse files
Files changed (2) hide show
  1. planparser/api.py +6 -43
  2. 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
- 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)
@@ -77,9 +39,11 @@ def health():
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")
@@ -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": os.getenv("MODEL_1"),
26
- "custom": os.getenv("MODEL_2"),
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 len(MODEL_CHOICES) != 2:
32
- raise RuntimeError("Exactly two models must be set: MODEL_1 and MODEL_2")
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
- k = min(max_n, len(files))
84
- return [str(x) for x in random.sample(files, k=k)]
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={"model_name": MODEL_MAP[model_label]},
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="Architectural plan elements detection") as demo:
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)