Ann-Grabetski commited on
Commit
acf75f8
·
1 Parent(s): fa7178a

fasterrcnn_resnet50 model

Browse files
planparser/api.py CHANGED
@@ -1,25 +1,50 @@
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)
19
- _models[weights_path] = m
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  return m
21
 
22
 
 
 
 
 
23
  class Detection(BaseModel):
24
  class_id: int
25
  class_name: str
@@ -40,26 +65,85 @@ def health():
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")
50
-
51
- r0 = load_model(weights_path)(img)[0]
52
- boxes = getattr(r0, "boxes", None)
53
- if boxes is None:
54
- raise HTTPException(status_code=500, detail="model output has no boxes (not a detection model?)")
55
-
56
- names = getattr(r0, "names", {}) or {}
57
- dets: list[Detection] = []
58
- for cls_t, conf_t, xyxy_t in zip(boxes.cls, boxes.conf, boxes.xyxy):
59
- cls_id = int(cls_t.item())
60
- conf = float(conf_t.item())
61
- xyxy = [float(x) for x in xyxy_t.tolist()]
62
- cls_name = names.get(cls_id, str(cls_id)) if isinstance(names, dict) else str(cls_id)
63
- dets.append(Detection(class_id=cls_id, class_name=cls_name, confidence=conf, xyxy=xyxy))
64
-
65
- return PredictResponse(detections=dets)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import io
3
+ from typing import Any
4
 
5
+ import torch
6
  from ultralytics import YOLO
7
+ from PIL import Image, UnidentifiedImageError
8
  from fastapi import FastAPI, UploadFile, File, Form, HTTPException
9
  from pydantic import BaseModel
10
+ from torchvision.transforms.functional import pil_to_tensor as tv_pil_to_tensor
11
 
12
 
13
  app = FastAPI(title="Planparser API")
 
14
 
15
+ _MODEL_CACHE: dict[tuple[str, str], Any] = {}
16
 
17
+
18
+ def _abs_pt(p: str) -> str:
19
+ return os.path.abspath(os.path.expanduser(p))
20
+
21
+
22
+ def get_model(model_type: str, weights_path: str):
23
+ wp = _abs_pt(weights_path)
24
+ key = (model_type, wp)
25
+
26
+ m = _MODEL_CACHE.get(key)
27
+ if m is not None:
28
+ return m
29
+
30
+ if model_type == "yolo":
31
+ m = YOLO(wp)
32
+
33
+ elif model_type == "fasterrcnn":
34
+ m = torch.jit.load(wp, map_location="cpu")
35
+ m.eval()
36
+
37
+ else:
38
+ raise HTTPException(status_code=400, detail="model_type must be yolo or fasterrcnn")
39
+
40
+ _MODEL_CACHE[key] = m
41
  return m
42
 
43
 
44
+ def pil_to_tensor(img: Image.Image) -> torch.Tensor:
45
+ return tv_pil_to_tensor(img.convert("RGB")).float().div(255.0)
46
+
47
+
48
  class Detection(BaseModel):
49
  class_id: int
50
  class_name: str
 
65
  async def predict(
66
  file: UploadFile = File(...),
67
  weights_path: str = Form(...),
68
+ model_type: str = Form("yolo"),
69
+ conf: float = Form(0.25),
70
  ):
71
+ weights_path = _abs_pt(weights_path)
72
  if not (os.path.isfile(weights_path) and weights_path.lower().endswith(".pt")):
73
  raise HTTPException(status_code=400, detail=f"weights_path must be an existing .pt file: {weights_path}")
74
 
75
  raw = await file.read()
