Denny Lulak commited on
Commit
66e269f
·
1 Parent(s): 780879e
Files changed (3) hide show
  1. __pycache__/app.cpython-312.pyc +0 -0
  2. app.py +24 -142
  3. inference.py +112 -0
__pycache__/app.cpython-312.pyc ADDED
Binary file (9.18 kB). View file
 
app.py CHANGED
@@ -1,169 +1,51 @@
1
  # app.py
 
 
2
  import numpy as np
3
  import cv2
4
- import onnxruntime as ort
5
- from fastapi import FastAPI, File, UploadFile, HTTPException
6
- from fastapi.middleware.cors import CORSMiddleware # <-- Add this import
7
- from typing import List, Dict, Tuple
8
- import os
9
- from ultralytics import YOLO
10
 
11
  # Configuration
12
- MODEL_PT_PATH = "model.pt"
13
  MODEL_ONNX_PATH = "model.onnx"
14
- INPUT_SIZE = 640
15
  CLASS_NAMES = ["class0", "class1"]
16
- CONF_THRESHOLD = 0.5
17
- IOU_THRESHOLD = 0.45
 
 
 
 
 
 
18
 
19
  # Initialize FastAPI
20
  app = FastAPI()
21
 
22
- # Add CORS middleware # <-- Add this section
23
  app.add_middleware(
24
  CORSMiddleware,
25
- allow_origins=["*"], # Allows all origins (for testing only!)
26
  allow_credentials=True,
27
- allow_methods=["*"], # Allows all methods
28
- allow_headers=["*"], # Allows all headers
29
  )
30
 
