cyberai-1 commited on
Commit
1a6e37b
·
1 Parent(s): 0a92b1d

Update upload

Browse files
Files changed (3) hide show
  1. app.py +87 -38
  2. templates/logs.html +7 -5
  3. 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 inference
294
- results = _model(frame, conf=CONF, iou=IOU, imgsz=INFER_SZ, verbose=False)
 
 
 
 
 
 
 
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.writer(output)
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
- // Format exact du prof
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([