76
+ if not raw:
77
+ raise HTTPException(status_code=400, detail="empty file")
78
+
79
+ try:
80
+ img = Image.open(io.BytesIO(raw)).convert("RGB")
81
+ except UnidentifiedImageError:
82
+ raise HTTPException(status_code=400, detail="invalid image file")
83
+
84
+ if model_type == "yolo":
85
+ model = get_model("yolo", weights_path)
86
+ with torch.inference_mode():
87
+ r0 = model.predict(img, device="cpu", verbose=False)[0]
88
+
89
+ boxes = getattr(r0, "boxes", None)
90
+ if boxes is None:
91
+ raise HTTPException(status_code=500, detail="model output has no boxes")
92
+
93
+ names = getattr(r0, "names", {}) or {}
94
+ dets: list[Detection] = []
95
+ for cls_t, conf_t, xyxy_t in zip(boxes.cls, boxes.conf, boxes.xyxy):
96
+ cls_id = int(cls_t.item())
97
+ c = float(conf_t.item())
98
+ if c < float(conf):
99
+ continue
100
+ xyxy = [float(x) for x in xyxy_t.tolist()]
101
+ cls_name = names.get(cls_id, str(cls_id)) if isinstance(names, dict) else str(cls_id)
102
+ dets.append(Detection(class_id=cls_id, class_name=cls_name, confidence=c, xyxy=xyxy))
103
+
104
+ return PredictResponse(detections=dets)
105
+
106
+ if model_type == "fasterrcnn":
107
+ try:
108
+ x = pil_to_tensor(img)
109
+ model = get_model("fasterrcnn", weights_path)
110
+
111
+ get_names = getattr(model, "get_class_names", None)
112
+ class_names = list(get_names()) if callable(get_names) else []
113
+
114
+ with torch.inference_mode():
115
+ out = model([x])
116
+
117
+ boxes = out["boxes"].detach().cpu()
118
+ scores = out["scores"].detach().cpu()
119
+ labels = out["labels"].detach().cpu()
120
+
121
+ dets: list[Detection] = []
122
+ for b, s, l in zip(boxes, scores, labels):
123
+ c = float(s)
124
+ if c < float(conf):
125
+ continue
126
+
127
+ cid = int(l)
128
+ if cid == 0:
129
+ continue
130
+
131
+ cname = class_names[cid] if 0 <= cid < len(class_names) else str(cid)
132
+
133
+ dets.append(
134
+ Detection(
135
+ class_id=cid,
136
+ class_name=cname,
137
+ confidence=c,
138
+ xyxy=[float(v) for v in b.tolist()],
139
+ )
140
+ )
141
+
142
+ return PredictResponse(detections=dets)
143
+
144
+ except HTTPException:
145
+ raise
146
+ except Exception as e:
147
+ raise HTTPException(status_code=500, detail=f"fasterrcnn failed: {type(e).__name__}: {e}")
148
+
149
+ raise HTTPException(status_code=400, detail="model_type must be yolo or fasterrcnn")
planparser/app.py CHANGED
@@ -1,4 +1,3 @@
1
- # planparser/app.py
2
  import os
3
  import io
4
  import time
@@ -61,10 +60,13 @@ RESOLVED_MODEL_DIR = _resolve_models_dir()
61
 
62
  MODEL_MAP = {
63
  "yolo11l_custom": join_pt(RESOLVED_MODEL_DIR, MODEL_1),
64
- "custom": join_pt(RESOLVED_MODEL_DIR, MODEL_2),
65
  }
66
  MODEL_MAP = {k: v for k, v in MODEL_MAP.items() if v}
67
 
 
 
 
68
  MODEL_CHOICES = list(MODEL_MAP.keys())
69
  if not MODEL_CHOICES:
70
  raise RuntimeError("No valid .pt models found via MODEL_DIR + MODEL_1/MODEL_2")
@@ -130,21 +132,24 @@ def _pretty_name(raw: str) -> str:
130
  return CLASS_NAME_MAP.get(raw, raw)
131
 
132
 
133
- def _color_for_det(det: dict) -> tuple[int, int, int]:
134
  cls_id = det.get("class_id", None)
135
  if cls_id is None:
136
  name = det.get("class_name", "")
137
  cls_id = abs(hash(name))
 
 
 
138
  return _hex2rgb(_PALETTE[int(cls_id) % len(_PALETTE)])
139
 
140
 
141
- def _draw_detections(img: Image.Image, dets: list[dict]) -> Image.Image:
142
  out = img.copy().convert("RGB")
143
  d = ImageDraw.Draw(out)
144
 
145
  for det in dets:
146
  x1, y1, x2, y2 = det["xyxy"]
147
- color = _color_for_det(det)
148
 
149
  d.rectangle([x1, y1, x2, y2], outline=color, width=2)
150
 