31
- def load_onnx_model() -> ort.InferenceSession:
32
- """Initialize ONNX runtime session"""
33
- options = ort.SessionOptions()
34
- options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
35
- return ort.InferenceSession(
36
- MODEL_ONNX_PATH,
37
- providers=['CUDAExecutionProvider', 'CPUExecutionProvider'],
38
- sess_options=options
39
- )
40
-
41
- # Convert model if needed and load
42
- ort_session = load_onnx_model()
43
-
44
- # Warm-up run
45
- dummy_input = np.random.randn(1, 3, INPUT_SIZE, INPUT_SIZE).astype(np.float32)
46
- ort_session.run(None, {"images": dummy_input})
47
-
48
- # ================== Core Processing Functions ================== #
49
- def compute_iou(box: np.ndarray, boxes: np.ndarray) -> np.ndarray:
50
- """Compute Intersection over Union between a box and multiple boxes"""
51
- xmin = np.maximum(box[0], boxes[:, 0])
52
- ymin = np.maximum(box[1], boxes[:, 1])
53
- xmax = np.minimum(box[2], boxes[:, 2])
54
- ymax = np.minimum(box[3], boxes[:, 3])
55
-
56
- intersection_area = np.maximum(0, xmax - xmin) * np.maximum(0, ymax - ymin)
57
- box_area = (box[2] - box[0]) * (box[3] - box[1])
58
- boxes_area = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
59
-
60
- return intersection_area / (box_area + boxes_area - intersection_area + 1e-6)
61
-
62
- def nms(boxes: np.ndarray, scores: np.ndarray, iou_threshold: float) -> List[int]:
63
- """Non-Maximum Suppression implementation"""
64
- sorted_indices = np.argsort(scores)[::-1]
65
- keep_boxes = []
66
-
67
- while sorted_indices.size > 0:
68
- box_id = sorted_indices[0]
69
- keep_boxes.append(box_id)
70
-
71
- ious = compute_iou(boxes[box_id, :], boxes[sorted_indices[1:], :])
72
- keep_indices = np.where(ious < iou_threshold)[0]
73
- sorted_indices = sorted_indices[keep_indices + 1]
74
-
75
- return keep_boxes
76
-
77
- def preprocess_image(image: np.ndarray) -> Tuple[np.ndarray, float, Tuple[int, int]]:
78
- """Resize and normalize image with letterboxing"""
79
- h, w = image.shape[:2]
80
- scale = min(INPUT_SIZE / h, INPUT_SIZE / w)
81
- new_h, new_w = int(h * scale), int(w * scale)
82
-
83
- resized = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_LINEAR)
84
- canvas = np.full((INPUT_SIZE, INPUT_SIZE, 3), 114, dtype=np.uint8)
85
- ph, pw = (INPUT_SIZE - new_h) // 2, (INPUT_SIZE - new_w) // 2
86
- canvas[ph:ph+new_h, pw:pw+new_w] = resized
87
-
88
- # Normalize and transpose for ONNX
89
- blob = canvas.astype(np.float32) / 255.0
90
- return blob.transpose(2, 0, 1)[None, ...], scale, (pw, ph)
91
-
92
- def postprocess(
93
- predictions: np.ndarray,
94
- original_shape: Tuple[int, int],
95
- scale: float,
96
- padding: Tuple[int, int]
97
- ) -> List[Dict]:
98
- """Process model outputs into final detections"""
99
- predictions = np.squeeze(predictions).T
100
- scores = np.max(predictions[:, 4:], axis=1)
101
- valid = scores > CONF_THRESHOLD
102
- predictions = predictions[valid]
103
-
104
- if predictions.size == 0:
105
- return []
106
-
107
- # Extract boxes
108
- boxes = predictions[:, :4]
109
- boxes[:, [0, 1]] = boxes[:, [0, 1]] - boxes[:, [2, 3]] / 2
110
- boxes[:, [2, 3]] = boxes[:, [0, 1]] + boxes[:, [2, 3]]
111
-
112
- # Adjust for letterbox
113
- pad_w, pad_h = padding
114
- boxes[:, [0, 2]] = (boxes[:, [0, 2]] - pad_w) / scale
115
- boxes[:, [1, 3]] = (boxes[:, [1, 3]] - pad_h) / scale
116
-
117
- # Clip to image dimensions
118
- h, w = original_shape
119
- boxes[:, [0, 2]] = boxes[:, [0, 2]].clip(0, w)
120
- boxes[:, [1, 3]] = boxes[:, [1, 3]].clip(0, h)
121
-
122
- # Get class IDs and apply NMS
123
- class_ids = np.argmax(predictions[:, 4:], axis=1)
124
- indices = nms(boxes, scores[valid], IOU_THRESHOLD)
125
-
126
- return [{
127
- "class": CLASS_NAMES[int(class_ids[i])],
128
- "confidence": float(scores[valid][i]),
129
- "bbox": boxes[i].tolist(), # [x1, y1, x2, y2]
130
- "bbox_normalized": [ # [x_center, y_center, width, height] normalized
131
- float((boxes[i][0] + boxes[i][2])/2 / w),
132
- float((boxes[i][1] + boxes[i][3])/2 / h),
133
- float((boxes[i][2] - boxes[i][0]) / w),
134
- float((boxes[i][3] - boxes[i][1]) / h)
135
- ]
136
- } for i in indices]
137
-
138
- # ================== API Endpoint ================== #
139
- @app.post("/detect")
140
  async def detect_objects(file: UploadFile = File(...)):
141
  try:
142
- # Read and validate image
143
  if not file.content_type.startswith("image/"):
144
- raise HTTPException(status_code=400, detail="Invalid file type")
145
 
146
  image_data = await file.read()
147
  image = cv2.imdecode(np.frombuffer(image_data, np.uint8), cv2.IMREAD_COLOR)
 
148
  if image is None:
149
- raise HTTPException(status_code=400, detail="Invalid image data")
 
 
 
150
 
151
- # Preprocess
152
- input_tensor, scale, padding = preprocess_image(image)
153
-
154
- # Inference
155
- outputs = ort_session.run(None, {"images": input_tensor})
156
-
157
- # Post-process
158
- detections = postprocess(outputs[0], image.shape[:2], scale, padding)
159
-
160
- return {"detections": detections}
161
-
162
  except HTTPException as he:
