Spaces:
Sleeping
Sleeping
cyberai-1 commited on
Commit ·
aece0e6
1
Parent(s): 8b1c56f
improv home
Browse files- __pycache__/app.cpython-311.pyc +0 -0
- app.py +34 -7
- templates/dashboard.html +43 -4
- templates/home.html +1 -6
__pycache__/app.cpython-311.pyc
CHANGED
|
Binary files a/__pycache__/app.cpython-311.pyc and b/__pycache__/app.cpython-311.pyc differ
|
|
|
app.py
CHANGED
|
@@ -86,6 +86,8 @@ _webcam_classes = []
|
|
| 86 |
_webcam_frame_count = 0
|
| 87 |
_webcam_detections = 0
|
| 88 |
_webcam_detections_by_class = defaultdict(int)
|
|
|
|
|
|
|
| 89 |
_webcam_lock = threading.Lock()
|
| 90 |
|
| 91 |
# ── Chargement du modèle en arrière-plan ──────────────────────────────────────
|
|
@@ -882,6 +884,7 @@ def api_logs_csv(scene_id):
|
|
| 882 |
def api_webcam_start():
|
| 883 |
global _webcam_active, _webcam_classes, _webcam_frame_count
|
| 884 |
global _webcam_detections, _webcam_detections_by_class
|
|
|
|
| 885 |
|
| 886 |
data = request.json or {}
|
| 887 |
classes = data.get("classes", DEFAULT_CLASSES)
|
|
@@ -896,6 +899,8 @@ def api_webcam_start():
|
|
| 896 |
_webcam_frame_count = 0
|
| 897 |
_webcam_detections = 0
|
| 898 |
_webcam_detections_by_class = defaultdict(int)
|
|
|
|
|
|
|
| 899 |
|
| 900 |
print(f"✅ Webcam started with classes: {classes}")
|
| 901 |
return jsonify({"status": "started"}), 200
|
|
@@ -903,6 +908,7 @@ def api_webcam_start():
|
|
| 903 |
@app.route("/api/webcam/frame", methods=["POST"])
|
| 904 |
def api_webcam_frame():
|
| 905 |
global _webcam_frame_count, _webcam_detections, _webcam_detections_by_class
|
|
|
|
| 906 |
|
| 907 |
if not _webcam_active or not _model_ready:
|
| 908 |
return jsonify({"error": "Webcam not active"}), 503
|
|
@@ -918,21 +924,41 @@ def api_webcam_frame():
|
|
| 918 |
if frame is None:
|
| 919 |
return jsonify({"error": "Could not decode image"}), 400
|
| 920 |
|
| 921 |
-
#
|
| 922 |
-
results = _model(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 923 |
|
| 924 |
-
annotated,
|
|
|
|
|
|
|
| 925 |
|
| 926 |
with _webcam_lock:
|
| 927 |
_webcam_frame_count += 1
|
| 928 |
-
_webcam_detections += detection_count
|
| 929 |
|
| 930 |
-
if results[0].boxes:
|
| 931 |
for box in results[0].boxes:
|
| 932 |
cls = int(box.cls[0])
|
| 933 |
class_name = COCO_TO_LABEL.get(cls)
|
| 934 |
if class_name and class_name in _webcam_classes:
|
| 935 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 936 |
|
| 937 |
ret, buffer = cv2.imencode('.jpg', annotated)
|
| 938 |
return Response(buffer.tobytes(), mimetype='image/jpeg'), 200
|
|
@@ -953,12 +979,13 @@ def api_webcam_stop():
|
|
| 953 |
def api_webcam_stats():
|
| 954 |
with _webcam_lock:
|
| 955 |
by_class = dict(_webcam_detections_by_class)
|
|
|
|
| 956 |
return jsonify({
|
| 957 |
"active": _webcam_active,
|
| 958 |
"frame": _webcam_frame_count,
|
| 959 |
"detections": _webcam_detections,
|
| 960 |
"detections_by_class": by_class,
|
| 961 |
-
"frame_counts":
|
| 962 |
"unique_counts": by_class
|
| 963 |
}), 200
|
| 964 |
|
|
|
|
| 86 |
_webcam_frame_count = 0
|
| 87 |
_webcam_detections = 0
|
| 88 |
_webcam_detections_by_class = defaultdict(int)
|
| 89 |
+
_webcam_current_counts = defaultdict(int)
|
| 90 |
+
_webcam_seen_track_ids = set()
|
| 91 |
_webcam_lock = threading.Lock()
|
| 92 |
|
| 93 |
# ── Chargement du modèle en arrière-plan ──────────────────────────────────────
|
|
|
|
| 884 |
def api_webcam_start():
|
| 885 |
global _webcam_active, _webcam_classes, _webcam_frame_count
|
| 886 |
global _webcam_detections, _webcam_detections_by_class
|
| 887 |
+
global _webcam_current_counts, _webcam_seen_track_ids
|
| 888 |
|
| 889 |
data = request.json or {}
|
| 890 |
classes = data.get("classes", DEFAULT_CLASSES)
|
|
|
|
| 899 |
_webcam_frame_count = 0
|
| 900 |
_webcam_detections = 0
|
| 901 |
_webcam_detections_by_class = defaultdict(int)
|
| 902 |
+
_webcam_current_counts = defaultdict(int)
|
| 903 |
+
_webcam_seen_track_ids = set()
|
| 904 |
|
| 905 |
print(f"✅ Webcam started with classes: {classes}")
|
| 906 |
return jsonify({"status": "started"}), 200
|
|
|
|
| 908 |
@app.route("/api/webcam/frame", methods=["POST"])
|
| 909 |
def api_webcam_frame():
|
| 910 |
global _webcam_frame_count, _webcam_detections, _webcam_detections_by_class
|
| 911 |
+
global _webcam_current_counts, _webcam_seen_track_ids
|
| 912 |
|
| 913 |
if not _webcam_active or not _model_ready:
|
| 914 |
return jsonify({"error": "Webcam not active"}), 503
|
|
|
|
| 924 |
if frame is None:
|
| 925 |
return jsonify({"error": "Could not decode image"}), 400
|
| 926 |
|
| 927 |
+
# Tracking YOLO: count unique objects by persistent track_id, not by frame.
|
| 928 |
+
results = _model.track(
|
| 929 |
+
frame,
|
| 930 |
+
conf=CONF,
|
| 931 |
+
iou=IOU,
|
| 932 |
+
imgsz=INFER_SZ,
|
| 933 |
+
persist=True,
|
| 934 |
+
verbose=False
|
| 935 |
+
)
|
| 936 |
|
| 937 |
+
annotated, _ = _draw_detections(frame, results, _webcam_classes)
|
| 938 |
+
current_counts = defaultdict(int)
|
| 939 |
+
new_unique_counts = defaultdict(int)
|
| 940 |
|
| 941 |
with _webcam_lock:
|
| 942 |
_webcam_frame_count += 1
|
|
|
|
| 943 |
|
| 944 |
+
if results and results[0].boxes:
|
| 945 |
for box in results[0].boxes:
|
| 946 |
cls = int(box.cls[0])
|
| 947 |
class_name = COCO_TO_LABEL.get(cls)
|
| 948 |
if class_name and class_name in _webcam_classes:
|
| 949 |
+
current_counts[class_name] += 1
|
| 950 |
+
if box.id is None:
|
| 951 |
+
continue
|
| 952 |
+
track_id = int(box.id[0])
|
| 953 |
+
unique_key = (class_name, track_id)
|
| 954 |
+
if unique_key not in _webcam_seen_track_ids:
|
| 955 |
+
_webcam_seen_track_ids.add(unique_key)
|
| 956 |
+
new_unique_counts[class_name] += 1
|
| 957 |
+
|
| 958 |
+
for class_name, count in new_unique_counts.items():
|
| 959 |
+
_webcam_detections_by_class[class_name] += count
|
| 960 |
+
_webcam_current_counts = current_counts
|
| 961 |
+
_webcam_detections = sum(_webcam_detections_by_class.values())
|
| 962 |
|
| 963 |
ret, buffer = cv2.imencode('.jpg', annotated)
|
| 964 |
return Response(buffer.tobytes(), mimetype='image/jpeg'), 200
|
|
|
|
| 979 |
def api_webcam_stats():
|
| 980 |
with _webcam_lock:
|
| 981 |
by_class = dict(_webcam_detections_by_class)
|
| 982 |
+
frame_counts = dict(_webcam_current_counts)
|
| 983 |
return jsonify({
|
| 984 |
"active": _webcam_active,
|
| 985 |
"frame": _webcam_frame_count,
|
| 986 |
"detections": _webcam_detections,
|
| 987 |
"detections_by_class": by_class,
|
| 988 |
+
"frame_counts": frame_counts,
|
| 989 |
"unique_counts": by_class
|
| 990 |
}), 200
|
| 991 |
|
templates/dashboard.html
CHANGED
|
@@ -265,6 +265,17 @@ window._lastPieColors=[];
|
|
| 265 |
window._lastFlowScene=null;
|
| 266 |
window._lastCounts={};
|
| 267 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
function chartReady(){
|
| 269 |
return false;
|
| 270 |
}
|
|
@@ -485,6 +496,16 @@ function gc(){
|
|
| 485 |
return{grid:dk?'rgba(255,255,255,0.05)':'rgba(0,0,0,0.06)',tick:dk?'#4a5a6a':'#94a3b8'};
|
| 486 |
}
|
| 487 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 488 |
/* ── Pie chart — Object Type Distribution ── */
|
| 489 |
window.pch = null;
|
| 490 |
|
|
@@ -640,13 +661,27 @@ function drawHm(){
|
|
| 640 |
mxR=Math.max(mxR,0.01);mxH=Math.max(mxH,0.01);
|
| 641 |
pts.forEach(p=>{
|
| 642 |
const px=(p.x/mxR)*w,py=(p.y/mxH)*h;
|
| 643 |
-
const rad=Math.max(10,Math.min(w,h)*0.
|
|
|
|
| 644 |
const grd=ctx.createRadialGradient(px,py,0,px,py,rad);
|
| 645 |
-
grd.addColorStop(0,
|
| 646 |
-
grd.addColorStop(0.
|
| 647 |
grd.addColorStop(1,'rgba(0,0,0,0)');
|
| 648 |
ctx.fillStyle=grd;ctx.fillRect(0,0,w,h);
|
| 649 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 650 |
ctx.font="9px 'DM Mono',monospace";
|
| 651 |
ctx.fillStyle=dk?'rgba(255,255,255,0.3)':'rgba(0,0,0,0.25)';
|
| 652 |
ctx.textAlign='left';ctx.fillText(pts.length+' points',8,13);
|
|
@@ -669,7 +704,11 @@ async function loadHeatmap(){
|
|
| 669 |
if(!rows.length)return;
|
| 670 |
const pts=[];let mxX=0,mxY=0;
|
| 671 |
rows.slice(0,4000).forEach(r=>{
|
| 672 |
-
if(r.cx>0&&r.cy>0){
|
|
|
|
|
|
|
|
|
|
|
|
|
| 673 |
});
|
| 674 |
if(!pts.length)return;
|
| 675 |
pts.forEach(p=>{p.r=mxX;p.h=mxY;});
|
|
|
|
| 265 |
window._lastFlowScene=null;
|
| 266 |
window._lastCounts={};
|
| 267 |
|
| 268 |
+
const CLASS_COLORS = {
|
| 269 |
+
'Vehicle':'#3b82f6',
|
| 270 |
+
'Motorcycle':'#10b981',
|
| 271 |
+
'Truck':'#f59e0b',
|
| 272 |
+
'Bus':'#8b5cf6',
|
| 273 |
+
'Human':'#ec4899',
|
| 274 |
+
'Bicycle':'#06b6d4',
|
| 275 |
+
'Traffic light':'#ef4444',
|
| 276 |
+
'Road sign':'#f97316'
|
| 277 |
+
};
|
| 278 |
+
|
| 279 |
function chartReady(){
|
| 280 |
return false;
|
| 281 |
}
|
|
|
|
| 496 |
return{grid:dk?'rgba(255,255,255,0.05)':'rgba(0,0,0,0.06)',tick:dk?'#4a5a6a':'#94a3b8'};
|
| 497 |
}
|
| 498 |
|
| 499 |
+
function hexToRgba(hex,alpha){
|
| 500 |
+
const clean=String(hex||'').replace('#','');
|
| 501 |
+
const value=clean.length===3
|
| 502 |
+
? clean.split('').map(ch=>ch+ch).join('')
|
| 503 |
+
: clean.padEnd(6,'0').slice(0,6);
|
| 504 |
+
const num=parseInt(value,16);
|
| 505 |
+
const r=(num>>16)&255,g=(num>>8)&255,b=num&255;
|
| 506 |
+
return `rgba(${r},${g},${b},${alpha})`;
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
/* ── Pie chart — Object Type Distribution ── */
|
| 510 |
window.pch = null;
|
| 511 |
|
|
|
|
| 661 |
mxR=Math.max(mxR,0.01);mxH=Math.max(mxH,0.01);
|
| 662 |
pts.forEach(p=>{
|
| 663 |
const px=(p.x/mxR)*w,py=(p.y/mxH)*h;
|
| 664 |
+
const rad=Math.max(10,Math.min(w,h)*0.018);
|
| 665 |
+
const color=p.color||CLASS_COLORS[p.cls]||'#dc2626';
|
| 666 |
const grd=ctx.createRadialGradient(px,py,0,px,py,rad);
|
| 667 |
+
grd.addColorStop(0,hexToRgba(color,0.55));
|
| 668 |
+
grd.addColorStop(0.42,hexToRgba(color,0.22));
|
| 669 |
grd.addColorStop(1,'rgba(0,0,0,0)');
|
| 670 |
ctx.fillStyle=grd;ctx.fillRect(0,0,w,h);
|
| 671 |
});
|
| 672 |
+
const classes=[...new Set(pts.map(p=>p.cls).filter(Boolean))].slice(0,8);
|
| 673 |
+
let lx=8,ly=h-12;
|
| 674 |
+
ctx.font="9px 'DM Sans',sans-serif";
|
| 675 |
+
ctx.textAlign='left';
|
| 676 |
+
classes.forEach(cls=>{
|
| 677 |
+
const color=CLASS_COLORS[cls]||'#888';
|
| 678 |
+
ctx.fillStyle=color;
|
| 679 |
+
ctx.fillRect(lx,ly-7,7,7);
|
| 680 |
+
ctx.fillStyle=dk?'rgba(255,255,255,0.55)':'rgba(15,25,35,0.6)';
|
| 681 |
+
ctx.fillText(cls,lx+10,ly);
|
| 682 |
+
lx+=ctx.measureText(cls).width+26;
|
| 683 |
+
if(lx>w-80){lx=8;ly-=14;}
|
| 684 |
+
});
|
| 685 |
ctx.font="9px 'DM Mono',monospace";
|
| 686 |
ctx.fillStyle=dk?'rgba(255,255,255,0.3)':'rgba(0,0,0,0.25)';
|
| 687 |
ctx.textAlign='left';ctx.fillText(pts.length+' points',8,13);
|
|
|
|
| 704 |
if(!rows.length)return;
|
| 705 |
const pts=[];let mxX=0,mxY=0;
|
| 706 |
rows.slice(0,4000).forEach(r=>{
|
| 707 |
+
if(r.cx>0&&r.cy>0){
|
| 708 |
+
const cls=r.class_name||r.class||'Unknown';
|
| 709 |
+
pts.push({x:r.cx,y:r.cy,cls,color:CLASS_COLORS[cls]||'#888',r:0,h:0});
|
| 710 |
+
if(r.cx>mxX)mxX=r.cx;if(r.cy>mxY)mxY=r.cy;
|
| 711 |
+
}
|
| 712 |
});
|
| 713 |
if(!pts.length)return;
|
| 714 |
pts.forEach(p=>{p.r=mxX;p.h=mxY;});
|
templates/home.html
CHANGED
|
@@ -813,12 +813,7 @@ async function _pollWc(){
|
|
| 813 |
const fc=d.frame_counts||{};
|
| 814 |
const uc=d.unique_counts||{};
|
| 815 |
_showCountsWc(fc,uc);
|
| 816 |
-
|
| 817 |
-
if(d.detections!==undefined){
|
| 818 |
-
_showBanner(d.detections===0 && d.frame>0);
|
| 819 |
-
}else{
|
| 820 |
-
_showBanner(!Object.keys(fc).length);
|
| 821 |
-
}
|
| 822 |
}catch(e){}
|
| 823 |
}
|
| 824 |
|
|
|
|
| 813 |
const fc=d.frame_counts||{};
|
| 814 |
const uc=d.unique_counts||{};
|
| 815 |
_showCountsWc(fc,uc);
|
| 816 |
+
_showBanner(!Object.keys(fc).length && d.frame>0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 817 |
}catch(e){}
|
| 818 |
}
|
| 819 |
|