@@ -193,13 +198,16 @@ def _request_predict(model_label: str, img: Image.Image) -> tuple[list[dict], fl
193
  r = requests.post(
194
  f"{API_URL}/predict",
195
  files={"file": ("image.jpg", buf, "image/jpeg")},
196
- data={"weights_path": MODEL_MAP[model_label]},
 
 
 
197
  timeout=60,
198
  )
199
  dt = time.perf_counter() - t0
200
 
201
  if r.status_code != 200:
202
- return [{"error": r.text}], dt, "error"
203
 
204
  data = r.json()
205
  dets = data.get("detections", []) or []
@@ -225,14 +233,16 @@ def run_predict(model_label: str, img: Image.Image):
225
  if err is not None:
226
  return (
227
  None,
228
- time_md,
229
  empty_df,
230
  dets,
231
  gr.update(value=None, visible=False),
232
- gr.update(visible=False),
233
  )
234
 
235
- vis = _draw_detections(img, dets)
 
 
236
  df_counts = _counts_df(dets)
237
  csv_path = export_df(df_counts)
238
 
 
 
1
  import os
2
  import io
3
  import time
 
60
 
61
  MODEL_MAP = {
62
  "yolo11l_custom": join_pt(RESOLVED_MODEL_DIR, MODEL_1),
63
+ "fasterrcnn_resnet50.pt": join_pt(RESOLVED_MODEL_DIR, MODEL_2),
64
  }
65
  MODEL_MAP = {k: v for k, v in MODEL_MAP.items() if v}
66
 
67
+ def _model_type(model_label: str) -> str:
68
+ return "fasterrcnn" if "fasterrcnn" in model_label.lower() else "yolo"
69
+
70
  MODEL_CHOICES = list(MODEL_MAP.keys())
71
  if not MODEL_CHOICES:
72
  raise RuntimeError("No valid .pt models found via MODEL_DIR + MODEL_1/MODEL_2")
 
132
  return CLASS_NAME_MAP.get(raw, raw)
133
 
134
 
135
+ def _color_for_det(det: dict, model_type: str) -> tuple[int, int, int]:
136
  cls_id = det.get("class_id", None)
137
  if cls_id is None:
138
  name = det.get("class_name", "")
139
  cls_id = abs(hash(name))
140
+ else:
141
+ if model_type == "fasterrcnn":
142
+ cls_id = cls_id - 1
143
  return _hex2rgb(_PALETTE[int(cls_id) % len(_PALETTE)])
144
 
145
 
146
+ def _draw_detections(img: Image.Image, dets: list[dict], model_type: str) -> Image.Image:
147
  out = img.copy().convert("RGB")
148
  d = ImageDraw.Draw(out)
149
 
150
  for det in dets:
151
  x1, y1, x2, y2 = det["xyxy"]
152
+ color = _color_for_det(det, model_type)
153
 
154
  d.rectangle([x1, y1, x2, y2], outline=color, width=2)
155
 
 
198
  r = requests.post(
199
  f"{API_URL}/predict",
200
  files={"file": ("image.jpg", buf, "image/jpeg")},
201
+ data={
202
+ "weights_path": MODEL_MAP[model_label],
203
+ "model_type": _model_type(model_label),
204
+ },
205
  timeout=60,
206
  )
207
  dt = time.perf_counter() - t0
208
 
209
  if r.status_code != 200:
210
+ return [{"error": r.text}], dt, r.text
211
 
212
  data = r.json()
213
  dets = data.get("detections", []) or []
 
233
  if err is not None:
234
  return (
235
  None,
236
+ f"_processing time: {dt:.3f} s_\n\n**API error:**\n{err}",
237
  empty_df,
238
  dets,
239
  gr.update(value=None, visible=False),
240
+ gr.update(visible=True, open=True),
241
  )
242
 
243
+ mt = _model_type(model_label)
244
+ vis = _draw_detections(img, dets, mt)
245
+
246
  df_counts = _counts_df(dets)
247
  csv_path = export_df(df_counts)
248
 
pyproject.toml CHANGED
@@ -17,5 +17,7 @@ dependencies = [
17
 
18
  [dependency-groups]
19
  dev = [
 
 
20
  "jupyterlab>=4.5.1",
21
  ]
 
17
 
18
  [dependency-groups]
19
  dev = [
20
+ "albumentations>=2.0.8",
21
+ "ipywidgets>=8.1.8",
22
  "jupyterlab>=4.5.1",
23
  ]
src/models/fasterrcnn_resnet50.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:26a8b77e25357e2af9f66a6d7ade101a2f0204d48fe3b567759d6ec007f7f3be
3
+ size 166312561