163
  raise he
164
  except Exception as e:
165
- return {"error": str(e), "detail": "Internal processing error"}
166
-
167
- if __name__ == "__main__":
168
- import uvicorn
169
- uvicorn.run(app, host="0.0.0.0", port=8000)
 
1
  # app.py
2
+ from fastapi import FastAPI, File, UploadFile, HTTPException
3
+ from fastapi.middleware.cors import CORSMiddleware
4
  import numpy as np
5
  import cv2
6
+ from inference import ObjectDetector
7
+ from typing import List
 
 
 
 
8
 
9
  # Configuration
 
10
  MODEL_ONNX_PATH = "model.onnx"
 
11
  CLASS_NAMES = ["class0", "class1"]
12
+ INPUT_SIZE = 640
13
+
14
+ # Initialize detector
15
+ detector = ObjectDetector(
16
+ model_path=MODEL_ONNX_PATH,
17
+ class_names=CLASS_NAMES,
18
+ input_size=INPUT_SIZE
19
+ )
20
 
21
  # Initialize FastAPI
22
  app = FastAPI()
23
 
24
+ # CORS configuration
25
  app.add_middleware(
26
  CORSMiddleware,
27
+ allow_origins=["*"],
28
  allow_credentials=True,
29
+ allow_methods=["*"],
30
+ allow_headers=["*"],
31
  )
32
 
33
+ @app.post("/detect", response_model=List[dict])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  async def detect_objects(file: UploadFile = File(...)):
35
  try:
 
36
  if not file.content_type.startswith("image/"):
37
+ raise HTTPException(400, "Invalid file type")
38
 
39
  image_data = await file.read()
40
  image = cv2.imdecode(np.frombuffer(image_data, np.uint8), cv2.IMREAD_COLOR)
41
+
42
  if image is None:
43
+ raise HTTPException(400, "Invalid image data")
44
+
45
+ detections = detector.predict(image)
46
+ return detections
47
 
 
 
 
 
 
 
 
 
 
 
 
48
  except HTTPException as he:
49
  raise he
50
  except Exception as e:
