Files
bytetrack-counter-dashboard/SPEC.md
T

157 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SPEC — bytetrack-counter
## §G — Goal
RTSP camera → YOLO RKNN (NPU) → ByteTrack → line-crossing counter → SQLite + Flask dashboard.
Count `ayam` (chicken) crossing counting line. Close batch on `talenan` (cutting-board) crossing.
Daily cutoff @ HH:MM resets batch numbering. RK3588 hardware.
**Reference implementation for C++ port in separate repo.**
## §C — Constraints
- Python 3.10, `rknn-toolkit-lite2` (NPU), `opencv-python` (RTSP/FFmpeg)
- `numpy<2` (rknn-toolkit-lite2 incompatible with numpy≥2)
- `PYTHONNOUSERSITE=1` ! set or venv breaks
- SQLite for persistence, JSON file for active-batch state
- Flask on port 5000 (dashboard) + 5002 (recounting), systemd supervision
- `counter_live.py` ⊥ run on RK3588 — Jetson TensorRT artifact, ! port target
- `counter_live_rknn_bytetrack.py` — reference for C++ port, this is what systemd runs
- Single deployment: `counter_live_rknn_bytetrack.py` + `counter_dashboard.py` + `recounting_dashboard.py`
- `go2rtc` ! running for recounting MP4 streaming (port 1984)
## §I — Interfaces
### Systemd
```
unit: bytetrack-counter.service → `venv/bin/python counter_live_rknn_bytetrack.py`
unit: bytetrack-counter-dashboard.service → `venv/bin/python counter_dashboard.py`
unit: bytetrack-recounting-dashboard.service → `venv/bin/python recounting_dashboard.py`
env: PYTHONNOUSERSITE=1 ! set in all units
env: EnvironmentFile=/opt/bytetrack-counter/.env
user: root (not jetson)
path: /opt/bytetrack-counter (fixed in service file)
```
### `.env` config (40+ vars, gitignored)
```
env: SOURCE ! set → RTSP URL | file path
env: MODEL_PATH ! set → .rknn file
env: IMGSZ ! set → model input size (e.g. 320)
env: CORE_MASK → NPU core mask (1=core0, 2=core1, 3=dual, 7=all)
env: NUM_CLASSES ! match model output count
env: CONF → detection confidence threshold (default 0.3)
env: DAILY_CUTOFF_TIME → HH:MM, default "20:00"
env: CROSS_DIRECTION → rtl | ltr | both (default rtl)
env: LINE_X | LINE_X_FRAC → counting line position
env: CLASS_AYAM → class name for counted object (index 0)
env: CLASS_TALENAN → class name for batch-close trigger (index 1)
env: RESET_COUNTERS_AT_CUTOFF → defined in example, ! consumed by code ? zombie
env: MOTION_DETECTION_ENABLED | MOTION_THRESHOLD → skip inference on still frames ?
env: TRACK_HIGH_THRESH_{0,1} | TRACK_LOW_THRESH_{0,1} | TRACK_MATCH_THRESH_{0,1} → ByteTrack params per class
env: BATCH_TIMEOUT_SECONDS → auto-close after inactivity (default 300)
env: IGNORE_BATCH_LABEL_TIMEOUT_SECONDS → suppress talenan close after batch start (default 30)
env: MIN_OBJECT_PER_BATCH → min count to persist batch (default 60)
env: MIN_DURATION_PER_BATCH → min seconds to persist batch (default 60)
env: LIVE_STREAM_ENABLED → write annotated JPEG snapshot each N frames
env: EXPORT_CSV → write per-crossing CSV (default true)
### Recounting dashboard config
```
env: RECOUNTING_DASHBOARD_PORT → port for recounting UI (default 5002)
env: LIVE_API_URL → base URL of live counter API (default http://localhost:5000)
env: RECOUNT_API_URL → base URL of recounting counter API (second node)
env: GO2RTC_API_URL → go2rtc REST API (default http://localhost:1984)
env: GO2RTC_STREAM_NAME → go2rtc stream name for recount preview (default "recount")
```
```
### SQLite
```
table: batches (date, batch#, camera, label, count, start, end, created_at)
UNIQUE(counting_date, batch_number, camera_name, object_label)
table: daily_summaries (date, camera, label, total_count, total_batches, updated_at)
UNIQUE(counting_date, camera_name, object_label)
```
### JSON state file
```
path: /tmp/bytetrack_current_batch.json (default)
schema: {counting_date, batch_number, count, start_time, last_detection_time, counted_event_ids[]}
```
### Flask API
```
api: GET / → dashboard HTML
api: GET /api/current-batch → {count, batch_number, counting_date, start_time, last_detection_time}
api: GET /api/previous-batch → {date, batch#, count, start/end, duration_minutes}
api: GET /api/summary → {today, yesterday, all_time, average_per_day, best_day}
api: GET /api/daily-data?days=N → [ {date, total_count, total_batches, avg_per_batch} ]
api: GET /api/day-detail/<date> → {date, total_count, total_batches, total_duration, avg_duration, batches[]}
api: GET /api/recent-batches?limit=N → [ {date, batch#, count, start/end, duration} ]
api: GET /api/available-dates → [ {date, total_count, total_batches} ]
api: GET /api/export-daily-csv?days=N → .xlsx download (named csv, emits xlsx)
api: GET /api/export-day-csv/<date> → .xlsx download (named csv, emits xlsx)
api: GET /api/live-video → MJPEG stream from shared-memory JPEG
### Recounting dashboard
```
api: GET / → recounting HTML
api: GET /api/live-progress → proxy to LIVE_API_URL:/api/current-batch
api: GET /api/recount-progress → proxy to RECOUNT_API_URL:/api/current-batch
api: GET /api/batch-result?file=<path> → parse `batch_NN_YYYYMMDD_HHmmSS.mp4`, query live API for count
api: GET /api/mp4-files → [ {name, path, size, mtime} ... ]
api: POST /api/start-recount {path} → configure go2rtc stream, return {stream_url}
api: POST /api/stop-recount → tear down go2rtc stream
```
```
## §V — Invariants
```
V1: NUM_CLASSES must match model output → class 0=ayam, class 1=talenan
V2: line crossing → prev_cx > line_x ≥ cx (rtl) | prev_cx < line_x ≤ cx (ltr)
V3: ∀ track_id → counted at most once per batch (counted_event_ids set)
V4: batch persisted → count ≥ MIN_OBJECT_PER_BATCH & duration ≥ MIN_DURATION_PER_BATCH
V5: dt.time() < DAILY_CUTOFF_TIME → counting_date = today, else tomorrow
V6: talenan crossing → close batch, but ignored ∀ IGNORE_BATCH_LABEL_TIMEOUT_SEC after batch start
V7: batch inactivity ≥ BATCH_TIMEOUT_SECONDS → auto-close
V8: PYTHONNOUSERSITE=1 ! set for venv isolation
V9: .env ! exist before counter or dashboard starts
V10: DB tables ! exist on startup (created if absent, both store & dashboard)
V11: previous batch → last CARRY_IDS (default 50) track IDs carried forward to next batch
V12: ∃ ! batch per (date, batch#, camera, label) — UNIQUE constraint in DB
V13: counter & dashboard share DB path → no process-level coordination
V14: stream disconnect → reconnect with delay (RECONNECT_DELAY_SEC), ! block main loop
V15: model inference → letterbox-resize to IMGSZ×IMGSZ, BGR→RGB, run NPU
V16: NMS postprocessing → iou_thr=0.45, class-aware grouping, score > CONF
V17: tracks pruned after TRACKED_PRUNE_SEC (default 300s) without update
V18: .env missing → scripts fail at import (os.getenv falls back to defaults, may mismatch)
V19: stream reconnect → reset motion detection state (prev_gray = None)
```
## §T — Tasks
```
id|status|task|cites
T1|.|unify install paths — README/DEPLOY/setup-venv.sh/uninstall say `/opt/jetson-counter`, service files & .env.example say `/opt/bytetrack-counter`|
T2|.|zombie var RESET_COUNTERS_AT_CUTOFF — defined in .env.example, unused in code (confirmed by review)|
T3|.|add version flag — no `--version` or git-derived version exists|
T4|.|keep reference Python clean — counter_live.py is artifact; focus edits on counter_live_rknn_bytetrack.py as port source|
T5|.|rename export routes — routes named `export-daily-csv` but emit `.xlsx`|I.flask
T6|.|fix live-stream snapshot path — some scripts default `/dev/shm/jetson-counter/`, example says `bytetrack-counter`|
T7|.|add dashboard health-check endpoint (no `/health` or `/api/status` exists)|
T8|.|add model checks on startup — model class names ! validated against CLASS_AYAM/CLASS_TALENAN (TensorRT variant validates; RKNN variants do not)|
T9|.|reset prev_gray on stream reconnect — stale gray ref causes crash or false motion|V19,B1
T10|.|decay inf_ms toward 0 when inference skipped — stale display misleads operator|
T11|x|reduce live-counter poll interval 2000→200ms for real-time feel|
T12|x|add recounting dashboard — dual-API counter panels, MP4 browser, go2rtc streaming|
```
## §B — Bugs
```
id|date|cause|fix
B1|2026-07-29|prev_gray not reset on stream reconnect → cv2.absdiff crash or false motion|V19
```