Spaces:
Sleeping
Sleeping
cyberai-1 commited on
Commit ·
1a6e37b
1
Parent(s): 0a92b1d
Update upload
Browse files- app.py +87 -38
- templates/logs.html +7 -5
- test_csv_format.py +1 -1
app.py
CHANGED
|
@@ -36,6 +36,14 @@ COCO_TO_LABEL = {
|
|
| 36 |
}
|
| 37 |
DEFAULT_CLASSES = list(COCO_TO_LABEL.values())
|
| 38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
CLASS_COLORS = {
|
| 40 |
"Vehicle": (255, 120, 80),
|
| 41 |
"Motorcycle": (80, 200, 80),
|
|
@@ -242,6 +250,7 @@ def _worker(jid):
|
|
| 242 |
job["progress"] = 0
|
| 243 |
job["stats"] = {}
|
| 244 |
classes_filter = set(job.get("classes") or DEFAULT_CLASSES)
|
|
|
|
| 245 |
|
| 246 |
print(f"\n[PROCESS] Opening video: {job['path']}")
|
| 247 |
cap = cv2.VideoCapture(job["path"])
|
|
@@ -290,24 +299,36 @@ def _worker(jid):
|
|
| 290 |
frame_count += 1
|
| 291 |
timestamp_sec = (frame_count - 1) / fps if fps > 0 else 0
|
| 292 |
|
| 293 |
-
# Run
|
| 294 |
-
results = _model(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
|
| 296 |
# Draw detections on frame
|
| 297 |
annotated_frame = frame.copy()
|
| 298 |
-
if results[0].boxes:
|
| 299 |
-
for box in results[0].boxes:
|
| 300 |
cls = int(box.cls[0])
|
| 301 |
conf = float(box.conf[0])
|
| 302 |
class_name = COCO_TO_LABEL.get(cls)
|
| 303 |
|
| 304 |
if class_name and class_name in classes_filter:
|
| 305 |
-
job["detections"][class_name] = job["detections"].get(class_name, 0) + 1
|
| 306 |
-
|
| 307 |
# Get bounding box coordinates
|
| 308 |
x1, y1, x2, y2 = map(int, box.xyxy[0])
|
| 309 |
cx = (x1 + x2) // 2
|
| 310 |
cy = (y1 + y2) // 2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
|
| 312 |
# Store detection data for CSV
|
| 313 |
frame_detections.append({
|
|
@@ -316,7 +337,7 @@ def _worker(jid):
|
|
| 316 |
"scene_name": jid,
|
| 317 |
"group_id": jid,
|
| 318 |
"video_name": job["name"],
|
| 319 |
-
"track_id":
|
| 320 |
"class_name": class_name,
|
| 321 |
"confidence": conf,
|
| 322 |
"bbox_x1": x1,
|
|
@@ -442,6 +463,42 @@ def api_logs():
|
|
| 442 |
with _stats_lock:
|
| 443 |
return jsonify(_global_stats["scenes"]), 200
|
| 444 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 445 |
@app.route("/api/logs/<scene_id>/csv")
|
| 446 |
def api_logs_csv(scene_id):
|
| 447 |
job = _jobs.get(scene_id)
|
|
@@ -449,41 +506,33 @@ def api_logs_csv(scene_id):
|
|
| 449 |
return jsonify({"error": "not found"}), 404
|
| 450 |
|
| 451 |
output = io.StringIO()
|
| 452 |
-
writer = csv.
|
| 453 |
-
|
| 454 |
-
# Write CSV header - SCHEMA_EXAMPLE.csv format
|
| 455 |
-
writer.writerow([
|
| 456 |
-
"frame", "timestamp_sec", "scene_name", "group_id",
|
| 457 |
-
"video_name", "track_id", "class_name", "confidence",
|
| 458 |
-
"bbox_x1", "bbox_y1", "bbox_x2", "bbox_y2",
|
| 459 |
-
"cx", "cy", "frame_width", "frame_height",
|
| 460 |
-
"crossed_line", "direction", "speed_px_s"
|
| 461 |
-
])
|
| 462 |
|
| 463 |
# Write detections for each frame
|
| 464 |
frame_detections = job.get("frame_detections", [])
|
| 465 |
for det in frame_detections:
|
| 466 |
-
writer.writerow(
|
| 467 |
-
det["frame"],
|
| 468 |
-
f"{det['timestamp_sec']:.3f}",
|
| 469 |
-
det["scene_name"],
|
| 470 |
-
det["group_id"],
|
| 471 |
-
det["video_name"],
|
| 472 |
-
det["track_id"],
|
| 473 |
-
det["class_name"],
|
| 474 |
-
f"{det['confidence']:.3f}",
|
| 475 |
-
det["bbox_x1"],
|
| 476 |
-
det["bbox_y1"],
|
| 477 |
-
det["bbox_x2"],
|
| 478 |
-
det["bbox_y2"],
|
| 479 |
-
det["cx"],
|
| 480 |
-
det["cy"],
|
| 481 |
-
det["frame_width"],
|
| 482 |
-
det["frame_height"],
|
| 483 |
-
det["crossed_line"],
|
| 484 |
-
det["direction"],
|
| 485 |
-
f"{det['speed_px_s']:.1f}"
|
| 486 |
-
|
| 487 |
|
| 488 |
return Response(
|
| 489 |
output.getvalue(),
|
|
|
|
| 36 |
}
|
| 37 |
DEFAULT_CLASSES = list(COCO_TO_LABEL.values())
|
| 38 |
|
| 39 |
+
CSV_FIELDS = [
|
| 40 |
+
"frame", "timestamp_sec", "scene_name", "group_id",
|
| 41 |
+
"video_name", "track_id", "class_name", "confidence",
|
| 42 |
+
"bbox_x1", "bbox_y1", "bbox_x2", "bbox_y2",
|
| 43 |
+
"cx", "cy", "frame_width", "frame_height",
|
| 44 |
+
"crossed_line", "direction", "speed_px_s"
|
| 45 |
+
]
|
| 46 |
+
|
| 47 |
CLASS_COLORS = {
|
| 48 |
"Vehicle": (255, 120, 80),
|
| 49 |
"Motorcycle": (80, 200, 80),
|
|
|
|
| 250 |
job["progress"] = 0
|
| 251 |
job["stats"] = {}
|
| 252 |
classes_filter = set(job.get("classes") or DEFAULT_CLASSES)
|
| 253 |
+
counted_track_ids = set()
|
| 254 |
|
| 255 |
print(f"\n[PROCESS] Opening video: {job['path']}")
|
| 256 |
cap = cv2.VideoCapture(job["path"])
|
|
|
|
| 299 |
frame_count += 1
|
| 300 |
timestamp_sec = (frame_count - 1) / fps if fps > 0 else 0
|
| 301 |
|
| 302 |
+
# Run tracking so each physical object gets a stable track_id.
|
| 303 |
+
results = _model.track(
|
| 304 |
+
frame,
|
| 305 |
+
conf=CONF,
|
| 306 |
+
iou=IOU,
|
| 307 |
+
imgsz=INFER_SZ,
|
| 308 |
+
persist=True,
|
| 309 |
+
verbose=False
|
| 310 |
+
)
|
| 311 |
|
| 312 |
# Draw detections on frame
|
| 313 |
annotated_frame = frame.copy()
|
| 314 |
+
if results and results[0].boxes:
|
| 315 |
+
for box_index, box in enumerate(results[0].boxes):
|
| 316 |
cls = int(box.cls[0])
|
| 317 |
conf = float(box.conf[0])
|
| 318 |
class_name = COCO_TO_LABEL.get(cls)
|
| 319 |
|
| 320 |
if class_name and class_name in classes_filter:
|
|
|
|
|
|
|
| 321 |
# Get bounding box coordinates
|
| 322 |
x1, y1, x2, y2 = map(int, box.xyxy[0])
|
| 323 |
cx = (x1 + x2) // 2
|
| 324 |
cy = (y1 + y2) // 2
|
| 325 |
+
raw_track_id = box.id[0] if box.id is not None else None
|
| 326 |
+
track_id = str(int(raw_track_id)) if raw_track_id is not None else f"untracked-{class_name}-{box_index}"
|
| 327 |
+
unique_key = (class_name, track_id)
|
| 328 |
+
|
| 329 |
+
if unique_key not in counted_track_ids:
|
| 330 |
+
counted_track_ids.add(unique_key)
|
| 331 |
+
job["detections"][class_name] = job["detections"].get(class_name, 0) + 1
|
| 332 |
|
| 333 |
# Store detection data for CSV
|
| 334 |
frame_detections.append({
|
|
|
|
| 337 |
"scene_name": jid,
|
| 338 |
"group_id": jid,
|
| 339 |
"video_name": job["name"],
|
| 340 |
+
"track_id": track_id,
|
| 341 |
"class_name": class_name,
|
| 342 |
"confidence": conf,
|
| 343 |
"bbox_x1": x1,
|
|
|
|
| 463 |
with _stats_lock:
|
| 464 |
return jsonify(_global_stats["scenes"]), 200
|
| 465 |
|
| 466 |
+
@app.route("/api/logs/rows")
|
| 467 |
+
def api_logs_rows():
|
| 468 |
+
rows = []
|
| 469 |
+
for jid, job in _jobs.items():
|
| 470 |
+
for det in job.get("frame_detections", []):
|
| 471 |
+
rows.append({
|
| 472 |
+
"frame": det["frame"],
|
| 473 |
+
"frame_id": det["frame"],
|
| 474 |
+
"timestamp_sec": det["timestamp_sec"],
|
| 475 |
+
"timestamp_s": det["timestamp_sec"],
|
| 476 |
+
"scene_name": det["scene_name"],
|
| 477 |
+
"scene_id": jid,
|
| 478 |
+
"group_id": det["group_id"],
|
| 479 |
+
"video_name": det["video_name"],
|
| 480 |
+
"track_id": det["track_id"],
|
| 481 |
+
"class_name": det["class_name"],
|
| 482 |
+
"confidence": det["confidence"],
|
| 483 |
+
"bbox_x1": det["bbox_x1"],
|
| 484 |
+
"bbox_y1": det["bbox_y1"],
|
| 485 |
+
"bbox_x2": det["bbox_x2"],
|
| 486 |
+
"bbox_y2": det["bbox_y2"],
|
| 487 |
+
"x1": det["bbox_x1"],
|
| 488 |
+
"y1": det["bbox_y1"],
|
| 489 |
+
"x2": det["bbox_x2"],
|
| 490 |
+
"y2": det["bbox_y2"],
|
| 491 |
+
"cx": det["cx"],
|
| 492 |
+
"cy": det["cy"],
|
| 493 |
+
"frame_width": det["frame_width"],
|
| 494 |
+
"frame_height": det["frame_height"],
|
| 495 |
+
"crossed_line": det["crossed_line"],
|
| 496 |
+
"direction": det["direction"],
|
| 497 |
+
"speed_px_s": det["speed_px_s"]
|
| 498 |
+
})
|
| 499 |
+
rows.sort(key=lambda r: (r["scene_id"], r["frame"], str(r["track_id"])))
|
| 500 |
+
return jsonify(rows), 200
|
| 501 |
+
|
| 502 |
@app.route("/api/logs/<scene_id>/csv")
|
| 503 |
def api_logs_csv(scene_id):
|
| 504 |
job = _jobs.get(scene_id)
|
|
|
|
| 506 |
return jsonify({"error": "not found"}), 404
|
| 507 |
|
| 508 |
output = io.StringIO()
|
| 509 |
+
writer = csv.DictWriter(output, fieldnames=CSV_FIELDS, lineterminator="\n")
|
| 510 |
+
writer.writeheader()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 511 |
|
| 512 |
# Write detections for each frame
|
| 513 |
frame_detections = job.get("frame_detections", [])
|
| 514 |
for det in frame_detections:
|
| 515 |
+
writer.writerow({
|
| 516 |
+
"frame": det["frame"],
|
| 517 |
+
"timestamp_sec": f"{det['timestamp_sec']:.3f}",
|
| 518 |
+
"scene_name": det["scene_name"],
|
| 519 |
+
"group_id": det["group_id"],
|
| 520 |
+
"video_name": det["video_name"],
|
| 521 |
+
"track_id": det["track_id"],
|
| 522 |
+
"class_name": det["class_name"],
|
| 523 |
+
"confidence": f"{det['confidence']:.3f}",
|
| 524 |
+
"bbox_x1": det["bbox_x1"],
|
| 525 |
+
"bbox_y1": det["bbox_y1"],
|
| 526 |
+
"bbox_x2": det["bbox_x2"],
|
| 527 |
+
"bbox_y2": det["bbox_y2"],
|
| 528 |
+
"cx": det["cx"],
|
| 529 |
+
"cy": det["cy"],
|
| 530 |
+
"frame_width": det["frame_width"],
|
| 531 |
+
"frame_height": det["frame_height"],
|
| 532 |
+
"crossed_line": det["crossed_line"],
|
| 533 |
+
"direction": det["direction"],
|
| 534 |
+
"speed_px_s": f"{det['speed_px_s']:.1f}"
|
| 535 |
+
})
|
| 536 |
|
| 537 |
return Response(
|
| 538 |
output.getvalue(),
|
templates/logs.html
CHANGED
|
@@ -207,6 +207,7 @@ async function loadLogs(){
|
|
| 207 |
video_name: s.video_name||'', class_name: cls,
|
| 208 |
confidence: 0, frame_id: 0, timestamp_s: 0,
|
| 209 |
track_id: 0, x1:0, y1:0, x2:0, y2:0, cx:0, cy:0,
|
|
|
|
| 210 |
direction:'unknown', speed_px_s:0, crossed_line:false
|
| 211 |
});
|
| 212 |
});
|
|
@@ -307,17 +308,18 @@ function getCsvFilename(){
|
|
| 307 |
return `${g}_${scenes.length===1?scenes[0]:'all'}.csv`;
|
| 308 |
}
|
| 309 |
function buildCsvContent(){
|
| 310 |
-
//
|
| 311 |
-
const hdr = 'frame,timestamp_sec,scene_name,group_id,video_name,track_id,class_name,confidence,bbox_x1,bbox_y1,bbox_x2,bbox_y2,cx,cy,crossed_line,direction,speed_px_s\n';
|
| 312 |
const g = getGroupName();
|
| 313 |
const rows = filteredRows.map(r => [
|
| 314 |
-
r.frame_id??'', r.timestamp_s??'',
|
| 315 |
r.scene_name||r.scene_id||'',
|
| 316 |
-
g,
|
| 317 |
`"${(r.video_name||'').replace(/"/g,'""')}"`,
|
| 318 |
r.track_id??'', r.class_name||'', r.confidence??'',
|
| 319 |
-
r.x1??'', r.y1??'', r.x2??'', r.y2??'',
|
| 320 |
r.cx??'', r.cy??'',
|
|
|
|
| 321 |
r.crossed_line??false,
|
| 322 |
r.direction||'unknown',
|
| 323 |
r.speed_px_s??0
|
|
|
|
| 207 |
video_name: s.video_name||'', class_name: cls,
|
| 208 |
confidence: 0, frame_id: 0, timestamp_s: 0,
|
| 209 |
track_id: 0, x1:0, y1:0, x2:0, y2:0, cx:0, cy:0,
|
| 210 |
+
frame_width:0, frame_height:0,
|
| 211 |
direction:'unknown', speed_px_s:0, crossed_line:false
|
| 212 |
});
|
| 213 |
});
|
|
|
|
| 308 |
return `${g}_${scenes.length===1?scenes[0]:'all'}.csv`;
|
| 309 |
}
|
| 310 |
function buildCsvContent(){
|
| 311 |
+
// Same schema as SCHEMA_EXAMPLE.csv.
|
| 312 |
+
const hdr = 'frame,timestamp_sec,scene_name,group_id,video_name,track_id,class_name,confidence,bbox_x1,bbox_y1,bbox_x2,bbox_y2,cx,cy,frame_width,frame_height,crossed_line,direction,speed_px_s\n';
|
| 313 |
const g = getGroupName();
|
| 314 |
const rows = filteredRows.map(r => [
|
| 315 |
+
r.frame??r.frame_id??'', r.timestamp_sec??r.timestamp_s??'',
|
| 316 |
r.scene_name||r.scene_id||'',
|
| 317 |
+
r.group_id||g,
|
| 318 |
`"${(r.video_name||'').replace(/"/g,'""')}"`,
|
| 319 |
r.track_id??'', r.class_name||'', r.confidence??'',
|
| 320 |
+
r.bbox_x1??r.x1??'', r.bbox_y1??r.y1??'', r.bbox_x2??r.x2??'', r.bbox_y2??r.y2??'',
|
| 321 |
r.cx??'', r.cy??'',
|
| 322 |
+
r.frame_width??'', r.frame_height??'',
|
| 323 |
r.crossed_line??false,
|
| 324 |
r.direction||'unknown',
|
| 325 |
r.speed_px_s??0
|
test_csv_format.py
CHANGED
|
@@ -75,7 +75,7 @@ frame_detections = [
|
|
| 75 |
|
| 76 |
# Generate CSV
|
| 77 |
output = StringIO()
|
| 78 |
-
writer = csv.writer(output)
|
| 79 |
|
| 80 |
# Header
|
| 81 |
writer.writerow([
|
|
|
|
| 75 |
|
| 76 |
# Generate CSV
|
| 77 |
output = StringIO()
|
| 78 |
+
writer = csv.writer(output, lineterminator="\n")
|
| 79 |
|
| 80 |
# Header
|
| 81 |
writer.writerow([
|