51
+ return {"error": str(e)}
 
 
 
 
inference.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # inference.py
2
+ import numpy as np
3
+ import cv2
4
+ import onnxruntime as ort
5
+ from typing import List, Dict, Tuple
6
+
7
+ class ObjectDetector:
8
+ def __init__(self, model_path: str, class_names: List[str], input_size: int = 640):
9
+ self.class_names = class_names
10
+ self.input_size = input_size
11
+ self.session = self._load_model(model_path)
12
+ self._warmup()
13
+
14
+ def _load_model(self, model_path: str) -> ort.InferenceSession:
15
+ options = ort.SessionOptions()
16
+ options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
17
+ return ort.InferenceSession(
18
+ model_path,
19
+ providers=['CUDAExecutionProvider', 'CPUExecutionProvider'],
20
+ sess_options=options
21
+ )
22
+
23
+ def _warmup(self):
24
+ dummy_input = np.random.randn(1, 3, self.input_size, self.input_size).astype(np.float32)
25
+ self.session.run(None, {"images": dummy_input})
26
+
27
+ @staticmethod
28
+ def compute_iou(box: np.ndarray, boxes: np.ndarray) -> np.ndarray:
29
+ xmin = np.maximum(box[0], boxes[:, 0])
30
+ ymin = np.maximum(box[1], boxes[:, 1])
31
+ xmax = np.minimum(box[2], boxes[:, 2])
32
+ ymax = np.minimum(box[3], boxes[:, 3])
33
+
34
+ intersection_area = np.maximum(0, xmax - xmin) * np.maximum(0, ymax - ymin)
35
+ box_area = (box[2] - box[0]) * (box[3] - box[1])
36
+ boxes_area = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
37
+ return intersection_area / (box_area + boxes_area - intersection_area + 1e-6)
38
+
39
+ @staticmethod
40
+ def nms(boxes: np.ndarray, scores: np.ndarray, iou_threshold: float) -> List[int]:
41
+ sorted_indices = np.argsort(scores)[::-1]
42
+ keep_boxes = []
43
+
44
+ while sorted_indices.size > 0:
45
+ box_id = sorted_indices[0]
46
+ keep_boxes.append(box_id)
47
+ ious = ObjectDetector.compute_iou(boxes[box_id, :], boxes[sorted_indices[1:], :])
48
+ keep_indices = np.where(ious < iou_threshold)[0]
49
+ sorted_indices = sorted_indices[keep_indices + 1]
50
+ return keep_boxes
51
+
52
+ def preprocess(self, image: np.ndarray) -> Tuple[np.ndarray, float, Tuple[int, int]]:
53
+ h, w = image.shape[:2]
54
+ scale = min(self.input_size / h, self.input_size / w)
55
+ new_h, new_w = int(h * scale), int(w * scale)
56
+
57
+ resized = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_LINEAR)
58
+ canvas = np.full((self.input_size, self.input_size, 3), 114, dtype=np.uint8)
59
+ ph, pw = (self.input_size - new_h) // 2, (self.input_size - new_w) // 2
60
+ canvas[ph:ph+new_h, pw:pw+new_w] = resized
61
+
62
+ blob = canvas.astype(np.float32) / 255.0
63
+ return blob.transpose(2, 0, 1)[None, ...], scale, (pw, ph)
64
+
65
+ def postprocess(
66
+ self,
67
+ predictions: np.ndarray,
68
+ original_shape: Tuple[int, int],
69
+ scale: float,
70
+ padding: Tuple[int, int],
71
+ conf_threshold: float = 0.5,
72
+ iou_threshold: float = 0.45
73
+ ) -> List[Dict]:
74
+ predictions = np.squeeze(predictions).T
75
+ scores = np.max(predictions[:, 4:], axis=1)
76
+ valid = scores > conf_threshold
77
+ predictions = predictions[valid]
78
+
79
+ if predictions.size == 0:
80
+ return []
81
+
82
+ boxes = predictions[:, :4]
83
+ boxes[:, [0, 1]] = boxes[:, [0, 1]] - boxes[:, [2, 3]] / 2
84
+ boxes[:, [2, 3]] = boxes[:, [0, 1]] + boxes[:, [2, 3]]
85
+
86
+ pad_w, pad_h = padding
87
+ boxes[:, [0, 2]] = (boxes[:, [0, 2]] - pad_w) / scale
88
+ boxes[:, [1, 3]] = (boxes[:, [1, 3]] - pad_h) / scale
89
+
90
+ h, w = original_shape
91
+ boxes[:, [0, 2]] = boxes[:, [0, 2]].clip(0, w)
92
+ boxes[:, [1, 3]] = boxes[:, [1, 3]].clip(0, h)
93
+
94
+ class_ids = np.argmax(predictions[:, 4:], axis=1)
95
+ indices = self.nms(boxes, scores[valid], iou_threshold)
96
+
97
+ return [{
98
+ "class": self.class_names[int(class_ids[i])],
99
+ "confidence": float(scores[valid][i]),
100
+ "bbox": boxes[i].tolist(),
101
+ "bbox_normalized": [
102
+ float((boxes[i][0] + boxes[i][2])/2 / w),
103
+ float((boxes[i][1] + boxes[i][3])/2 / h),
104
+ float((boxes[i][2] - boxes[i][0]) / w),
105
+ float((boxes[i][3] - boxes[i][1]) / h)
106
+ ]
107
+ } for i in indices]
108
+
109
+ def predict(self, image: np.ndarray) -> List[Dict]:
110
+ input_tensor, scale, padding = self.preprocess(image)
111
+ outputs = self.session.run(None, {"images": input_tensor})
112
+ return self.postprocess(outputs[0], image.shape[:2], scale, padding)